react-native-nitro-pose-exercises 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +207 -126
  2. package/lib/module/config/boat-pose.js +41 -0
  3. package/lib/module/config/boat-pose.js.map +1 -0
  4. package/lib/module/config/bow-pose.js +34 -0
  5. package/lib/module/config/bow-pose.js.map +1 -0
  6. package/lib/module/config/bridge-pose.js +34 -0
  7. package/lib/module/config/bridge-pose.js.map +1 -0
  8. package/lib/module/config/calf-raise.js +44 -0
  9. package/lib/module/config/calf-raise.js.map +1 -0
  10. package/lib/module/config/camel-pose.js +34 -0
  11. package/lib/module/config/camel-pose.js.map +1 -0
  12. package/lib/module/config/childs-pose.js +39 -0
  13. package/lib/module/config/childs-pose.js.map +1 -0
  14. package/lib/module/config/cobra-wings.js +44 -0
  15. package/lib/module/config/cobra-wings.js.map +1 -0
  16. package/lib/module/config/dead-lift.js +51 -0
  17. package/lib/module/config/dead-lift.js.map +1 -0
  18. package/lib/module/config/extended-side-angle.js +46 -0
  19. package/lib/module/config/extended-side-angle.js.map +1 -0
  20. package/lib/module/config/fish-pose.js +34 -0
  21. package/lib/module/config/fish-pose.js.map +1 -0
  22. package/lib/module/config/front-raise.js +51 -0
  23. package/lib/module/config/front-raise.js.map +1 -0
  24. package/lib/module/config/glute-bridge.js +46 -0
  25. package/lib/module/config/glute-bridge.js.map +1 -0
  26. package/lib/module/config/hip-abduction.js +39 -0
  27. package/lib/module/config/hip-abduction.js.map +1 -0
  28. package/lib/module/config/knee-raise.js +44 -0
  29. package/lib/module/config/knee-raise.js.map +1 -0
  30. package/lib/module/config/lateral-raise.js +51 -0
  31. package/lib/module/config/lateral-raise.js.map +1 -0
  32. package/lib/module/config/leg-raise.js +46 -0
  33. package/lib/module/config/leg-raise.js.map +1 -0
  34. package/lib/module/config/mountain-pose.js +46 -0
  35. package/lib/module/config/mountain-pose.js.map +1 -0
  36. package/lib/module/config/overarm-reach.js +44 -0
  37. package/lib/module/config/overarm-reach.js.map +1 -0
  38. package/lib/module/config/pull-up.js +44 -0
  39. package/lib/module/config/pull-up.js.map +1 -0
  40. package/lib/module/config/reverse-warrior.js +46 -0
  41. package/lib/module/config/reverse-warrior.js.map +1 -0
  42. package/lib/module/config/side-lung.js +51 -0
  43. package/lib/module/config/side-lung.js.map +1 -0
  44. package/lib/module/config/side-plank.js +34 -0
  45. package/lib/module/config/side-plank.js.map +1 -0
  46. package/lib/module/config/sumo-squat.js +51 -0
  47. package/lib/module/config/sumo-squat.js.map +1 -0
  48. package/lib/module/config/triangle-pose.js +46 -0
  49. package/lib/module/config/triangle-pose.js.map +1 -0
  50. package/lib/module/config/v-up.js +39 -0
  51. package/lib/module/config/v-up.js.map +1 -0
  52. package/lib/module/config/warrior-iii.js +58 -0
  53. package/lib/module/config/warrior-iii.js.map +1 -0
  54. package/lib/module/index.js +31 -3
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/typescript/src/config/boat-pose.d.ts +3 -0
  57. package/lib/typescript/src/config/boat-pose.d.ts.map +1 -0
  58. package/lib/typescript/src/config/bow-pose.d.ts +3 -0
  59. package/lib/typescript/src/config/bow-pose.d.ts.map +1 -0
  60. package/lib/typescript/src/config/bridge-pose.d.ts +3 -0
  61. package/lib/typescript/src/config/bridge-pose.d.ts.map +1 -0
  62. package/lib/typescript/src/config/calf-raise.d.ts +3 -0
  63. package/lib/typescript/src/config/calf-raise.d.ts.map +1 -0
  64. package/lib/typescript/src/config/camel-pose.d.ts +3 -0
  65. package/lib/typescript/src/config/camel-pose.d.ts.map +1 -0
  66. package/lib/typescript/src/config/childs-pose.d.ts +3 -0
  67. package/lib/typescript/src/config/childs-pose.d.ts.map +1 -0
  68. package/lib/typescript/src/config/cobra-wings.d.ts +3 -0
  69. package/lib/typescript/src/config/cobra-wings.d.ts.map +1 -0
  70. package/lib/typescript/src/config/dead-lift.d.ts +3 -0
  71. package/lib/typescript/src/config/dead-lift.d.ts.map +1 -0
  72. package/lib/typescript/src/config/extended-side-angle.d.ts +3 -0
  73. package/lib/typescript/src/config/extended-side-angle.d.ts.map +1 -0
  74. package/lib/typescript/src/config/fish-pose.d.ts +3 -0
  75. package/lib/typescript/src/config/fish-pose.d.ts.map +1 -0
  76. package/lib/typescript/src/config/front-raise.d.ts +3 -0
  77. package/lib/typescript/src/config/front-raise.d.ts.map +1 -0
  78. package/lib/typescript/src/config/glute-bridge.d.ts +3 -0
  79. package/lib/typescript/src/config/glute-bridge.d.ts.map +1 -0
  80. package/lib/typescript/src/config/hip-abduction.d.ts +3 -0
  81. package/lib/typescript/src/config/hip-abduction.d.ts.map +1 -0
  82. package/lib/typescript/src/config/knee-raise.d.ts +3 -0
  83. package/lib/typescript/src/config/knee-raise.d.ts.map +1 -0
  84. package/lib/typescript/src/config/lateral-raise.d.ts +3 -0
  85. package/lib/typescript/src/config/lateral-raise.d.ts.map +1 -0
  86. package/lib/typescript/src/config/leg-raise.d.ts +3 -0
  87. package/lib/typescript/src/config/leg-raise.d.ts.map +1 -0
  88. package/lib/typescript/src/config/mountain-pose.d.ts +3 -0
  89. package/lib/typescript/src/config/mountain-pose.d.ts.map +1 -0
  90. package/lib/typescript/src/config/overarm-reach.d.ts +3 -0
  91. package/lib/typescript/src/config/overarm-reach.d.ts.map +1 -0
  92. package/lib/typescript/src/config/pull-up.d.ts +3 -0
  93. package/lib/typescript/src/config/pull-up.d.ts.map +1 -0
  94. package/lib/typescript/src/config/reverse-warrior.d.ts +3 -0
  95. package/lib/typescript/src/config/reverse-warrior.d.ts.map +1 -0
  96. package/lib/typescript/src/config/side-lung.d.ts +3 -0
  97. package/lib/typescript/src/config/side-lung.d.ts.map +1 -0
  98. package/lib/typescript/src/config/side-plank.d.ts +3 -0
  99. package/lib/typescript/src/config/side-plank.d.ts.map +1 -0
  100. package/lib/typescript/src/config/sumo-squat.d.ts +3 -0
  101. package/lib/typescript/src/config/sumo-squat.d.ts.map +1 -0
  102. package/lib/typescript/src/config/triangle-pose.d.ts +3 -0
  103. package/lib/typescript/src/config/triangle-pose.d.ts.map +1 -0
  104. package/lib/typescript/src/config/v-up.d.ts +3 -0
  105. package/lib/typescript/src/config/v-up.d.ts.map +1 -0
  106. package/lib/typescript/src/config/warrior-iii.d.ts +3 -0
  107. package/lib/typescript/src/config/warrior-iii.d.ts.map +1 -0
  108. package/lib/typescript/src/index.d.ts +26 -0
  109. package/lib/typescript/src/index.d.ts.map +1 -1
  110. package/package.json +1 -1
  111. package/src/config/boat-pose.ts +33 -0
  112. package/src/config/bow-pose.ts +24 -0
  113. package/src/config/bridge-pose.ts +25 -0
  114. package/src/config/calf-raise.ts +27 -0
  115. package/src/config/camel-pose.ts +24 -0
  116. package/src/config/childs-pose.ts +23 -0
  117. package/src/config/cobra-wings.ts +26 -0
  118. package/src/config/dead-lift.ts +35 -0
  119. package/src/config/extended-side-angle.ts +33 -0
  120. package/src/config/fish-pose.ts +25 -0
  121. package/src/config/front-raise.ts +54 -0
  122. package/src/config/glute-bridge.ts +33 -0
  123. package/src/config/hip-abduction.ts +25 -0
  124. package/src/config/knee-raise.ts +26 -0
  125. package/src/config/lateral-raise.ts +55 -0
  126. package/src/config/leg-raise.ts +33 -0
  127. package/src/config/mountain-pose.ts +33 -0
  128. package/src/config/overarm-reach.ts +46 -0
  129. package/src/config/pull-up.ts +27 -0
  130. package/src/config/reverse-warrior.ts +33 -0
  131. package/src/config/side-lung.ts +34 -0
  132. package/src/config/side-plank.ts +25 -0
  133. package/src/config/sumo-squat.ts +34 -0
  134. package/src/config/triangle-pose.ts +34 -0
  135. package/src/config/v-up.ts +25 -0
  136. package/src/config/warrior-iii.ts +42 -0
  137. package/src/index.tsx +31 -3
