diff --git a/Exercise Library/Abdominal/frames/frame-1.svg b/Exercise Library/Abdominal/frames/frame-1.svg
new file mode 100644
index 0000000..0fac729
--- /dev/null
+++ b/Exercise Library/Abdominal/frames/frame-1.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Abdominal/frames/frame-2.svg b/Exercise Library/Abdominal/frames/frame-2.svg
new file mode 100644
index 0000000..e0f75fc
--- /dev/null
+++ b/Exercise Library/Abdominal/frames/frame-2.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Abdominal/info.md b/Exercise Library/Abdominal/info.md
new file mode 100644
index 0000000..b272ba1
--- /dev/null
+++ b/Exercise Library/Abdominal/info.md
@@ -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.
diff --git a/Exercise Library/Abdominal/motion.json b/Exercise Library/Abdominal/motion.json
new file mode 100644
index 0000000..36f15cb
--- /dev/null
+++ b/Exercise Library/Abdominal/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Abdominal/preview.gif b/Exercise Library/Abdominal/preview.gif
new file mode 100644
index 0000000..934972f
Binary files /dev/null and b/Exercise Library/Abdominal/preview.gif differ
diff --git a/Exercise Library/Abdominal/visual.svg b/Exercise Library/Abdominal/visual.svg
new file mode 100644
index 0000000..e0f75fc
--- /dev/null
+++ b/Exercise Library/Abdominal/visual.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Abductor/frames/frame-1.svg b/Exercise Library/Abductor/frames/frame-1.svg
new file mode 100644
index 0000000..5670ff6
--- /dev/null
+++ b/Exercise Library/Abductor/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Abductor/frames/frame-2.svg b/Exercise Library/Abductor/frames/frame-2.svg
new file mode 100644
index 0000000..c611420
--- /dev/null
+++ b/Exercise Library/Abductor/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Abductor/info.md b/Exercise Library/Abductor/info.md
new file mode 100644
index 0000000..acedd3c
--- /dev/null
+++ b/Exercise Library/Abductor/info.md
@@ -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.
diff --git a/Exercise Library/Abductor/motion.json b/Exercise Library/Abductor/motion.json
new file mode 100644
index 0000000..e986689
--- /dev/null
+++ b/Exercise Library/Abductor/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Abductor/preview.gif b/Exercise Library/Abductor/preview.gif
new file mode 100644
index 0000000..88b6a83
Binary files /dev/null and b/Exercise Library/Abductor/preview.gif differ
diff --git a/Exercise Library/Abductor/visual.svg b/Exercise Library/Abductor/visual.svg
new file mode 100644
index 0000000..c611420
--- /dev/null
+++ b/Exercise Library/Abductor/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Adductor/frames/frame-1.svg b/Exercise Library/Adductor/frames/frame-1.svg
new file mode 100644
index 0000000..26f2537
--- /dev/null
+++ b/Exercise Library/Adductor/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Adductor/frames/frame-2.svg b/Exercise Library/Adductor/frames/frame-2.svg
new file mode 100644
index 0000000..1432983
--- /dev/null
+++ b/Exercise Library/Adductor/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Adductor/info.md b/Exercise Library/Adductor/info.md
new file mode 100644
index 0000000..e1e3010
--- /dev/null
+++ b/Exercise Library/Adductor/info.md
@@ -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.
diff --git a/Exercise Library/Adductor/motion.json b/Exercise Library/Adductor/motion.json
new file mode 100644
index 0000000..c012ce9
--- /dev/null
+++ b/Exercise Library/Adductor/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Adductor/preview.gif b/Exercise Library/Adductor/preview.gif
new file mode 100644
index 0000000..763a32d
Binary files /dev/null and b/Exercise Library/Adductor/preview.gif differ
diff --git a/Exercise Library/Adductor/visual.svg b/Exercise Library/Adductor/visual.svg
new file mode 100644
index 0000000..1432983
--- /dev/null
+++ b/Exercise Library/Adductor/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Arm Curl/frames/frame-1.svg b/Exercise Library/Arm Curl/frames/frame-1.svg
new file mode 100644
index 0000000..369d1c0
--- /dev/null
+++ b/Exercise Library/Arm Curl/frames/frame-1.svg
@@ -0,0 +1,21 @@
+
diff --git a/Exercise Library/Arm Curl/frames/frame-2.svg b/Exercise Library/Arm Curl/frames/frame-2.svg
new file mode 100644
index 0000000..093d0c8
--- /dev/null
+++ b/Exercise Library/Arm Curl/frames/frame-2.svg
@@ -0,0 +1,21 @@
+
diff --git a/Exercise Library/Arm Curl/info.md b/Exercise Library/Arm Curl/info.md
new file mode 100644
index 0000000..9989727
--- /dev/null
+++ b/Exercise Library/Arm Curl/info.md
@@ -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.
diff --git a/Exercise Library/Arm Curl/motion.json b/Exercise Library/Arm Curl/motion.json
new file mode 100644
index 0000000..cf63762
--- /dev/null
+++ b/Exercise Library/Arm Curl/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Arm Curl/preview.gif b/Exercise Library/Arm Curl/preview.gif
new file mode 100644
index 0000000..217d0d3
Binary files /dev/null and b/Exercise Library/Arm Curl/preview.gif differ
diff --git a/Exercise Library/Arm Curl/visual.svg b/Exercise Library/Arm Curl/visual.svg
new file mode 100644
index 0000000..093d0c8
--- /dev/null
+++ b/Exercise Library/Arm Curl/visual.svg
@@ -0,0 +1,21 @@
+
diff --git a/Exercise Library/Calfs/frames/frame-1.svg b/Exercise Library/Calfs/frames/frame-1.svg
new file mode 100644
index 0000000..c09ea7c
--- /dev/null
+++ b/Exercise Library/Calfs/frames/frame-1.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Calfs/frames/frame-2.svg b/Exercise Library/Calfs/frames/frame-2.svg
new file mode 100644
index 0000000..c3ffa53
--- /dev/null
+++ b/Exercise Library/Calfs/frames/frame-2.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Calfs/info.md b/Exercise Library/Calfs/info.md
new file mode 100644
index 0000000..98918ef
--- /dev/null
+++ b/Exercise Library/Calfs/info.md
@@ -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.
diff --git a/Exercise Library/Calfs/motion.json b/Exercise Library/Calfs/motion.json
new file mode 100644
index 0000000..2d07937
--- /dev/null
+++ b/Exercise Library/Calfs/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Calfs/preview.gif b/Exercise Library/Calfs/preview.gif
new file mode 100644
index 0000000..237e744
Binary files /dev/null and b/Exercise Library/Calfs/preview.gif differ
diff --git a/Exercise Library/Calfs/visual.svg b/Exercise Library/Calfs/visual.svg
new file mode 100644
index 0000000..c3ffa53
--- /dev/null
+++ b/Exercise Library/Calfs/visual.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Chest Press/frames/frame-1.svg b/Exercise Library/Chest Press/frames/frame-1.svg
new file mode 100644
index 0000000..1e6a755
--- /dev/null
+++ b/Exercise Library/Chest Press/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Chest Press/frames/frame-2.svg b/Exercise Library/Chest Press/frames/frame-2.svg
new file mode 100644
index 0000000..fc88385
--- /dev/null
+++ b/Exercise Library/Chest Press/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Chest Press/info.md b/Exercise Library/Chest Press/info.md
new file mode 100644
index 0000000..585165a
--- /dev/null
+++ b/Exercise Library/Chest Press/info.md
@@ -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.
diff --git a/Exercise Library/Chest Press/motion.json b/Exercise Library/Chest Press/motion.json
new file mode 100644
index 0000000..8b8cf53
--- /dev/null
+++ b/Exercise Library/Chest Press/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Chest Press/preview.gif b/Exercise Library/Chest Press/preview.gif
new file mode 100644
index 0000000..7a5fe2e
Binary files /dev/null and b/Exercise Library/Chest Press/preview.gif differ
diff --git a/Exercise Library/Chest Press/visual.svg b/Exercise Library/Chest Press/visual.svg
new file mode 100644
index 0000000..fc88385
--- /dev/null
+++ b/Exercise Library/Chest Press/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Lat Pull Down/frames/frame-1.svg b/Exercise Library/Lat Pull Down/frames/frame-1.svg
new file mode 100644
index 0000000..d235eb9
--- /dev/null
+++ b/Exercise Library/Lat Pull Down/frames/frame-1.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Lat Pull Down/frames/frame-2.svg b/Exercise Library/Lat Pull Down/frames/frame-2.svg
new file mode 100644
index 0000000..427b7cd
--- /dev/null
+++ b/Exercise Library/Lat Pull Down/frames/frame-2.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Lat Pull Down/info.md b/Exercise Library/Lat Pull Down/info.md
new file mode 100644
index 0000000..8db8d56
--- /dev/null
+++ b/Exercise Library/Lat Pull Down/info.md
@@ -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.
diff --git a/Exercise Library/Lat Pull Down/motion.json b/Exercise Library/Lat Pull Down/motion.json
new file mode 100644
index 0000000..e4d0031
--- /dev/null
+++ b/Exercise Library/Lat Pull Down/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Lat Pull Down/preview.gif b/Exercise Library/Lat Pull Down/preview.gif
new file mode 100644
index 0000000..daab8d5
Binary files /dev/null and b/Exercise Library/Lat Pull Down/preview.gif differ
diff --git a/Exercise Library/Lat Pull Down/visual.svg b/Exercise Library/Lat Pull Down/visual.svg
new file mode 100644
index 0000000..427b7cd
--- /dev/null
+++ b/Exercise Library/Lat Pull Down/visual.svg
@@ -0,0 +1,23 @@
+
diff --git a/Exercise Library/Leg Curl/frames/frame-1.svg b/Exercise Library/Leg Curl/frames/frame-1.svg
new file mode 100644
index 0000000..db6e71d
--- /dev/null
+++ b/Exercise Library/Leg Curl/frames/frame-1.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Leg Curl/frames/frame-2.svg b/Exercise Library/Leg Curl/frames/frame-2.svg
new file mode 100644
index 0000000..d7317a1
--- /dev/null
+++ b/Exercise Library/Leg Curl/frames/frame-2.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Leg Curl/info.md b/Exercise Library/Leg Curl/info.md
new file mode 100644
index 0000000..135a9e1
--- /dev/null
+++ b/Exercise Library/Leg Curl/info.md
@@ -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.
diff --git a/Exercise Library/Leg Curl/motion.json b/Exercise Library/Leg Curl/motion.json
new file mode 100644
index 0000000..0151be6
--- /dev/null
+++ b/Exercise Library/Leg Curl/motion.json
@@ -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]
+ }
+ ]
+}
diff --git a/Exercise Library/Leg Curl/preview.gif b/Exercise Library/Leg Curl/preview.gif
new file mode 100644
index 0000000..ecc0c95
Binary files /dev/null and b/Exercise Library/Leg Curl/preview.gif differ
diff --git a/Exercise Library/Leg Curl/visual.svg b/Exercise Library/Leg Curl/visual.svg
new file mode 100644
index 0000000..d7317a1
--- /dev/null
+++ b/Exercise Library/Leg Curl/visual.svg
@@ -0,0 +1,25 @@
+
diff --git a/Exercise Library/Leg Extension/frames/frame-1.svg b/Exercise Library/Leg Extension/frames/frame-1.svg
new file mode 100644
index 0000000..6482955
--- /dev/null
+++ b/Exercise Library/Leg Extension/frames/frame-1.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Leg Extension/frames/frame-2.svg b/Exercise Library/Leg Extension/frames/frame-2.svg
new file mode 100644
index 0000000..65601f1
--- /dev/null
+++ b/Exercise Library/Leg Extension/frames/frame-2.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Leg Extension/info.md b/Exercise Library/Leg Extension/info.md
new file mode 100644
index 0000000..1490e87
--- /dev/null
+++ b/Exercise Library/Leg Extension/info.md
@@ -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.
diff --git a/Exercise Library/Leg Extension/motion.json b/Exercise Library/Leg Extension/motion.json
new file mode 100644
index 0000000..367717c
--- /dev/null
+++ b/Exercise Library/Leg Extension/motion.json
@@ -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]
+ }
+ ]
+}
diff --git a/Exercise Library/Leg Extension/preview.gif b/Exercise Library/Leg Extension/preview.gif
new file mode 100644
index 0000000..e14bb27
Binary files /dev/null and b/Exercise Library/Leg Extension/preview.gif differ
diff --git a/Exercise Library/Leg Extension/visual.svg b/Exercise Library/Leg Extension/visual.svg
new file mode 100644
index 0000000..65601f1
--- /dev/null
+++ b/Exercise Library/Leg Extension/visual.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Leg Press/frames/frame-1.svg b/Exercise Library/Leg Press/frames/frame-1.svg
new file mode 100644
index 0000000..8763d4b
--- /dev/null
+++ b/Exercise Library/Leg Press/frames/frame-1.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Leg Press/frames/frame-2.svg b/Exercise Library/Leg Press/frames/frame-2.svg
new file mode 100644
index 0000000..ec26558
--- /dev/null
+++ b/Exercise Library/Leg Press/frames/frame-2.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Leg Press/info.md b/Exercise Library/Leg Press/info.md
new file mode 100644
index 0000000..f051bda
--- /dev/null
+++ b/Exercise Library/Leg Press/info.md
@@ -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.
diff --git a/Exercise Library/Leg Press/motion.json b/Exercise Library/Leg Press/motion.json
new file mode 100644
index 0000000..c28c4eb
--- /dev/null
+++ b/Exercise Library/Leg Press/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Leg Press/preview.gif b/Exercise Library/Leg Press/preview.gif
new file mode 100644
index 0000000..819d134
Binary files /dev/null and b/Exercise Library/Leg Press/preview.gif differ
diff --git a/Exercise Library/Leg Press/visual.svg b/Exercise Library/Leg Press/visual.svg
new file mode 100644
index 0000000..ec26558
--- /dev/null
+++ b/Exercise Library/Leg Press/visual.svg
@@ -0,0 +1,24 @@
+
diff --git a/Exercise Library/Rotary/frames/frame-1.svg b/Exercise Library/Rotary/frames/frame-1.svg
new file mode 100644
index 0000000..68bcaa4
--- /dev/null
+++ b/Exercise Library/Rotary/frames/frame-1.svg
@@ -0,0 +1,20 @@
+
diff --git a/Exercise Library/Rotary/frames/frame-2.svg b/Exercise Library/Rotary/frames/frame-2.svg
new file mode 100644
index 0000000..3039099
--- /dev/null
+++ b/Exercise Library/Rotary/frames/frame-2.svg
@@ -0,0 +1,20 @@
+
diff --git a/Exercise Library/Rotary/info.md b/Exercise Library/Rotary/info.md
new file mode 100644
index 0000000..72fe969
--- /dev/null
+++ b/Exercise Library/Rotary/info.md
@@ -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.
diff --git a/Exercise Library/Rotary/motion.json b/Exercise Library/Rotary/motion.json
new file mode 100644
index 0000000..1fbab42
--- /dev/null
+++ b/Exercise Library/Rotary/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Rotary/preview.gif b/Exercise Library/Rotary/preview.gif
new file mode 100644
index 0000000..0482f89
Binary files /dev/null and b/Exercise Library/Rotary/preview.gif differ
diff --git a/Exercise Library/Rotary/visual.svg b/Exercise Library/Rotary/visual.svg
new file mode 100644
index 0000000..68bcaa4
--- /dev/null
+++ b/Exercise Library/Rotary/visual.svg
@@ -0,0 +1,20 @@
+
diff --git a/Exercise Library/SYSTEM.md b/Exercise Library/SYSTEM.md
index 3adc340..e3123ef 100644
--- a/Exercise Library/SYSTEM.md
+++ b/Exercise Library/SYSTEM.md
@@ -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 + .motion.json app resources
+python3 render.py --export # copy body.json + .motion.json + .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
-`.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.
+`.motion.json` and `.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.
diff --git a/Exercise Library/Seated Row/frames/frame-1.svg b/Exercise Library/Seated Row/frames/frame-1.svg
new file mode 100644
index 0000000..c5388f1
--- /dev/null
+++ b/Exercise Library/Seated Row/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Seated Row/frames/frame-2.svg b/Exercise Library/Seated Row/frames/frame-2.svg
new file mode 100644
index 0000000..b017cad
--- /dev/null
+++ b/Exercise Library/Seated Row/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Seated Row/info.md b/Exercise Library/Seated Row/info.md
new file mode 100644
index 0000000..9dfb106
--- /dev/null
+++ b/Exercise Library/Seated Row/info.md
@@ -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.
diff --git a/Exercise Library/Seated Row/motion.json b/Exercise Library/Seated Row/motion.json
new file mode 100644
index 0000000..5e582b0
--- /dev/null
+++ b/Exercise Library/Seated Row/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Seated Row/preview.gif b/Exercise Library/Seated Row/preview.gif
new file mode 100644
index 0000000..7adf014
Binary files /dev/null and b/Exercise Library/Seated Row/preview.gif differ
diff --git a/Exercise Library/Seated Row/visual.svg b/Exercise Library/Seated Row/visual.svg
new file mode 100644
index 0000000..b017cad
--- /dev/null
+++ b/Exercise Library/Seated Row/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Shoulder Press/frames/frame-1.svg b/Exercise Library/Shoulder Press/frames/frame-1.svg
new file mode 100644
index 0000000..7afd240
--- /dev/null
+++ b/Exercise Library/Shoulder Press/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Shoulder Press/frames/frame-2.svg b/Exercise Library/Shoulder Press/frames/frame-2.svg
new file mode 100644
index 0000000..5db76f8
--- /dev/null
+++ b/Exercise Library/Shoulder Press/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Shoulder Press/info.md b/Exercise Library/Shoulder Press/info.md
new file mode 100644
index 0000000..c7ea8bd
--- /dev/null
+++ b/Exercise Library/Shoulder Press/info.md
@@ -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.
diff --git a/Exercise Library/Shoulder Press/motion.json b/Exercise Library/Shoulder Press/motion.json
new file mode 100644
index 0000000..2502f62
--- /dev/null
+++ b/Exercise Library/Shoulder Press/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Shoulder Press/preview.gif b/Exercise Library/Shoulder Press/preview.gif
new file mode 100644
index 0000000..a4dada7
Binary files /dev/null and b/Exercise Library/Shoulder Press/preview.gif differ
diff --git a/Exercise Library/Shoulder Press/visual.svg b/Exercise Library/Shoulder Press/visual.svg
new file mode 100644
index 0000000..5db76f8
--- /dev/null
+++ b/Exercise Library/Shoulder Press/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Tricep Press/frames/frame-1.svg b/Exercise Library/Tricep Press/frames/frame-1.svg
new file mode 100644
index 0000000..9bc4e3b
--- /dev/null
+++ b/Exercise Library/Tricep Press/frames/frame-1.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Tricep Press/frames/frame-2.svg b/Exercise Library/Tricep Press/frames/frame-2.svg
new file mode 100644
index 0000000..a77e7ee
--- /dev/null
+++ b/Exercise Library/Tricep Press/frames/frame-2.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/Tricep Press/info.md b/Exercise Library/Tricep Press/info.md
new file mode 100644
index 0000000..e60a145
--- /dev/null
+++ b/Exercise Library/Tricep Press/info.md
@@ -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.
diff --git a/Exercise Library/Tricep Press/motion.json b/Exercise Library/Tricep Press/motion.json
new file mode 100644
index 0000000..fe227fa
--- /dev/null
+++ b/Exercise Library/Tricep Press/motion.json
@@ -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]}
+ }
+ ]
+}
diff --git a/Exercise Library/Tricep Press/preview.gif b/Exercise Library/Tricep Press/preview.gif
new file mode 100644
index 0000000..512378d
Binary files /dev/null and b/Exercise Library/Tricep Press/preview.gif differ
diff --git a/Exercise Library/Tricep Press/visual.svg b/Exercise Library/Tricep Press/visual.svg
new file mode 100644
index 0000000..a77e7ee
--- /dev/null
+++ b/Exercise Library/Tricep Press/visual.svg
@@ -0,0 +1,22 @@
+
diff --git a/Exercise Library/contact-sheet.png b/Exercise Library/contact-sheet.png
index 362701f..4ccc472 100644
Binary files a/Exercise Library/contact-sheet.png and b/Exercise Library/contact-sheet.png differ
diff --git a/Exercise Library/render.py b/Exercise Library/render.py
index 5ad2f8c..33b1b68 100644
--- a/Exercise Library/render.py
+++ b/Exercise Library/render.py
@@ -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' ')
+ elif p["kind"] == "circle":
+ cx, cy = p["c"]
+ if p.get("fill"):
+ lines.append(f' ')
+ else:
+ lines.append(f' ')
+ elif p["kind"] == "rect":
+ lines.append(f' ')
+ 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'