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
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 131.0 43.1 L 159.6 34.0 L 161.0 64.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 134.0 128.0 L 176.4 110.2 L 172.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 128.0 126.0 Q 123.5 82.7 125.0 41.1" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 125.0 41.1 L 153.6 32.0 L 155.0 62.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 128.0 126.0 L 171.2 110.1 L 168.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 158.0 77.0 L 158.0 49.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="127.8" cy="21.3" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="137.7" y1="22.7" x2="144.6" y2="23.7" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 173.6 58.7 L 201.9 48.6 L 196.0 78.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 134.0 128.0 L 176.4 110.2 L 172.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 128.0 126.0 Q 123.1 76.0 167.6 56.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 167.6 56.7 L 195.9 46.6 L 190.0 76.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 128.0 126.0 L 171.2 110.1 L 168.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 193.0 91.0 L 193.0 63.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="186.2" cy="49.2" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="193.6" y1="55.9" x2="198.8" y2="60.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+40
View File
@@ -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.
+34
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 173.6 58.7 L 201.9 48.6 L 196.0 78.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 134.0 128.0 L 176.4 110.2 L 172.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 128.0 126.0 Q 123.1 76.0 167.6 56.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 167.6 56.7 L 195.9 46.6 L 190.0 76.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 128.0 126.0 L 171.2 110.1 L 168.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 193.0 91.0 L 193.0 63.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="186.2" cy="49.2" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="193.6" y1="55.9" x2="198.8" y2="60.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 213.0 110.0 L 213.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 107.0 110.0 L 107.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.1 121.3 L 140.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 205.1 124.9 L 180.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 213.0 110.0 L 213.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 107.0 110.0 L 107.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.0 119.3 L 104.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 206.0 117.3 L 216.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+38
View File
@@ -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.
+34
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 213.0 110.0 L 213.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 107.0 110.0 L 107.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.0 119.3 L 104.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 206.0 117.3 L 216.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 197.0 110.0 L 197.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 123.0 110.0 L 123.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.0 119.3 L 104.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 206.0 117.3 L 216.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 197.0 110.0 L 197.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 123.0 110.0 L 123.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.1 121.3 L 140.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 205.1 124.9 L 180.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+39
View File
@@ -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.
+34
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 136.0 78.0 L 136.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 188.0 78.0 L 188.0 108.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 197.0 110.0 L 197.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 123.0 110.0 L 123.0 132.0" stroke="#6b7180" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 166.0 33.0 L 145.9 55.3 L 141.8 85.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 118.0 L 120.1 121.3 L 140.0 156.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 116.0 Q 160.0 72.5 160.0 31.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 160.0 31.0 L 180.1 53.3 L 184.2 83.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 116.0 L 205.1 124.9 L 180.0 156.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="160.0" cy="11.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 146.0 42.0 L 176.0 68.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 68.0 L 176.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.4 41.1 L 165.0 58.3 L 183.5 82.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 136.0 126.0 L 179.5 111.0 L 180.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 130.0 124.0 Q 130.8 80.5 134.4 39.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 134.4 39.1 L 159.0 56.3 L 177.5 80.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 130.0 124.0 L 173.8 110.1 L 176.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 180.5 87.0 L 180.5 75.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="137.2" cy="19.3" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="146.7" y1="22.4" x2="153.4" y2="24.6" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 146.0 42.0 L 176.0 68.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 68.0 L 176.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.4 41.1 L 165.0 58.3 L 142.7 38.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 136.0 126.0 L 179.5 111.0 L 180.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 130.0 124.0 Q 130.8 80.5 134.4 39.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 134.4 39.1 L 159.0 56.3 L 136.7 36.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 130.0 124.0 L 173.8 110.1 L 176.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 139.7 43.3 L 139.7 31.3" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="137.2" cy="19.3" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="146.7" y1="22.4" x2="153.4" y2="24.6" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+37
View File
@@ -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.
+32
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 146.0 42.0 L 176.0 68.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 68.0 L 176.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.4 41.1 L 165.0 58.3 L 142.7 38.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 136.0 126.0 L 179.5 111.0 L 180.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 130.0 124.0 Q 130.8 80.5 134.4 39.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 134.4 39.1 L 159.0 56.3 L 136.7 36.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 130.0 124.0 L 173.8 110.1 L 176.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 139.7 43.3 L 139.7 31.3" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="137.2" cy="19.3" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="146.7" y1="22.4" x2="153.4" y2="24.6" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 183.0 62.0 L 183.0 124.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="121" width="27" height="7" rx="2" fill="#c5cad4"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 134.1 35.9 L 162.0 47.1 L 170.0 76.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 120.0 L 192.0 118.0 L 216.0 150.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 118.0 Q 131.0 75.4 128.1 33.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 128.1 33.9 L 155.7 45.8 L 166.0 74.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 118.0 L 186.0 117.6 L 212.0 148.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 207.2 150.8 L 220.8 147.2" stroke="#6b7180" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="131.6" cy="14.2" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="141.5" y1="15.9" x2="148.4" y2="17.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 183.0 62.0 L 183.0 124.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="121" width="27" height="7" rx="2" fill="#c5cad4"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 134.1 35.9 L 162.0 47.1 L 170.0 76.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 120.0 L 190.9 109.9 L 216.0 141.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 118.0 Q 131.0 75.4 128.1 33.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 128.1 33.9 L 155.7 45.8 L 166.0 74.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 118.0 L 185.2 109.3 L 212.0 139.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 207.2 141.8 L 220.8 138.2" stroke="#6b7180" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="131.6" cy="14.2" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="141.5" y1="15.9" x2="148.4" y2="17.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+40
View File
@@ -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.
+36
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 183.0 62.0 L 183.0 124.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="121" width="27" height="7" rx="2" fill="#c5cad4"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 134.1 35.9 L 162.0 47.1 L 170.0 76.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 120.0 L 190.9 109.9 L 216.0 141.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 118.0 Q 131.0 75.4 128.1 33.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 128.1 33.9 L 155.7 45.8 L 166.0 74.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 118.0 L 185.2 109.3 L 212.0 139.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 207.2 141.8 L 220.8 138.2" stroke="#6b7180" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="131.6" cy="14.2" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="141.5" y1="15.9" x2="148.4" y2="17.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.3 40.9 L 99.1 70.5 L 122.1 51.2" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 122.0 124.0 L 166.5 112.3 L 162.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 116.0 122.0 Q 104.0 80.1 98.3 38.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 98.3 38.9 L 93.1 68.5 L 116.1 49.2" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 116.0 122.0 L 160.7 111.1 L 158.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 119.1 58.2 L 119.1 42.2" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="100.4" cy="19.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="110.4" y1="18.2" x2="117.3" y2="17.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.3 40.9 L 134.1 44.1 L 163.2 51.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 122.0 124.0 L 166.5 112.3 L 162.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 116.0 122.0 Q 104.0 80.1 98.3 38.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 98.3 38.9 L 128.1 42.1 L 157.2 49.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 116.0 122.0 L 160.7 111.1 L 158.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.2 58.3 L 160.2 42.3" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="100.4" cy="19.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="110.4" y1="18.2" x2="117.3" y2="17.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+38
View File
@@ -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.
+33
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.3 40.9 L 134.1 44.1 L 163.2 51.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 122.0 124.0 L 166.5 112.3 L 162.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 116.0 122.0 Q 104.0 80.1 98.3 38.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 98.3 38.9 L 128.1 42.1 L 157.2 49.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 116.0 122.0 L 160.7 111.1 L 158.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.2 58.3 L 160.2 42.3" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="100.4" cy="19.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="110.4" y1="18.2" x2="117.3" y2="17.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 130.0 L 124.0 56.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 56.0 L 198.0 12.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="112" width="40" height="7" rx="3" fill="#c5cad4"/>
<path d="M 198.0 8.0 L 200.0 25.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 153.7 39.1 L 173.5 16.5 L 202.0 26.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 156.0 124.0 L 200.2 111.1 L 197.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 150.0 122.0 Q 146.6 78.6 147.7 37.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 147.7 37.1 L 169.1 16.0 L 198.0 24.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 150.0 122.0 L 194.4 110.0 L 193.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 172.0 25.0 L 228.0 25.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="149.5" cy="17.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="155.2" y1="8.9" x2="159.2" y2="3.2" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 130.0 L 124.0 56.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 56.0 L 198.0 12.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="112" width="40" height="7" rx="3" fill="#c5cad4"/>
<path d="M 198.0 8.0 L 167.0 67.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 153.7 39.1 L 139.1 65.3 L 169.0 68.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 156.0 124.0 L 200.2 111.1 L 197.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 150.0 122.0 Q 146.6 78.6 147.7 37.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 147.7 37.1 L 135.1 64.2 L 165.0 66.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 150.0 122.0 L 194.4 110.0 L 193.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 139.0 67.0 L 195.0 67.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="149.5" cy="17.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="158.2" y1="12.1" x2="164.2" y2="8.6" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+37
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 130.0 L 124.0 56.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 56.0 L 198.0 12.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="170" y="112" width="40" height="7" rx="3" fill="#c5cad4"/>
<path d="M 198.0 8.0 L 167.0 67.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 153.7 39.1 L 139.1 65.3 L 169.0 68.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 156.0 124.0 L 200.2 111.1 L 197.0 151.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 150.0 122.0 Q 146.6 78.6 147.7 37.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 147.7 37.1 L 135.1 64.2 L 165.0 66.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 150.0 122.0 L 194.4 110.0 L 193.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 139.0 67.0 L 195.0 67.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="149.5" cy="17.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="158.2" y1="12.1" x2="164.2" y2="8.6" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 209.2 107.2" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 203.2 105.2" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 205.6 97.2 L 206.8 115.2" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 165.8 149.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 159.8 147.9" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 171.8 149.6 L 153.8 148.1" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+37
View File
@@ -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.
+34
View File
@@ -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]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 165.8 149.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 159.8 147.9" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 171.8 149.6 L 153.8 148.1" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 174.2 149.7" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 168.2 147.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 180.1 147.6 L 162.2 149.8" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 209.2 107.2" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 203.2 105.2" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 205.6 97.2 L 206.8 115.2" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+40
View File
@@ -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]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 97.7 37.2 L 105.5 66.2 L 120.5 92.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 118.0 L 169.3 110.0 L 209.2 107.2" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 116.0 Q 101.7 75.6 91.7 35.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 91.7 35.2 L 99.5 64.2 L 114.5 90.2" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 116.0 L 163.3 108.0 L 203.2 105.2" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 205.6 97.2 L 206.8 115.2" stroke="#6b7180" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="97.2" cy="16.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="106.6" y1="19.4" x2="113.2" y2="21.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 111.4 40.4 L 119.2 69.4 L 147.4 79.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 118.0 L 161.7 74.8 L 189.0 104.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 116.0 Q 119.6 77.5 105.4 38.4" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 105.4 38.4 L 113.2 67.4 L 141.4 77.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 116.0 L 157.1 73.3 L 185.0 102.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 186.3 123.0 L 187.7 83.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="110.6" cy="19.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="120.3" y1="21.7" x2="127.0" y2="23.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 111.4 40.4 L 119.2 69.4 L 147.4 79.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 118.0 L 185.1 93.7 L 224.0 103.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 116.0 Q 119.6 77.5 105.4 38.4" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 105.4 38.4 L 113.2 67.4 L 141.4 77.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 116.0 L 180.6 94.3 L 220.0 101.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 221.3 122.0 L 222.7 82.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="110.6" cy="19.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="120.3" y1="21.7" x2="127.0" y2="23.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+39
View File
@@ -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.
+35
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="arm_l" d="M 111.4 40.4 L 119.2 69.4 L 147.4 79.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 118.0 L 185.1 93.7 L 224.0 103.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 116.0 Q 119.6 77.5 105.4 38.4" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 105.4 38.4 L 113.2 67.4 L 141.4 77.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 116.0 L 180.6 94.3 L 220.0 101.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 221.3 122.0 L 222.7 82.0" stroke="#6b7180" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="110.6" cy="19.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="120.3" y1="21.7" x2="127.0" y2="23.5" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 141.9 42.6 L 147.9 72.0 L 118.0 74.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 124.0 L 164.7 170.0 L 130.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 151.2 79.3 135.9 40.6" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 135.9 40.6 L 141.9 70.0 L 112.0 72.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 122.0 L 154.1 167.6 L 190.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 115.0 87.0 L 115.0 59.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="129.4" cy="21.7" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 190.1 42.6 L 172.8 67.1 L 202.0 74.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 124.0 L 164.7 170.0 L 130.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 168.8 79.3 184.1 40.6" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 184.1 40.6 L 178.1 70.0 L 208.0 72.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 122.0 L 154.1 167.6 L 190.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 205.0 87.0 L 205.0 59.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="190.6" cy="21.7" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+39
View File
@@ -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.
+32
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 141.9 42.6 L 147.9 72.0 L 118.0 74.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 166.0 124.0 L 164.7 170.0 L 130.0 150.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 151.2 79.3 135.9 40.6" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 135.9 40.6 L 141.9 70.0 L 112.0 72.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 160.0 122.0 L 154.1 167.6 L 190.0 150.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 115.0 87.0 L 115.0 59.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="129.4" cy="21.7" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+55 -8
View File
@@ -52,6 +52,48 @@ changing data, never artwork.
natural arcs and bone lengths never distort. The last frame tweens back to
the first (looping). Asymmetric timing carries technique: leg raises lower
slowly (`tween` 1.4 s down, 0.6 s up).
- **Face-on figures** — `gaze` is optional: a frame without it faces the
viewer and draws no nose tick. Used by exercises whose motion is lateral
(abductor/adductor), where a side view would hide the movement.
## The props layer
Machines and free weights are data too: an optional top-level `"props"` array
adds an equipment layer around the figure. `scene` shapes and `cable`s draw
*behind* the figure in a recessive gray; joint-attached items (`bar`,
`dumbbell`, `pad`) draw *over* the limbs in a darker gray and follow the
resolved hand/foot positions every frame — a pinned foot pressing a `pad`
carries the platform with it through the tween for free. The figure stays the
hero: props are schematic silhouettes (a seat, a backrest, one handle), never
scale drawings of the machine.
```json
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
{"kind": "rect", "x": 54, "y": 104, "w": 40, "h": 8, "r": 3},
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "cable", "from": [190, 8], "to": ["hand_r", "hand_l"]},
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 26, "plateR": 0},
{"type": "dumbbell", "at": "hand_r"},
{"type": "pad", "at": ["foot_r", "foot_l"], "angle": 88, "halfLen": 20, "w": 6}
]
```
- **`scene`** — static shapes in canvas coordinates: `line` (polyline, stroke
width `w`), `circle` (`fill: false` for an outline), `rect` (filled, corner
radius `r`). A shape may set `"color": "prop"` to use the darker
attached-item gray (e.g. a fixed handle the hands rest on).
- **`cable`** — a thin line from a fixed anchor `from` to a moving joint `to`;
the machine's pulley line.
- **`bar` / `dumbbell` / `pad`** — a segment centered on the joint(s) in
`at` (a single extremity, or the midpoint of `["hand_r", "hand_l"]`).
`bar` lies at a fixed world `angle` (default 0 = horizontal — in side view a
two-handed bar is drawn horizontal by convention); `dumbbell` and `pad`
default to perpendicular to the lower bone (forearm/shin), or take an
explicit `angle`. `plateR` puts filled discs on both ends (dumbbells default
to 4.5). A prop whose limb is hidden that frame simply isn't drawn.
- The same math is the plan for the app: a small SwiftUI renderer consumes
`body.json` + `motion.json` and tweens angles on the lower half of the
exercise screen (the paged timer flow occupies only the top half).
@@ -61,9 +103,12 @@ changing data, never artwork.
- **Right vs left limb** — the one rule that never bends: the figure's
**right**-side limbs are dark (`#3a3f4b`), its **left**-side limbs are light
(`#a9afba`) and drawn *behind* the body. Working limbs keep the split:
right = teal `#0d9488`, left = light teal `#86cfc5`. Every render embeds a
small `R —` / `L —` legend, so opposite-limb moves (bird dog, dead bug) are
visibly opposite: one dark-teal limb, one light-teal limb.
right = teal `#0d9488`, left = light teal `#86cfc5`. Opposite-limb moves
(bird dog, dead bug) read as visibly opposite: one dark-teal limb, one
light-teal limb. `render.py` reference renders embed a small `R —` / `L —`
legend for the rig author; the in-app renderer deliberately omits it — the
labels are only anatomically true for right-facing figures, and the
contrast alone carries the meaning.
- **Facing / front-of-torso** — the head carries a **nose tick** (`gaze`
angle); the belly is on that side. Prone noses point at the floor, supine
at the ceiling. The head is drawn last, filled opaque, so overhead arms
@@ -83,7 +128,7 @@ python3 render.py --sheet # + contact-sheet.png of every key frame
python3 render.py --demo # + demo-sheet.png: profile / flip / theme variants
python3 render.py --figure=female # render with another body profile
python3 render.py --flip # mirror the figure (faces the other way)
python3 render.py --export # copy body.json + <Name>.motion.json app resources
python3 render.py --export # copy body.json + <Name>.motion.json + <Name>.info.md app resources
```
`render.py` needs only Pillow (for GIFs/sheets; the SVGs have no dependency).
@@ -91,7 +136,9 @@ The library lives at the repo root, outside every target's source folders —
same-named files per entry (`info.md`, `visual.svg`) would collide in Xcode's
flat resource copy, so the library itself never enters the app bundle. Only
the `--export` copies ship: `body.json` plus uniquely-named
`<Name>.motion.json` files in `Workouts/Resources/ExerciseMotions/`, consumed
by the in-app SwiftUI renderer (`Workouts/ExerciseFigure/`). Re-run
`python3 render.py --export` after editing any motion; the library stays the
source of truth.
`<Name>.motion.json` and `<Name>.info.md` files in
`Workouts/Resources/ExerciseMotions/`, consumed by the in-app SwiftUI renderer
(`Workouts/ExerciseFigure/`) and the exercise-library reference screen
(`ExerciseInfo.swift` parses the info pages). Re-run
`python3 render.py --export` after editing any motion or info page; the
library stays the source of truth.
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 202.0 63.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.6 39.6 L 185.6 40.3 L 204.0 64.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 124.0 L 189.5 109.1 L 192.0 149.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 122.0 Q 142.7 78.5 149.6 37.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 149.6 37.6 L 179.5 40.1 L 200.0 62.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 122.0 L 183.9 108.2 L 188.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 202.0 70.0 L 202.0 56.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="153.1" cy="17.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="163.0" y1="17.0" x2="170.0" y2="16.4" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 160.0 73.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.6 39.6 L 134.8 61.2 L 162.0 74.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 124.0 L 189.5 109.1 L 192.0 149.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 122.0 Q 142.7 78.5 149.6 37.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 149.6 37.6 L 130.3 60.5 L 158.0 72.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 122.0 L 183.9 108.2 L 188.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 80.0 L 160.0 66.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="153.1" cy="17.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="163.0" y1="17.0" x2="170.0" y2="16.4" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+37
View File
@@ -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.
+35
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 160.0 73.0" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.6 39.6 L 134.8 61.2 L 162.0 74.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 146.0 124.0 L 189.5 109.1 L 192.0 149.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 140.0 122.0 Q 142.7 78.5 149.6 37.6" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 149.6 37.6 L 130.3 60.5 L 158.0 72.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 140.0 122.0 L 183.9 108.2 L 188.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 80.0 L 160.0 66.0" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="153.1" cy="17.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="163.0" y1="17.0" x2="170.0" y2="16.4" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.1 50.8 L 116.1 76.8 L 116.1 46.8" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 126.0 132.0 L 167.4 112.0 L 168.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 120.0 130.0 Q 103.0 89.8 95.1 48.8" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 95.1 48.8 L 110.1 74.8 L 110.1 44.8" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 120.0 130.0 L 161.9 111.1 L 164.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 103.1 45.8 L 123.1 45.8" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="98.6" cy="29.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="108.5" y1="27.8" x2="115.4" y2="26.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.1 50.8 L 118.8 26.6 L 143.0 8.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 126.0 132.0 L 167.4 112.0 L 168.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 120.0 130.0 Q 103.0 89.8 95.1 48.8" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 95.1 48.8 L 112.8 24.6 L 137.0 6.9" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 120.0 130.0 L 161.9 111.1 L 164.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 7.9 L 150.0 7.9" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="98.6" cy="29.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="108.5" y1="27.8" x2="115.4" y2="26.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+39
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.1 50.8 L 118.8 26.6 L 143.0 8.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 126.0 132.0 L 167.4 112.0 L 168.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 120.0 130.0 Q 103.0 89.8 95.1 48.8" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 95.1 48.8 L 112.8 24.6 L 137.0 6.9" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 120.0 130.0 L 161.9 111.1 L 164.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 7.9 L 150.0 7.9" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="98.6" cy="29.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="108.5" y1="27.8" x2="115.4" y2="26.8" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.7 42.1 L 116.9 71.4 L 142.9 56.4" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 126.0 L 168.1 112.8 L 160.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 124.0 Q 109.7 81.3 104.7 40.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 104.7 40.1 L 110.9 69.4 L 136.9 54.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 124.0 L 162.3 111.5 L 156.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 139.9 61.4 L 139.9 49.4" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="106.1" cy="20.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="116.0" y1="19.1" x2="123.0" y2="18.3" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.7 42.1 L 116.9 71.4 L 118.0 101.4" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 126.0 L 168.1 112.8 L 160.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 124.0 Q 109.7 81.3 104.7 40.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 104.7 40.1 L 110.9 69.4 L 112.0 99.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 124.0 L 162.3 111.5 L 156.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 115.0 106.4 L 115.0 94.4" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="106.1" cy="20.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="116.0" y1="19.1" x2="123.0" y2="18.3" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+39
View File
@@ -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.
+33
View File
@@ -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]}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<line x1="16" y1="156" x2="304" y2="156" stroke="#b9bec9" stroke-width="3" stroke-linecap="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.7 42.1 L 116.9 71.4 L 118.0 101.4" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 124.0 126.0 L 168.1 112.8 L 160.0 152.0" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 118.0 124.0 Q 109.7 81.3 104.7 40.1" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 104.7 40.1 L 110.9 69.4 L 112.0 99.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 118.0 124.0 L 162.3 111.5 L 156.0 151.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 115.0 106.4 L 115.0 94.4" stroke="#6b7180" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="106.1" cy="20.1" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="116.0" y1="19.1" x2="123.0" y2="18.3" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
<line x1="242" y1="16" x2="258" y2="16" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<text x="264" y="20">R</text>
<line x1="282" y1="16" x2="298" y2="16" stroke="#a9afba" stroke-width="4" stroke-linecap="round"/>
<text x="304" y="20">L</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 270 KiB

+173 -18
View File
@@ -32,11 +32,13 @@ PALETTES = {
"right": "#3a3f4b", "left": "#a9afba",
"right_working": "#0d9488", "left_working": "#86cfc5",
"ground": "#b9bec9", "legend_text": "#6b7180", "head_fill": "white",
"equipment": "#c5cad4", "prop": "#6b7180",
},
"indigo": {
"right": "#3a3f4b", "left": "#a9afba",
"right_working": "#4f46e5", "left_working": "#a5b4fc",
"ground": "#b9bec9", "legend_text": "#6b7180", "head_fill": "white",
"equipment": "#c5cad4", "prop": "#6b7180",
},
}
WIDTHS = {"right": 6, "left": 5, "spine": 6, "head": 6, "nose": 4}
@@ -55,6 +57,8 @@ LIMBS = { # limb -> (attach joint, [bone length keys], pin key)
ANGLE_KEYS = ["spine", "neck", "gaze", "arm_r", "arm_l", "leg_r", "leg_l"]
JOINT_LIMB = {"hand_r": "arm_r", "hand_l": "arm_l", "foot_r": "leg_r", "foot_l": "leg_l"}
def bodies():
return json.loads((LIB / "body.json").read_text())
@@ -153,12 +157,14 @@ def geometry(frame, body, hide=(), flip=False):
re-solved so planted hands/feet hold exactly through tweens."""
at = attach_points(frame, body, flip)
head = walk(at["neck"], [frame["neck"]], [body["neck"]])[1]
nose_tip = walk(head, [frame["gaze"]], [body["headR"] + 7])[1]
geo = {"head": head, "nose": nose_tip, "headR": body["headR"],
geo = {"head": head, "headR": body["headR"],
"spine": [at["pelvis"],
(2 * at["mid"][0] - (at["pelvis"][0] + at["neck"][0]) / 2,
2 * at["mid"][1] - (at["pelvis"][1] + at["neck"][1]) / 2),
at["neck"]]}
# `gaze` is optional: a frame without it faces the viewer, so no nose tick.
if "gaze" in frame:
geo["nose"] = walk(head, [frame["gaze"]], [body["headR"] + 7])[1]
for limb, (joint, bone_keys, pin) in LIMBS.items():
if limb not in frame or limb in hide:
continue
@@ -206,6 +212,134 @@ def timeline(norms, fps=20):
return frames
# -------------------------------------------------------------------- props
# Equipment layer (see SYSTEM.md): `scene` shapes and `cable`s draw behind the
# figure in the recessive equipment gray; joint-attached items (`bar`,
# `dumbbell`, `pad`) draw over the limbs in the darker prop gray, following
# the resolved hand/foot positions frame by frame.
def flip_props(props, width):
"""Mirror the props horizontally, matching flip_frame. Joint-attached props
follow the mirrored limbs automatically; only fixed coordinates and world
angles need mirroring."""
def fx(p):
return [width - p[0], p[1]]
out = []
for prop in props:
p = json.loads(json.dumps(prop))
if p["type"] == "scene":
for s in p["shapes"]:
if s["kind"] == "line":
s["pts"] = [fx(pt) for pt in s["pts"]]
elif s["kind"] == "circle":
s["c"] = fx(s["c"])
elif s["kind"] == "rect":
s["x"] = width - s["x"] - s["w"]
elif p["type"] == "cable":
p["from"] = fx(p["from"])
elif p["type"] in ("bar", "pad") and "angle" in p:
p["angle"] = 180 - p["angle"]
out.append(p)
return out
def joint_points(geo, ref):
"""Resolve a joint ref — `"hand_r"` or a `["hand_r", "hand_l"]` midpoint —
to (point, lower-bone unit direction). None when the limb isn't drawn."""
names = ref if isinstance(ref, list) else [ref]
pts, direction = [], None
for name in names:
limb = geo.get(JOINT_LIMB[name])
if not limb:
return None, None
pts.append(limb[-1])
if direction is None:
a, b = limb[-2], limb[-1]
d = math.hypot(b[0] - a[0], b[1] - a[1]) or 1.0
direction = ((b[0] - a[0]) / d, (b[1] - a[1]) / d)
return ((sum(p[0] for p in pts) / len(pts),
sum(p[1] for p in pts) / len(pts)), direction)
def resolve_props(props, geo):
"""Props -> drawable primitives for one frame: (background, foreground)."""
bg, fg = [], []
for p in props or []:
t = p["type"]
if t == "scene":
for s in p["shapes"]:
bg.append(dict(s, color=s.get("color", "equipment")))
elif t == "cable":
end, _ = joint_points(geo, p["to"])
if end:
bg.append({"kind": "line", "pts": [list(p["from"]), list(end)],
"w": p.get("w", 2), "color": "equipment"})
elif t in ("bar", "dumbbell", "pad"):
c, d = joint_points(geo, p["at"])
if not c:
continue
if t == "bar" or "angle" in p:
ux, uy = dirv(p.get("angle", 0)) # fixed world angle
else:
ux, uy = -d[1], d[0] # perpendicular to the lower bone
h = p.get("halfLen", {"bar": 24, "dumbbell": 7, "pad": 8}[t])
a = (c[0] - ux * h, c[1] - uy * h)
b = (c[0] + ux * h, c[1] + uy * h)
fg.append({"kind": "line", "pts": [a, b],
"w": p.get("w", {"bar": 4, "dumbbell": 3, "pad": 7}[t]),
"color": "prop"})
plate = p.get("plateR", 4.5 if t == "dumbbell" else 0)
if plate:
for e in (a, b):
fg.append({"kind": "circle", "c": list(e), "r": plate,
"fill": True, "color": "prop"})
return bg, fg
def svg_prims(prims, colors):
lines = []
for p in prims:
color = colors[p["color"]]
if p["kind"] == "line":
d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in p["pts"])
lines.append(f' <path d="{d}" stroke="{color}" stroke-width="{p.get("w", 4)}"'
f' stroke-linecap="round" stroke-linejoin="round"/>')
elif p["kind"] == "circle":
cx, cy = p["c"]
if p.get("fill"):
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}" fill="{color}"/>')
else:
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}"'
f' stroke="{color}" stroke-width="{p.get("w", 3)}"/>')
elif p["kind"] == "rect":
lines.append(f' <rect x="{p["x"]}" y="{p["y"]}" width="{p["w"]}" height="{p["h"]}"'
f' rx="{p.get("r", 2)}" fill="{colors[p.get("color", "equipment")]}"/>')
return lines
def draw_prims(d, prims, colors, scale):
for p in prims:
color = colors[p["color"]]
if p["kind"] == "line":
pts = [(x * scale, y * scale) for x, y in p["pts"]]
w = p.get("w", 4) * scale
d.line(pts, fill=color, width=w, joint="curve")
for x, y in (pts[0], pts[-1]):
d.ellipse([x - w / 2, y - w / 2, x + w / 2, y + w / 2], fill=color)
elif p["kind"] == "circle":
cx, cy = p["c"][0] * scale, p["c"][1] * scale
r = p["r"] * scale
if p.get("fill"):
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=color)
else:
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color,
width=p.get("w", 3) * scale)
elif p["kind"] == "rect":
x, y = p["x"] * scale, p["y"] * scale
d.rounded_rectangle([x, y, x + p["w"] * scale, y + p["h"] * scale],
radius=p.get("r", 2) * scale, fill=color)
# ------------------------------------------------------------------- drawing
def part_style(part, working, colors):
@@ -233,20 +367,24 @@ def nose_segment(geo):
return (hx + ux * r, hy + uy * r), (hx + ux * (r + 7), hy + uy * (r + 7))
def svg_for_frame(name, geo, working, colors):
def svg_for_frame(name, geo, working, colors, props=None):
bg, fg = resolve_props(props, geo)
w, h = CANVAS
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" fill="none">',
f' <title>{name}</title>',
f' <line x1="16" y1="{GROUND_Y + 4}" x2="{w - 16}" y2="{GROUND_Y + 4}"'
f' stroke="{colors["ground"]}" stroke-width="3" stroke-linecap="round"/>']
parts += svg_prims(bg, colors)
for part in PART_ORDER:
if part == "head":
parts += svg_prims(fg, colors)
hx, hy = geo["head"]
(sx, sy), (ex, ey) = nose_segment(geo)
parts.append(f' <circle id="head" cx="{hx:.1f}" cy="{hy:.1f}" r="{geo["headR"]}"'
f' fill="{colors["head_fill"]}" stroke="{colors["right"]}" stroke-width="{WIDTHS["head"]}"/>')
parts.append(f' <line id="nose" x1="{sx:.1f}" y1="{sy:.1f}" x2="{ex:.1f}" y2="{ey:.1f}"'
f' stroke="{colors["right"]}" stroke-width="{WIDTHS["nose"]}" stroke-linecap="round"/>')
if "nose" in geo:
(sx, sy), (ex, ey) = nose_segment(geo)
parts.append(f' <line id="nose" x1="{sx:.1f}" y1="{sy:.1f}" x2="{ex:.1f}" y2="{ey:.1f}"'
f' stroke="{colors["right"]}" stroke-width="{WIDTHS["nose"]}" stroke-linecap="round"/>')
continue
if part not in geo:
continue
@@ -269,9 +407,10 @@ def svg_for_frame(name, geo, working, colors):
return "\n".join(parts) + "\n"
def draw_geo(geo, working, colors, scale=2, font=None):
def draw_geo(geo, working, colors, scale=2, font=None, props=None):
from PIL import Image, ImageDraw
bg, fg = resolve_props(props, geo)
w, h = CANVAS[0] * scale, CANVAS[1] * scale
img = Image.new("RGB", (w, h), "white")
d = ImageDraw.Draw(img)
@@ -284,13 +423,16 @@ def draw_geo(geo, working, colors, scale=2, font=None):
d.ellipse([x - r, y - r, x + r, y + r], fill=color)
line([(16, GROUND_Y + 4), (CANVAS[0] - 16, GROUND_Y + 4)], colors["ground"], 3)
draw_prims(d, bg, colors, scale)
for part in PART_ORDER:
if part == "head":
draw_prims(d, fg, colors, scale)
hx, hy = geo["head"]
r, sw = geo["headR"] * scale, WIDTHS["head"] * scale
d.ellipse([hx * scale - r, hy * scale - r, hx * scale + r, hy * scale + r],
fill=colors["head_fill"], outline=colors["right"], width=sw)
line(list(nose_segment(geo)), colors["right"], WIDTHS["nose"])
if "nose" in geo:
line(list(nose_segment(geo)), colors["right"], WIDTHS["nose"])
continue
if part not in geo:
continue
@@ -325,8 +467,9 @@ def load_motion(folder):
def export_app_resources(folders):
"""Write the app's bundled copies: body.json plus one `<Name>.motion.json`
per library entry. Unique basenames — Xcode copies resources flat, so the
per-entry motion.json files can't ship under their own folder names."""
and one `<Name>.info.md` per library entry. Unique basenames — Xcode copies
resources flat, so the per-entry motion.json/info.md files can't ship under
their own folder names."""
out = LIB.parent / "Workouts" / "Resources" / "ExerciseMotions"
out.mkdir(parents=True, exist_ok=True)
(out / "body.json").write_text((LIB / "body.json").read_text())
@@ -335,6 +478,10 @@ def export_app_resources(folders):
(out / f"{motion['name']}.motion.json").write_text(
(folder / "motion.json").read_text())
print(f" exported {motion['name']}.motion.json")
info = folder / "info.md"
if info.exists():
(out / f"{motion['name']}.info.md").write_text(info.read_text())
print(f" exported {motion['name']}.info.md")
print(f" exported body.json -> {out}")
@@ -343,6 +490,9 @@ def render_exercise(folder, figure="neutral", flip=False):
body = bodies()[figure]
working = set(motion.get("working", []))
hide = set(motion.get("hide", []))
props = motion.get("props", [])
if flip:
props = flip_props(props, CANVAS[0])
norms = [normalize(kf, body, flip) for kf in motion["frames"]]
geos = [geometry(n, body, hide, flip) for n in norms]
@@ -351,14 +501,14 @@ def render_exercise(folder, figure="neutral", flip=False):
for old in frames_dir.glob("frame-*.svg"):
old.unlink()
colors = PALETTES["default"]
svgs = [svg_for_frame(motion["name"], g, working, colors) for g in geos]
svgs = [svg_for_frame(motion["name"], g, working, colors, props) for g in geos]
for i, svg in enumerate(svgs, start=1):
(frames_dir / f"frame-{i}.svg").write_text(svg)
(folder / "visual.svg").write_text(svgs[motion.get("primary", 1) - 1])
try:
font = legend_font()
imgs = [draw_geo(geometry(n, body, hide, flip), working, colors, font=font)
imgs = [draw_geo(geometry(n, body, hide, flip), working, colors, font=font, props=props)
for n in timeline(norms)]
imgs[0].save(folder / "preview.gif", save_all=True, append_images=imgs[1:],
duration=50, loop=0)
@@ -367,7 +517,7 @@ def render_exercise(folder, figure="neutral", flip=False):
print(f" {motion['name']}: Pillow missing — SVGs written, preview.gif skipped")
def contact_sheet(folders, figure="neutral"):
def contact_sheet(folders, figure="neutral", out=None):
from PIL import Image, ImageDraw
font = legend_font()
@@ -376,11 +526,12 @@ def contact_sheet(folders, figure="neutral"):
for folder in folders:
motion = load_motion(folder)
working, hide = set(motion.get("working", [])), set(motion.get("hide", []))
props = motion.get("props", [])
for i, kf in enumerate(motion["frames"], start=1):
geo = geometry(normalize(kf, body), body, hide)
cells.append((f"{motion['name']} {i}/{len(motion['frames'])}",
draw_geo(geo, working, PALETTES["default"], font=font)))
save_sheet(cells, LIB / "contact-sheet.png")
draw_geo(geo, working, PALETTES["default"], font=font, props=props)))
save_sheet(cells, Path(out) if out else LIB / "contact-sheet.png")
def demo_sheet(folder):
@@ -397,9 +548,10 @@ def demo_sheet(folder):
cells = []
for label, figure, flip, palette in variants:
body = bodies()[figure]
props = flip_props(motion.get("props", []), CANVAS[0]) if flip else motion.get("props", [])
geo = geometry(normalize(kf, body, flip), body, hide, flip)
cells.append((f"{motion['name']}{label}",
draw_geo(geo, working, PALETTES[palette], font=font)))
draw_geo(geo, working, PALETTES[palette], font=font, props=props)))
save_sheet(cells, LIB / "demo-sheet.png", cols=2)
@@ -422,9 +574,12 @@ def main():
flags = [a for a in sys.argv[1:] if a.startswith("--")]
names = [a for a in sys.argv[1:] if not a.startswith("--")]
figure = "neutral"
sheet = None # False = off, None+flag = default path, str = custom path
for f in flags:
if f.startswith("--figure="):
figure = f.split("=", 1)[1]
elif f.startswith("--sheet"):
sheet = f.split("=", 1)[1] if "=" in f else True
folders = ([LIB / n for n in names] if names
else sorted(p.parent for p in LIB.glob("*/motion.json")))
if "--export" in flags:
@@ -432,8 +587,8 @@ def main():
return
for folder in folders:
render_exercise(folder, figure=figure, flip="--flip" in flags)
if "--sheet" in flags:
contact_sheet(folders, figure=figure)
if sheet:
contact_sheet(folders, figure=figure, out=None if sheet is True else sheet)
if "--demo" in flags:
demo_sheet(folders[0] if names else LIB / "Bird Dog")
+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

Some files were not shown because too many files have changed in this diff Show More