package/README.md CHANGED
@@ -8,12 +8,14 @@
8
8
 
9
9
  A **React Native Nitro Module** for real-time, on-device exercise tracking using pose estimation. Uses **OS-native pose detection** — Apple Vision on iOS and Google ML Kit on Android — with **VisionCamera v5**.
10
10
 
11
- * 🏋️ **Rep Counting** — Automatic rep detection with configurable state machines
11
+ * 🏋️ **38 Built-In Exercises** — Push-ups, squats, deadlifts, yoga poses, and more
12
+ * 🔄 **Rep Counting** — Automatic rep detection with configurable state machines
12
13
  * 🧘 **Hold Tracking** — Duration and stability tracking for planks, yoga poses, and isometric holds
13
- * 📐 **Form Validation** — Real-time form feedback with configurable angle-based rules
14
- * 💀 **Skeleton Overlay** — Optional Skia-powered skeleton rendering over the camera feed
14
+ * 📐 **Form Validation** — Real-time form feedback with angle-based rules
15
+ * 💀 **Skeleton Overlay** — Skia-powered skeleton with glow effects and live angle badges
15
16
  * ⚡ **Fully Native** — OS-level pose detection via Nitro Modules, zero JS bridge overhead
16
17
  * 📦 **Zero Model Bundling** — No ML model files to download or ship with your app
18
+ * 🪶 **~200 KB** — Virtually zero app size impact
17
19
 
18
20
  ---
19
21
 
@@ -56,15 +58,15 @@ cd ios && pod install
56
58
 
57
59
  <table>
58
60
  <tr>
59
- <th align="center">🍏 iOS Normal Mode</th>
60
- <th align="center">🍏 iOS Skia Mode</th>
61
+ <th align="center">📸 Normal Mode</th>
62
+ <th align="center">💀 Skeleton + Angle Overlay</th>
61
63
  </tr>
62
64
  <tr>
63
65
  <td align="center">
64
- <img alt="android" src="./docs/img/normal-iOS.png" height="650" width="300"/>
66
+ <img alt="normal-mode" src="./docs/img/normal.png" height="650" width="300"/>
65
67
  </td>
66
68
  <td align="center">
67
- <img alt="android" src="./docs/img/skia-iOS.png" height="650" width="300"/>
69
+ <img alt="skeleton-mode" src="./docs/img/skeleton.png" height="650" width="300"/>
68
70
  </td>
69
71
  </tr>
70
72
  </table>
@@ -75,12 +77,12 @@ cd ios && pod install
75
77
 
76
78
  | Feature | Description |
77
79
  | --- | --- |
78
- | **Rep-Based Exercises** | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls. |
79
- | **Hold-Based Exercises** | Single target pose with duration tracking. Planks, wall sits, yoga poses. |
80
- | **Flow-Based Exercises** | Ordered sequence of poses. Sun salutation, yoga flows. *(coming soon)* |
81
- | **Form Feedback** | Angle-based rules with throttled real-time callbacks. |
82
- | **Skeleton Overlay** | Body skeleton drawn over camera via Skia (19 joints iOS, 33 joints Android). |
80
+ | **Rep-Based Exercises** | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls, and more. |
81
+ | **Hold-Based Exercises** | Single target pose with duration + stability tracking. Planks, wall sits, yoga poses. |
82
+ | **Form Feedback** | Angle-based rules with throttled real-time callbacks. Bad form blocks rep counting. |
83
+ | **Skeleton Overlay** | Glow-effect bones, color-coded joints, and live angle badges drawn over camera via Skia. |
83
84
  | **Bilateral Tracking** | Left and right side angles tracked independently. |
85
+ | **Fatigue Guard** | Minimum 800ms per rep prevents false counts. Form score gate rejects bad reps. |
84
86
 
85
87
  ---
86
88
 
@@ -90,8 +92,8 @@ cd ios && pod install
90
92
 
91
93
  Unlike MediaPipe-based solutions, this library uses OS-native APIs. There is **no model file to download or bundle**.
92
94
 
93
- * **iOS:** Apple Vision is a system framework — it's already on every iPhone running iOS 14+.
94
- * **Android:** ML Kit manages its own model via Google Play Services — it downloads and updates automatically.
95
+ * **iOS:** Apple Vision is a system framework — already on every iPhone running iOS 14+.
96
+ * **Android:** ML Kit manages its own model via Google Play Services — downloads and updates automatically.
95
97
 
96
98
  ### Permissions
97
99
 
@@ -124,18 +126,14 @@ module.exports = {
124
126
 
125
127
  ### Podspec (for library authors)
126
128
 
127
- The iOS podspec needs the Vision and AVFoundation system frameworks:
128
-
129
129
  ```ruby
130
- s.frameworks = ["AVFoundation", "Vision"]
130
+ s.frameworks = ['Vision', 'AVFoundation']
131
131
  ```
132
132
 
133
133
  No CocoaPods dependencies required — Vision is built into iOS.
134
134
 
135
135
  ### Android Gradle (for library authors)
136
136
 
137
- Add ML Kit Pose Detection to `android/build.gradle`:
138
-
139
137
  ```groovy
140
138
  dependencies {
141
139
  implementation 'com.google.mlkit:pose-detection:18.0.0-beta5'
@@ -149,8 +147,8 @@ dependencies {
149
147
  ### Basic — Normal Camera (No Skeleton)
150
148
 
151
149
  ```tsx
152
- import { useEffect, useCallback, useState } from 'react';
153
- import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
150
+ import { useEffect, useState } from 'react';
151
+ import { StyleSheet, View, Text } from 'react-native';
154
152
  import {
155
153
  Camera,
156
154
  useCameraDevice,
@@ -162,7 +160,6 @@ import {
162
160
  nitroPoseExercises,
163
161
  PUSHUP_CONFIG,
164
162
  type RepData,
165
- type FormFeedback,
166
163
  type SessionResult,
167
164
  } from 'react-native-nitro-pose-exercises';
168
165
 
@@ -176,7 +173,6 @@ export default function App() {
176
173
  if (!hasPermission) requestPermission();
177
174
  }, [hasPermission]);
178
175
 
179
- // Initialize pose engine — modelPath is ignored (OS-native, no model file)
180
176
  useEffect(() => {
181
177
  async function init() {
182
178
  await nitroPoseExercises.initialize('');
@@ -184,30 +180,19 @@ export default function App() {
184
180
 
185
181
  nitroPoseExercises.onRepComplete = (data: RepData) => {
186
182
  setRepCount(data.repNumber);
187
- console.log(`Rep ${data.repNumber} — form: ${data.formScore}`);
188
- };
189
-
190
- nitroPoseExercises.onFormFeedback = (feedback: FormFeedback) => {
191
- console.log(`Form: ${feedback.message}`);
192
183
  };
193
184
 
194
185
  nitroPoseExercises.onSessionComplete = (result: SessionResult) => {
195
- console.log(
196
- `Done! ${result.totalReps} reps, avg form: ${result.averageFormScore}`
197
- );
186
+ console.log(`Done! ${result.totalReps} reps, form: ${result.averageFormScore}`);
198
187
  };
199
188
 
200
- // Start: 10 target reps, 3 second countdown
201
189
  nitroPoseExercises.startSession(10, 3);
202
190
  }
203
191
 
204
192
  init();
205
- return () => {
206
- nitroPoseExercises.release();
207
- };
193
+ return () => { nitroPoseExercises.release(); };
208
194
  }, []);
209
195
 
210
- // Frame processor
211
196
  const frameOutput = useFrameOutput({
212
197
  pixelFormat: 'rgb',
213
198
  onFrame(frame) {
@@ -251,20 +236,55 @@ const styles = StyleSheet.create({
251
236
  });
252
237
  ```
253
238
 
254
- ### Skeleton Overlay — SkiaCamera
239
+ ### Skeleton Overlay with Angle Badges — SkiaCamera
240
+
241
+ > **Critical:** Create all `Skia.Paint()` and `Skia.Font()` objects **outside** the `onFrame` callback. Creating them inside causes memory leaks and crashes within seconds.
255
242
 
256
243
  ```tsx
257
- import { SkiaCamera } from 'react-native-vision-camera-skia'
258
- import { Skia } from '@shopify/react-native-skia'
259
- import { nitroPoseExercises } from 'react-native-nitro-pose-exercises'
260
-
261
- const SKELETON_CONNECTIONS: [number, number][] = [
262
- [11, 12], [11, 23], [12, 24], [23, 24], // Torso
263
- [11, 13], [13, 15], // Left arm
264
- [12, 14], [14, 16], // Right arm
265
- [23, 25], [25, 27], // Left leg
266
- [24, 26], [26, 28], // Right leg
267
- ]
244
+ import { Skia } from '@shopify/react-native-skia';
245
+ import { SkiaCamera } from 'react-native-vision-camera-skia';
246
+ import { nitroPoseExercises } from 'react-native-nitro-pose-exercises';
247
+
248
+ // Create paints ONCE at module level — NEVER inside onFrame
249
+ const GLOW_PAINT = Skia.Paint();
250
+ GLOW_PAINT.setColor(Skia.Color('rgba(0, 255, 102, 0.3)'));
251
+ GLOW_PAINT.setStrokeWidth(14);
252
+ GLOW_PAINT.setStyle(1);
253
+ GLOW_PAINT.setStrokeCap(1);
254
+ GLOW_PAINT.setAntiAlias(true);
255
+
256
+ const BONE_PAINT = Skia.Paint();
257
+ BONE_PAINT.setColor(Skia.Color('#00FF66'));
258
+ BONE_PAINT.setStrokeWidth(6);
259
+ BONE_PAINT.setStyle(1);
260
+ BONE_PAINT.setStrokeCap(1);
261
+ BONE_PAINT.setAntiAlias(true);
262
+
263
+ const JOINT_PAINT = Skia.Paint();
264
+ JOINT_PAINT.setColor(Skia.Color('#FF3366'));
265
+ JOINT_PAINT.setStyle(0);
266
+ JOINT_PAINT.setAntiAlias(true);
267
+
268
+ const KEY_JOINT_PAINT = Skia.Paint();
269
+ KEY_JOINT_PAINT.setColor(Skia.Color('#00FFFF'));
270
+ KEY_JOINT_PAINT.setStyle(0);
271
+ KEY_JOINT_PAINT.setAntiAlias(true);
272
+
273
+ const ANGLE_BG_PAINT = Skia.Paint();
274
+ ANGLE_BG_PAINT.setColor(Skia.Color('rgba(0, 0, 0, 0.7)'));
275
+ ANGLE_BG_PAINT.setStyle(0);
276
+
277
+ const ANGLE_TEXT_FONT = Skia.Font(null, 14);
278
+ const ANGLE_TEXT_PAINT = Skia.Paint();
279
+ ANGLE_TEXT_PAINT.setColor(Skia.Color('#FFFFFF'));
280
+
281
+ const SKELETON_CONNECTIONS = [
282
+ [11, 12], [11, 23], [12, 24], [23, 24],
283
+ [11, 13], [13, 15], [12, 14], [14, 16],
284
+ [23, 25], [25, 27], [24, 26], [26, 28],
285
+ ];
286
+
287
+ const KEY_LANDMARKS = [11, 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28];
268
288
 
269
289
  <SkiaCamera
270
290
  style={StyleSheet.absoluteFill}
@@ -272,49 +292,71 @@ const SKELETON_CONNECTIONS: [number, number][] = [
272
292
  device="back"
273
293
  pixelFormat="rgb"
274
294
  onFrame={(frame, render) => {
275
- 'worklet'
295
+ 'worklet';
276
296
  try {
277
- nitroPoseExercises.processFrame(frame)
278
- const landmarks = nitroPoseExercises.landmarks
297
+ nitroPoseExercises.processFrame(frame);
298
+ const landmarks = nitroPoseExercises.landmarks;
279
299
 
280
300
  render(({ frameTexture, canvas }) => {
281
- canvas.drawImage(frameTexture, 0, 0)
301
+ canvas.drawImage(frameTexture, 0, 0);
282
302
 
283
303
  if (landmarks && landmarks.length > 0) {
284
- const w = frame.width
285
- const h = frame.height
286
-
287
- // Draw bones
288
- const linePaint = Skia.Paint()
289
- linePaint.setColor(Skia.Color('#00FF00'))
290
- linePaint.setStrokeWidth(4)
291
- linePaint.setStyle(1)
304
+ const w = frame.width;
305
+ const h = frame.height;
292
306
 
307
+ // Glow layer (thick translucent)
293
308
  for (const [i, j] of SKELETON_CONNECTIONS) {
294
309
  if (i < landmarks.length && j < landmarks.length) {
295
- const a = landmarks[i]
296
- const b = landmarks[j]
310
+ const a = landmarks[i];
311
+ const b = landmarks[j];
297
312
  if (a.visibility > 0.5 && b.visibility > 0.5) {
298
- canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, linePaint)
313
+ canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, GLOW_PAINT);
299
314
  }
300
315
  }
301
316
  }
302
317
 
303
- // Draw joints
304
- const jointPaint = Skia.Paint()
305
- jointPaint.setColor(Skia.Color('#00FFFF'))
306
- jointPaint.setStyle(0)
318
+ // Solid bones on top
319
+ for (const [i, j] of SKELETON_CONNECTIONS) {
320
+ if (i < landmarks.length && j < landmarks.length) {
321
+ const a = landmarks[i];
322
+ const b = landmarks[j];
323
+ if (a.visibility > 0.5 && b.visibility > 0.5) {
324
+ canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, BONE_PAINT);
325
+ }
326
+ }
327
+ }
307
328
 
329
+ // Joints with glow rings
308
330
  for (let idx = 0; idx < landmarks.length; idx++) {
309
- const lm = landmarks[idx]
310
- if (lm.visibility > 0.5) {
311
- canvas.drawCircle(lm.x * w, lm.y * h, 6, jointPaint)
331
+ const lm = landmarks[idx];
332
+ if (lm && lm.visibility > 0.5) {
333
+ const isKey = KEY_LANDMARKS.includes(idx);
334
+ if (isKey) canvas.drawCircle(lm.x * w, lm.y * h, 12, GLOW_PAINT);
335
+ canvas.drawCircle(lm.x * w, lm.y * h, isKey ? 8 : 4, isKey ? KEY_JOINT_PAINT : JOINT_PAINT);
336
+ }
337
+ }
338
+
339
+ // Elbow angle badges
340
+ for (const [shoulderIdx, elbowIdx, wristIdx] of [[11, 13, 15], [12, 14, 16]]) {
341
+ const s = landmarks[shoulderIdx];
342
+ const e = landmarks[elbowIdx];
343
+ const wr = landmarks[wristIdx];
344
+ if (s?.visibility > 0.5 && e?.visibility > 0.5 && wr?.visibility > 0.5) {
345
+ const vaX = s.x - e.x, vaY = s.y - e.y;
346
+ const vcX = wr.x - e.x, vcY = wr.y - e.y;
347
+ const dot = vaX * vcX + vaY * vcY;
348
+ const mag = Math.sqrt(vaX * vaX + vaY * vaY) * Math.sqrt(vcX * vcX + vcY * vcY);
349
+ const angle = Math.round(Math.acos(Math.max(-1, Math.min(1, dot / mag))) * (180 / Math.PI));
350
+ const tx = e.x * w + 15;
351
+ const ty = e.y * h - 10;
352
+ canvas.drawRoundRect({ x: tx - 4, y: ty - 14, width: 48, height: 20 }, 6, 6, ANGLE_BG_PAINT);
353
+ canvas.drawText(`${angle}°`, tx, ty, ANGLE_TEXT_PAINT, ANGLE_TEXT_FONT);
312
354
  }
313
355
  }
314
356
  }
315
- })
357
+ });
316
358
  } finally {
317
- frame.dispose()
359
+ frame.dispose();
318
360
  }
319
361
  }}
320
362
  />
@@ -337,7 +379,6 @@ release(): void
337
379
  ### Exercise Setup
338
380
 
339
381
  ```ts
340
- // Load an exercise config (built-in or custom)
341
382
  loadExercise(config: ExerciseConfig): void
342
383
  ```
343
384
 
@@ -353,7 +394,7 @@ stopSession(): void
353
394
  ### Frame Processing
354
395
 
355
396
  ```ts
356
- // Pass VisionCamera frame for pose detection — call from frame processor
397
+ // Call from VisionCamera frame processor worklet
357
398
  processFrame(frame: Frame): void
358
399
  ```
359
400
 
@@ -363,7 +404,7 @@ processFrame(frame: Frame): void
363
404
  readonly status: SessionStatus // 'idle' | 'countdown' | 'active' | 'paused' | 'completed'
364
405
  readonly currentPhase: ExercisePhase // 'up' | 'down' | 'hold' | 'transition' | 'unknown'
365
406
  readonly repCount: number
366
- readonly landmarks: Landmark[] // Body landmarks (mapped to MediaPipe indices)
407
+ readonly landmarks: Landmark[] // Body landmarks mapped to MediaPipe indices
367
408
  ```
368
409
 
369
410
  ### Callbacks
@@ -378,18 +419,16 @@ onPoseRegained: (() => void) | undefined
378
419
  onSessionComplete: ((result: SessionResult) => void) | undefined
379
420
  ```
380
421
 
381
- ---
382
-
383
422
  ### Callback Payloads
384
423
 
385
424
  #### RepData
386
425
 
387
426
  ```ts
388
427
  {
389
- repNumber: number // Current rep count
390
- durationMs: number // Time taken for this rep
391
- formScore: number // 0-100 form quality score
392
- angles: AngleSnapshot[] // Joint angles at rep completion
428
+ repNumber: number
429
+ durationMs: number
430
+ formScore: number // 0-100
431
+ angles: AngleSnapshot[] // all tracked angles at rep completion
393
432
  }
394
433
  ```
395
434
 
@@ -397,9 +436,9 @@ onSessionComplete: ((result: SessionResult) => void) | undefined
397
436
 
398
437
  ```ts
399
438
  {
400
- ruleName: string // e.g. 'hipSag'
401
- message: string // e.g. 'Keep your hips up'
402
- severity: FormSeverity // 'info' | 'warning' | 'error'
439
+ ruleName: string
440
+ message: string
441
+ severity: FormSeverity // 'info' | 'warning' | 'error'
403
442
  }
404
443
  ```
405
444
 
@@ -418,37 +457,68 @@ onSessionComplete: ((result: SessionResult) => void) | undefined
418
457
 
419
458
  ---
420
459
 
421
- ## 🏋️ Built-In Exercise Configs
460
+ ## 🏋️ All 38 Built-In Exercise Configs
461
+
462
+ ### Rep-Based: Strength (15 exercises)
463
+
464
+ | Config | Exercise | Primary Angle | Camera View |
465
+ | --- | --- | --- | --- |
466
+ | `PUSHUP_CONFIG` | Push-Up | Elbow 140°–180° / 30°–110° | Side |
467
+ | `PULL_UP_CONFIG` | Pull-Up | Elbow 150°–180° / 40°–90° | Side |
468
+ | `SQUAT_CONFIG` | Squat | Knee 155°–180° / 50°–105° | Side |
469
+ | `SUMO_SQUAT_CONFIG` | Sumo Squat | Knee 155°–180° / 60°–110° | Front |
470
+ | `BICEP_CURL_CONFIG` | Bicep Curl | Elbow 150°–180° / 25°–70° | Side |
471
+ | `SHOULDER_PRESS_CONFIG` | Shoulder Press | Elbow 155°–180° / 60°–100° | Side |
472
+ | `LUNGE_CONFIG` | Lunge | Front knee 155°–180° / 70°–110° | Side |
473
+ | `SIDE_LUNGE_CONFIG` | Side Lunge | Bent knee 155°–180° / 70°–110° | Front |
474
+ | `TRICEP_DIP_CONFIG` | Tricep Dip | Elbow 150°–180° / 60°–100° | Side |
475
+ | `DEADLIFT_CONFIG` | Deadlift | Hip 160°–180° / 60°–120° | Side |
476
+ | `LATERAL_RAISE_CONFIG` | Lateral Raise | Shoulder abduction 5°–30° / 75°–110° | Front |
477
+ | `FRONT_RAISE_CONFIG` | Front Raise | Shoulder flexion 0°–25° / 75°–110° | Side |
478
+ | `CALF_RAISE_CONFIG` | Calf Raise | Ankle 70°–95° / 110°–150° | Side |
479
+ | `OVERARM_REACH_CONFIG` | Overarm Reach | Shoulder abduction 0°–30° / 155°–180° | Front |
480
+ | `HIP_ABDUCTION_CONFIG` | Hip Abduction | Leg spread 0°–15° / 30°–60° | Front |
481
+
482
+ ### Rep-Based: Core (6 exercises)
483
+
484
+ | Config | Exercise | Primary Angle | Camera View |
485
+ | --- | --- | --- | --- |
486
+ | `SITUP_CONFIG` | Sit-Up | Hip 130°–180° / 40°–90° | Side |
487
+ | `LEG_RAISE_CONFIG` | Leg Raise | Hip 150°–180° / 60°–110° | Side |
488
+ | `V_UP_CONFIG` | V-Up | Hip fold 150°–180° / 30°–80° | Side |
489
+ | `GLUTE_BRIDGE_CONFIG` | Glute Bridge | Hip extension 80°–120° / 155°–180° | Side |
490
+ | `COBRA_WINGS_CONFIG` | Cobra Wings | Hip extension 160°–180° / 120°–155° | Side |
491
+ | `KNEE_RAISE_CONFIG` | Knee Raise | Hip 155°–180° / 60°–110° | Side |
492
+
493
+ ### Hold-Based: Strength (3 exercises)
494
+
495
+ | Config | Exercise | Hold Angle | Default Duration |
496
+ | --- | --- | --- | --- |
497
+ | `PLANK_CONFIG` | Plank | Hip 155°–180° | 60s |
498
+ | `SIDE_PLANK_CONFIG` | Side Plank | Hip lateral 155°–180° | 30s |
499
+ | `WALL_SIT_CONFIG` | Wall Sit | Knee 80°–110° | 45s |
422
500
 
423
- ### Rep-Based
501
+ ### Hold-Based: Yoga (14 exercises)
424
502
 
425
- | Config | Exercise | Primary Angle | Form Rules |
503
+ | Config | Exercise | Hold Angle | Default Duration |
426
504
  | --- | --- | --- | --- |
427
- | `PUSHUP_CONFIG` | Push-Up | Elbow (140°–180° up, 30°–110° down) | Hip sag, hip pike |
428
- | `SQUAT_CONFIG` | Squat | Knee (155°–180° up, 50°–105° down) | Knees caving, leaning forward |
429
- | `BICEP_CURL_CONFIG` | Bicep Curl | Elbow (150°–180° down, 25°–70° up) | Elbow flare, swinging |
430
- | `SHOULDER_PRESS_CONFIG` | Shoulder Press | Elbow (155°–180° up, 60°–100° down) | Back arch |
431
- | `LUNGE_CONFIG` | Lunge | Front knee (155°–180° up, 70°–110° down) | Knee over toe, torso lean |
432
- | `SITUP_CONFIG` | Sit-Up | Hip (130°–180° down, 40°–90° up) | Neck strain |
433
- | `TRICEP_DIP_CONFIG` | Tricep Dip | Elbow (150°–180° up, 60°–100° down) | Going too deep |
434
-
435
- ### Hold-Based
436
-
437
- | Config | Exercise | Hold Angle | Duration | Form Rules |
438
- | --- | --- | --- | --- | --- |
439
- | `PLANK_CONFIG` | Plank | Hip 155°–180° | 60s | Hip sag, hip pike |
440
- | `WALL_SIT_CONFIG` | Wall Sit | Knee 80°–110° | 45s | Too high, leaning forward |
441
-
442
- ### Yoga Poses
443
-
444
- | Config | Exercise | Hold Angle | Duration | Form Rules |
445
- | --- | --- | --- | --- | --- |
446
- | `TREE_POSE_CONFIG` | Tree Pose (Vrksasana) | Standing leg 165°–180° | 30s | Standing leg bent, leaning torso |
447
- | `WARRIOR_I_CONFIG` | Warrior I (Virabhadrasana I) | Front knee 80°–110° | 30s | Knee too straight, back leg bent, arms not extended, torso leaning |
448
- | `WARRIOR_II_CONFIG` | Warrior II (Virabhadrasana II) | Front knee 80°–110° | 30s | Knee too straight, back leg bent, arms drooping |
449
- | `DOWNWARD_DOG_CONFIG` | Downward Dog (Adho Mukha Svanasana) | Hip 55°–100° | 30s | Arms bent, legs bent, hips too low |
450
- | `CHAIR_POSE_CONFIG` | Chair Pose (Utkatasana) | Knee 90°–130° | 30s | Knees too straight, leaning forward, arms not up |
451
- | `COBRA_POSE_CONFIG` | Cobra Pose (Bhujangasana) | Hip extension 120°–170° | 30s | Shoulders tensed, legs bending |
505
+ | `MOUNTAIN_POSE_CONFIG` | Mountain Pose (Tadasana) | Knee 170°–180° | 30s |
506
+ | `TREE_POSE_CONFIG` | Tree Pose (Vrksasana) | Standing leg 165°–180° | 30s |
507
+ | `CHAIR_POSE_CONFIG` | Chair Pose (Utkatasana) | Knee 90°–130° | 30s |
508
+ | `WARRIOR_I_CONFIG` | Warrior I (Virabhadrasana I) | Front knee 80°–110° | 30s |
509
+ | `WARRIOR_II_CONFIG` | Warrior II (Virabhadrasana II) | Front knee 80°–110° | 30s |
510
+ | `WARRIOR_III_CONFIG` | Warrior III (Virabhadrasana III) | Hip hinge 70°–110° | 30s |
511
+ | `REVERSE_WARRIOR_CONFIG` | Reverse Warrior | Front knee 80°–110° | 30s |
512
+ | `DOWNWARD_DOG_CONFIG` | Downward Dog (Adho Mukha Svanasana) | Hip 55°–100° | 30s |
513
+ | `COBRA_POSE_CONFIG` | Cobra Pose (Bhujangasana) | Hip extension 120°–170° | 30s |
514
+ | `TRIANGLE_POSE_CONFIG` | Triangle Pose (Trikonasana) | Front leg 160°–180° | 30s |
515
+ | `EXTENDED_SIDE_ANGLE_CONFIG` | Extended Side Angle (Utthita Parsvakonasana) | Front knee 80°–110° | 30s |
516
+ | `BRIDGE_POSE_CONFIG` | Bridge Pose (Setu Bandhasana) | Knee 80°–110° | 30s |
517
+ | `BOAT_POSE_CONFIG` | Boat Pose (Navasana) | Hip flexion 60°–110° | 30s |
518
+ | `CAMEL_POSE_CONFIG` | Camel Pose (Ustrasana) | Hip extension 120°–165° | 30s |
519
+ | `CHILDS_POSE_CONFIG` | Child's Pose (Balasana) | Hip fold 30°–80° | 60s |
520
+ | `BOW_POSE_CONFIG` | Bow Pose (Dhanurasana) | Knee 50°–100° | 30s |
521
+ | `FISH_POSE_CONFIG` | Fish Pose (Matsyasana) | Chest open 130°–170° | 30s |
452
522
 
453
523
  ### Custom Exercise Config
454
524
 
@@ -486,12 +556,24 @@ Landmarks are mapped to MediaPipe-compatible indices on both platforms. iOS Visi
486
556
  | 14 | Right elbow | 26 | Right knee |
487
557
  | 15 | Left wrist | 27 | Left ankle |
488
558
 
489
- **iOS note:** Vision provides 19 joints. Indices not available from Vision (face details 1-10, hands 17-22, feet 29-32) are filled with `visibility: 0` and skipped by the skeleton overlay.
559
+ **iOS note:** Vision provides 19 joints. Indices not available (face 1-10, hands 17-22, feet 29-32) are filled with `visibility: 0`.
490
560
 
491
561
  **Android note:** ML Kit provides all 33 landmarks matching MediaPipe indices exactly.
492
562
 
493
563
  ---
494
564
 
565
+ ## 📏 Camera Angle Guide
566
+
567
+ | ✅ Good | ❌ Bad |
568
+ | --- | --- |
569
+ | Side view, full body visible | Front-facing view |
570
+ | Phone at waist height, 6-8 ft away | Ground-level angle |
571
+ | Well-lit environment | Heavy glare or backlight |
572
+
573
+ Each exercise config includes a `cameraAngle` recommendation (`'side'` or `'front'`). Side view works for most exercises. Front view is needed for lateral raises, sumo squats, warrior II, and hip abductions.
574
+
575
+ ---
576
+
495
577
  ## 🏗️ Architecture — OS-Native vs MediaPipe
496
578
 
497
579
  | | OS-Native (current) | MediaPipe (previous) |
@@ -499,21 +581,22 @@ Landmarks are mapped to MediaPipe-compatible indices on both platforms. iOS Visi
499
581
  | **iOS** | Apple Vision framework (built-in) | MediaPipeTasksVision (CocoaPod) |
500
582
  | **Android** | Google ML Kit (Play Services) | com.google.mediapipe:tasks-vision |
501
583
  | **Model file** | None needed | ~3 MB bundled `.task` file |
502
- | **Color conversion** | None — takes CVPixelBuffer/ImageProxy directly | BGRA required (iOS), NV21→RGB (Android) |
584
+ | **Color conversion** | None — takes CVPixelBuffer/InputImage directly | BGRA required (iOS), NV21→RGB (Android) |
503
585
  | **App size impact** | ~200 KB (Nitro module code only) | ~11-15 MB (SDK + model) |
504
586
  | **Updates** | OS/Play Services updates | Manual model file replacement |
505
587
 
506
588
  ---
507
589
 
508
- ## 📏 Camera Angle Guide
509
-
510
- For best results, the camera should see the exerciser from a **side profile**:
590
+ ## 🛡️ Safety Features
511
591
 
512
- | Good | Bad |
592
+ | Feature | Description |
513
593
  | --- | --- |
514
- | Side view, full body visible | Front-facing view |
515
- | Phone at waist height, 6-8 ft away | Ground-level angle |
516
- | Well-lit environment | Heavy glare or backlight |
594
+ | **Min rep duration** | 800ms minimum per rep — prevents false counts from sensor noise |
595
+ | **Form score gate** | Reps with form score below 30/100 are rejected and not counted |
596
+ | **Feedback throttle** | Same form warning fires max once every 5 seconds to avoid UI spam |
597
+ | **Pose lost detection** | `onPoseLost` / `onPoseRegained` callbacks when user exits/enters frame |
598
+ | **Frame throttle** | Processes every 3rd frame to reduce CPU load without losing accuracy |
599
+ | **Visibility filter** | Landmarks with confidence below 0.3 are excluded from angle calculations |
517
600
 
518
601
  ---
519
602
 
@@ -521,8 +604,8 @@ For best results, the camera should see the exerciser from a **side profile**:
521
604
 
522
605
  | Platform | Status | Notes |
523
606
  | --- | --- | --- |
524
- | **iOS** | ✅ Supported | Requires physical device, iOS 14+ (Vision body pose) |
525
- | **Android** | ✅ Supported | API 23+ (ML Kit), Google Play Services required |
607
+ | **iOS** | ✅ Supported | Physical device, iOS 14+ (Vision body pose) |
608
+ | **Android** | ✅ Supported | API 23+, Google Play Services required |
526
609
  | **iOS Simulator** | ❌ Not supported | No camera access |
527
610
  | **Android Emulator** | ❌ Not supported | No real camera feed |
528
611
 
@@ -537,13 +620,11 @@ For best results, the camera should see the exerciser from a **side profile**:
537
620
  | Vision framework (iOS, built-in) | ~0 KB (system framework) |
538
621
  | **Total new addition** | **~200 KB** |
539
622
 
540
- Compared to the MediaPipe approach (~11-15 MB), the OS-native approach adds virtually zero app size.
541
-
542
623
  ---
543
624
 
544
625
  ## 🤝 Contributing
545
626
 
546
- PRs welcome!
627
+ PRs welcome! Adding a new exercise is as simple as creating a config file — no native code changes needed.
547
628
 
548
629
  * [Development Workflow](CONTRIBUTING.md#development-workflow)
549
630
  * [Sending a PR](CONTRIBUTING.md#sending-a-pull-request)
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ export const BOAT_POSE_CONFIG = {
4
+ name: 'Boat Pose (Navasana)',
5
+ type: 'hold',
6
+ angles: [{
7
+ name: 'hipFlexion',
8
+ landmarkA: 11,
9
+ landmarkB: 23,
10
+ landmarkC: 25
11
+ }, {
12
+ name: 'kneeAngle',
13
+ landmarkA: 23,
14
+ landmarkB: 25,
15
+ landmarkC: 27
16
+ }],
17
+ phases: [{
18
+ phase: 'hold',
19
+ angleName: 'hipFlexion',
20
+ minAngle: 60,
21
+ maxAngle: 110
22
+ }],
23
+ repSequence: [],
24
+ formRules: [{
25
+ name: 'backRounding',
26
+ message: "Lengthen your spine — don't round your back",
27
+ severity: 'warning',
28
+ angleName: 'hipFlexion',
29
+ minAngle: 60,
30
+ maxAngle: 110
31
+ }, {
32
+ name: 'legsDropping',
33
+ message: 'Keep your legs lifted',
34
+ severity: 'warning',
35
+ angleName: 'kneeAngle',
36
+ minAngle: 150,
37
+ maxAngle: 180
38
+ }],
39
+ holdDurationMs: 30000
40
+ };
41
+ //# sourceMappingURL=boat-pose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["BOAT_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/boat-pose.ts"],"mappings":";;AAEA,OAAO,MAAMA,gBAAgC,GAAG;EAC9CC,IAAI,EAAE,sBAAsB;EAC5BC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IAAEF,IAAI,EAAE,YAAY;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,EACnE;IAAEL,IAAI,EAAE,WAAW;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,CACnE;EACDC,MAAM,EAAE,CACN;IAAEC,KAAK,EAAE,MAAM;IAAEC,SAAS,EAAE,YAAY;IAAEC,QAAQ,EAAE,EAAE;IAAEC,QAAQ,EAAE;EAAI,CAAC,CACxE;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,cAAc;IACpBa,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,YAAY;IACvBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,cAAc;IACpBa,OAAO,EAAE,uBAAuB;IAChCC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ export const BOW_POSE_CONFIG = {
4
+ name: 'Bow Pose (Dhanurasana)',
5
+ type: 'hold',
6
+ angles: [{
7
+ name: 'kneeAngle',
8
+ landmarkA: 23,
9
+ landmarkB: 25,
10
+ landmarkC: 27
11
+ }, {
12
+ name: 'shoulderExtension',
13
+ landmarkA: 23,
14
+ landmarkB: 11,
15
+ landmarkC: 15
16
+ }],
17
+ phases: [{
18
+ phase: 'hold',
19
+ angleName: 'kneeAngle',
20
+ minAngle: 50,
21
+ maxAngle: 100
22
+ }],
23
+ repSequence: [],
24
+ formRules: [{
25
+ name: 'kneesTooWide',
26
+ message: 'Keep your knees hip-width apart',
27
+ severity: 'warning',
28
+ angleName: 'kneeAngle',
29
+ minAngle: 50,
30
+ maxAngle: 100
31
+ }],
32
+ holdDurationMs: 30000
33
+ };
34
+ //# sourceMappingURL=bow-pose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["BOW_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/bow-pose.ts"],"mappings":";;AACA,OAAO,MAAMA,eAA+B,GAAG;EAC7CC,IAAI,EAAE,wBAAwB;EAC9BC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IAAEF,IAAI,EAAE,WAAW;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,EAClE;IAAEL,IAAI,EAAE,mBAAmB;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,CAC3E;EACDC,MAAM,EAAE,CACN;IAAEC,KAAK,EAAE,MAAM;IAAEC,SAAS,EAAE,WAAW;IAAEC,QAAQ,EAAE,EAAE;IAAEC,QAAQ,EAAE;EAAI,CAAC,CACvE;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,cAAc;IACpBa,OAAO,EAAE,iCAAiC;IAC1CC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ export const BRIDGE_POSE_CONFIG = {
4
+ name: 'Bridge Pose (Setu Bandhasana)',
5
+ type: 'hold',
6
+ angles: [{
7
+ name: 'hipExtension',
8
+ landmarkA: 11,
9
+ landmarkB: 23,
10
+ landmarkC: 25
11
+ }, {
12
+ name: 'kneeAngle',
13
+ landmarkA: 23,
14
+ landmarkB: 25,
15
+ landmarkC: 27
16
+ }],
17
+ phases: [{
18
+ phase: 'hold',
19
+ angleName: 'kneeAngle',
20
+ minAngle: 80,
21
+ maxAngle: 110
22
+ }],
23
+ repSequence: [],
24
+ formRules: [{
25
+ name: 'hipsLow',
26
+ message: 'Push your hips up higher',
27
+ severity: 'warning',
28
+ angleName: 'hipExtension',
29
+ minAngle: 140,
30
+ maxAngle: 180
31
+ }],
32
+ holdDurationMs: 30000
33
+ };
34
+ //# sourceMappingURL=bridge-pose.js.map