react-native-nitro-pose-exercises 1.0.8 → 1.1.1

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 (82) hide show
  1. package/NitroPoseExercises.podspec +2 -1
  2. package/README.md +137 -92
  3. package/android/build.gradle +9 -10
  4. package/android/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +148 -125
  5. package/ios/NitroPoseExercises.swift +113 -88
  6. package/lib/module/config/bicep-curl.js +61 -0
  7. package/lib/module/config/bicep-curl.js.map +1 -0
  8. package/lib/module/config/chair-pose.js +64 -0
  9. package/lib/module/config/chair-pose.js.map +1 -0
  10. package/lib/module/config/cobra-pose.js +57 -0
  11. package/lib/module/config/cobra-pose.js.map +1 -0
  12. package/lib/module/config/downward-dog.js +65 -0
  13. package/lib/module/config/downward-dog.js.map +1 -0
  14. package/lib/module/config/lunge.js +62 -0
  15. package/lib/module/config/lunge.js.map +1 -0
  16. package/lib/module/config/plank.js +57 -0
  17. package/lib/module/config/plank.js.map +1 -0
  18. package/lib/module/config/shoulder-press.js +62 -0
  19. package/lib/module/config/shoulder-press.js.map +1 -0
  20. package/lib/module/config/situp.js +48 -0
  21. package/lib/module/config/situp.js.map +1 -0
  22. package/lib/module/config/squat.js +69 -0
  23. package/lib/module/config/squat.js.map +1 -0
  24. package/lib/module/config/tree-pose.js +57 -0
  25. package/lib/module/config/tree-pose.js.map +1 -0
  26. package/lib/module/config/tricep-dip.js +55 -0
  27. package/lib/module/config/tricep-dip.js.map +1 -0
  28. package/lib/module/config/wall-sit.js +57 -0
  29. package/lib/module/config/wall-sit.js.map +1 -0
  30. package/lib/module/config/warrior-i.js +79 -0
  31. package/lib/module/config/warrior-i.js.map +1 -0
  32. package/lib/module/config/warrior-ii.js +86 -0
  33. package/lib/module/config/warrior-ii.js.map +1 -0
  34. package/lib/module/index.js +19 -0
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/src/config/bicep-curl.d.ts +3 -0
  37. package/lib/typescript/src/config/bicep-curl.d.ts.map +1 -0
  38. package/lib/typescript/src/config/chair-pose.d.ts +3 -0
  39. package/lib/typescript/src/config/chair-pose.d.ts.map +1 -0
  40. package/lib/typescript/src/config/cobra-pose.d.ts +3 -0
  41. package/lib/typescript/src/config/cobra-pose.d.ts.map +1 -0
  42. package/lib/typescript/src/config/downward-dog.d.ts +3 -0
  43. package/lib/typescript/src/config/downward-dog.d.ts.map +1 -0
  44. package/lib/typescript/src/config/lunge.d.ts +3 -0
  45. package/lib/typescript/src/config/lunge.d.ts.map +1 -0
  46. package/lib/typescript/src/config/plank.d.ts +3 -0
  47. package/lib/typescript/src/config/plank.d.ts.map +1 -0
  48. package/lib/typescript/src/config/shoulder-press.d.ts +3 -0
  49. package/lib/typescript/src/config/shoulder-press.d.ts.map +1 -0
  50. package/lib/typescript/src/config/situp.d.ts +3 -0
  51. package/lib/typescript/src/config/situp.d.ts.map +1 -0
  52. package/lib/typescript/src/config/squat.d.ts +3 -0
  53. package/lib/typescript/src/config/squat.d.ts.map +1 -0
  54. package/lib/typescript/src/config/tree-pose.d.ts +3 -0
  55. package/lib/typescript/src/config/tree-pose.d.ts.map +1 -0
  56. package/lib/typescript/src/config/tricep-dip.d.ts +3 -0
  57. package/lib/typescript/src/config/tricep-dip.d.ts.map +1 -0
  58. package/lib/typescript/src/config/wall-sit.d.ts +3 -0
  59. package/lib/typescript/src/config/wall-sit.d.ts.map +1 -0
  60. package/lib/typescript/src/config/warrior-i.d.ts +3 -0
  61. package/lib/typescript/src/config/warrior-i.d.ts.map +1 -0
  62. package/lib/typescript/src/config/warrior-ii.d.ts +3 -0
  63. package/lib/typescript/src/config/warrior-ii.d.ts.map +1 -0
  64. package/lib/typescript/src/index.d.ts +14 -0
  65. package/lib/typescript/src/index.d.ts.map +1 -1
  66. package/nitro.json +8 -1
  67. package/package.json +10 -10
  68. package/src/config/bicep-curl.ts +64 -0
  69. package/src/config/chair-pose.ts +67 -0
  70. package/src/config/cobra-pose.ts +59 -0
  71. package/src/config/downward-dog.ts +68 -0
  72. package/src/config/lunge.ts +65 -0
  73. package/src/config/plank.ts +59 -0
  74. package/src/config/shoulder-press.ts +63 -0
  75. package/src/config/situp.ts +51 -0
  76. package/src/config/squat.ts +71 -0
  77. package/src/config/tree-pose.ts +59 -0
  78. package/src/config/tricep-dip.ts +58 -0
  79. package/src/config/wall-sit.ts +59 -0
  80. package/src/config/warrior-i.ts +82 -0
  81. package/src/config/warrior-ii.ts +88 -0
  82. package/src/index.tsx +19 -0
@@ -1,15 +1,43 @@
1
+ // ios/HybridPoseExercise.swift
2
+
1
3
  import Foundation
2
4
  import NitroModules
3
- import MediaPipeTasksVision
4
5
  import VisionCamera
5
6
  import AVFoundation
7
+ import Vision
6
8
 
7
- class NitroPoseExercises: HybridNitroPoseExercisesSpec {
9
+ class HybridPoseExercise: HybridNitroPoseExercisesSpec {
8
10
 
9
- // ─── MediaPipe ──────────────────────────────────────────────
10
- private var poseLandmarker: PoseLandmarker?
11
+ // ─── Vision Framework ───────────────────────────────────────
11
12
  private var isInitialized = false
12
13
 
14
+ // ─── Landmark Index Mapping ─────────────────────────────────
15
+ // Maps Apple Vision joint names to MediaPipe landmark indices
16
+ // that our JS configs expect
17
+ private static let visionToMediaPipeMap: [(VNHumanBodyPoseObservation.JointName, Int)] = [
18
+ (.nose, 0),
19
+ (.leftShoulder, 11),
20
+ (.rightShoulder, 12),
21
+ (.leftElbow, 13),
22
+ (.rightElbow, 14),
23
+ (.leftWrist, 15),
24
+ (.rightWrist, 16),
25
+ (.leftHip, 23),
26
+ (.rightHip, 24),
27
+ (.leftKnee, 25),
28
+ (.rightKnee, 26),
29
+ (.leftAnkle, 27),
30
+ (.rightAnkle, 28),
31
+ // Vision also provides these but they map to non-standard indices
32
+ // We include them for skeleton drawing
33
+ (.neck, 10), // approximate — MediaPipe doesn't have neck
34
+ (.root, 33), // hip center — not in MediaPipe, we skip
35
+ (.leftEar, 7),
36
+ (.rightEar, 8),
37
+ (.leftEye, 2),
38
+ (.rightEye, 5),
39
+ ]
40
+
13
41
  // ─── Exercise Config ────────────────────────────────────────
14
42
  private var exerciseConfig: ExerciseConfig?
15
43
 
@@ -34,9 +62,6 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
34
62
  private var countdownSeconds: Double = 0
35
63
  private var countdownTimer: Timer?
36
64
 
37
- private var frameCount: Int = 0
38
- private let processEveryNFrames: Int = 3 // Only process every 3rd frame
39
-
40
65
  // ─── Form Tracking ──────────────────────────────────────────
41
66
  private var lastFormFeedbackTime: [String: Date] = [:]
42
67
  private var sessionFormViolations: [FormFeedback] = []
@@ -48,6 +73,10 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
48
73
  // ─── Pose Tracking ──────────────────────────────────────────
49
74
  private var poseWasLost = false
50
75
 
76
+ // ─── Frame Throttle ─────────────────────────────────────────
77
+ private var frameCount: Int = 0
78
+ private let processEveryNFrames: Int = 3
79
+
51
80
  // ─── Callbacks ──────────────────────────────────────────────
52
81
  var onRepComplete: ((_ data: RepData) -> Void)?
53
82
  var onPhaseChange: ((_ phase: ExercisePhase) -> Void)?
@@ -65,28 +94,15 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
65
94
  // ═══════════════════════════════════════════════════════════
66
95
 
67
96
  func initialize(modelPath: String) throws -> Promise<Void> {
68
- print("[PoseExercise] initialize called with path: \(modelPath)")
69
-
97
+ // No model loading needed — Vision framework is built into iOS
70
98
  return Promise.async { [weak self] in
71
99
  guard let self = self else { return }
72
-
73
- let options = PoseLandmarkerOptions()
74
- options.baseOptions.modelAssetPath = modelPath
75
- options.runningMode = .image
76
- options.numPoses = 1
77
- options.minPoseDetectionConfidence = 0.5
78
- options.minPosePresenceConfidence = 0.5
79
- options.minTrackingConfidence = 0.5
80
-
81
- self.poseLandmarker = try PoseLandmarker(options: options)
82
100
  self.isInitialized = true
83
-
84
- print("[PoseExercise] MediaPipe initialized successfully")
101
+ print("[PoseExercise] Initialized with Apple Vision (no model file needed)")
85
102
  }
86
103
  }
87
104
 
88
105
  func release() throws {
89
- poseLandmarker = nil
90
106
  isInitialized = false
91
107
  _status = .idle
92
108
  resetSession()
@@ -106,8 +122,6 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
106
122
  // ═══════════════════════════════════════════════════════════
107
123
 
108
124
  func startSession(targetReps: Double, countdownSeconds: Double) throws {
109
- print("[PoseExercise] startSession called - target: \(targetReps), countdown: \(countdownSeconds)")
110
-
111
125
  resetSession()
112
126
  self.targetReps = targetReps
113
127
  self.countdownSeconds = countdownSeconds
@@ -138,75 +152,90 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
138
152
  }
139
153
 
140
154
  // ═══════════════════════════════════════════════════════════
141
- // MARK: - Frame Processing
155
+ // MARK: - Frame Processing (Apple Vision)
142
156
  // ═══════════════════════════════════════════════════════════
143
157
 
144
- func processFrame(frame: any HybridFrameSpec) throws {
145
- print("[PoseExercise] processFrame called, status: \(_status)")
158
+ func processFrame(frame: any HybridFrameSpec) throws {
159
+ guard _status == .active || _status == .countdown else { return }
160
+ guard isInitialized else { return }
146
161
 
147
- frameCount += 1
148
- if frameCount % processEveryNFrames != 0 { return }
162
+ // Frame throttle
163
+ frameCount += 1
164
+ if frameCount % processEveryNFrames != 0 { return }
149
165
 
150
- guard _status == .active || _status == .countdown else {
151
- print("[PoseExercise] Skipping - status is \(_status)")
152
- return
153
- }
154
- guard isInitialized, let landmarker = poseLandmarker else {
155
- print("[PoseExercise] Skipping - not initialized: \(isInitialized)")
156
- return
157
- }
166
+ // Get CMSampleBuffer from VisionCamera frame
167
+ guard let nativeFrame = frame as? any NativeFrame,
168
+ let sampleBuffer = nativeFrame.sampleBuffer else { return }
158
169
 
159
- guard let nativeFrame = frame as? any NativeFrame else {
160
- print("[PoseExercise] Failed to cast to NativeFrame")
161
- return
162
- }
163
- guard let sampleBuffer = nativeFrame.sampleBuffer else {
164
- print("[PoseExercise] sampleBuffer is nil")
165
- return
166
- }
170
+ // Get pixel buffer Vision takes CVPixelBuffer directly, no color conversion needed
171
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
167
172
 
168
- print("[PoseExercise] Got sampleBuffer, running detection...")
173
+ // Create Vision request
174
+ let request = VNDetectHumanBodyPoseRequest()
169
175
 
170
- do {
171
- let mpImage = try MPImage(sampleBuffer: sampleBuffer)
172
- let result = try landmarker.detect(image: mpImage)
176
+ // Run synchronously on this frame processor thread
177
+ let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
173
178
 
174
- print("[PoseExercise] Detection done, landmarks count: \(result.landmarks.count)")
179
+ do {
180
+ try handler.perform([request])
181
+
182
+ guard let observation = request.results?.first else {
183
+ // No pose detected
184
+ if !poseWasLost {
185
+ poseWasLost = true
186
+ onPoseLost?()
187
+ }
188
+ _landmarks = []
189
+ return
190
+ }
175
191
 
176
- if let poseLandmarks = result.landmarks.first {
192
+ // Pose detected
177
193
  if poseWasLost {
178
194
  poseWasLost = false
179
195
  onPoseRegained?()
180
196
  }
181
197
 
182
- _landmarks = poseLandmarks.map { lm in
183
- Landmark(
184
- x: Double(lm.x),
185
- y: Double(lm.y),
186
- z: Double(lm.z),
187
- visibility: Double(lm.visibility ?? 0)
188
- )
198
+ // Map Vision joints to MediaPipe landmark array (34 slots, indices 0-33)
199
+ // Fill all slots with zero-visibility first
200
+ var landmarkArray = [Landmark](repeating: Landmark(x: 0, y: 0, z: 0, visibility: 0), count: 34)
201
+
202
+ for (jointName, mediaPipeIndex) in HybridPoseExercise.visionToMediaPipeMap {
203
+ guard mediaPipeIndex < 34 else { continue }
204
+
205
+ do {
206
+ let point = try observation.recognizedPoint(jointName)
207
+
208
+ // Vision uses bottom-left origin (0,0 = bottom-left)
209
+ // MediaPipe uses top-left origin (0,0 = top-left)
210
+ // Flip Y axis
211
+ let confidence = Double(point.confidence)
212
+
213
+ landmarkArray[mediaPipeIndex] = Landmark(
214
+ x: Double(point.location.x),
215
+ y: 1.0 - Double(point.location.y), // flip Y
216
+ z: 0, // Vision doesn't provide Z depth
217
+ visibility: confidence
218
+ )
219
+
220
+ // Debug logging — uncomment to verify mapping
221
+ // print("[PoseExercise] \(jointName.rawValue.rawValue) → index \(mediaPipeIndex): x=\(String(format: "%.3f", point.location.x)) y=\(String(format: "%.3f", 1.0 - Double(point.location.y))) conf=\(String(format: "%.2f", confidence))")
222
+
223
+ } catch {
224
+ // Joint not detected — leave as zero visibility
225
+ continue
226
+ }
189
227
  }
190
228
 
191
- print("[PoseExercise] Landmarks detected: \(_landmarks.count)")
229
+ _landmarks = landmarkArray
192
230
 
193
231
  if _status == .active {
194
232
  processExerciseLogic()
195
233
  }
196
234
 
197
- } else {
198
- print("[PoseExercise] No pose detected in frame")
199
- if !poseWasLost {
200
- poseWasLost = true
201
- onPoseLost?()
202
- }
203
- _landmarks = []
235
+ } catch {
236
+ print("[PoseExercise] Vision error: \(error.localizedDescription)")
204
237
  }
205
-
206
- } catch {
207
- print("[PoseExercise] MediaPipe error: \(error.localizedDescription)")
208
238
  }
209
- }
210
239
 
211
240
  // ═══════════════════════════════════════════════════════════
212
241
  // MARK: - Exercise Logic Engine
@@ -216,7 +245,6 @@ func processFrame(frame: any HybridFrameSpec) throws {
216
245
  guard let config = exerciseConfig else { return }
217
246
  guard !_landmarks.isEmpty else { return }
218
247
 
219
- // 1. Calculate all angles defined in the config
220
248
  var currentAngles: [String: Double] = [:]
221
249
  var angleSnapshots: [AngleSnapshot] = []
222
250
 
@@ -227,6 +255,11 @@ func processFrame(frame: any HybridFrameSpec) throws {
227
255
 
228
256
  guard a < _landmarks.count, b < _landmarks.count, c < _landmarks.count else { continue }
229
257
 
258
+ // Only calculate if all three landmarks have reasonable confidence
259
+ guard _landmarks[a].visibility > 0.3,
260
+ _landmarks[b].visibility > 0.3,
261
+ _landmarks[c].visibility > 0.3 else { continue }
262
+
230
263
  let angle = calculateAngle(
231
264
  pointA: _landmarks[a],
232
265
  vertex: _landmarks[b],
@@ -239,31 +272,25 @@ func processFrame(frame: any HybridFrameSpec) throws {
239
272
 
240
273
  repAngleSnapshots = angleSnapshots
241
274
 
242
- // 2. Determine current phase from angle thresholds
275
+ // Debug logging uncomment to see angles
276
+ // for (name, angle) in currentAngles {
277
+ // print("[PoseExercise] Angle \(name): \(String(format: "%.1f", angle))°")
278
+ // }
279
+
243
280
  let detectedPhase = determinePhase(from: currentAngles, config: config)
244
281
 
245
282
  if detectedPhase != _currentPhase && detectedPhase != .unknown {
246
283
  let previousPhase = _currentPhase
247
284
  _currentPhase = detectedPhase
248
285
  onPhaseChange?(detectedPhase)
249
-
250
- // 3. Update phase history for rep counting
251
286
  handlePhaseTransition(from: previousPhase, to: detectedPhase, config: config)
252
287
  }
253
288
 
254
- // 4. Check form rules
255
289
  checkFormRules(currentAngles: currentAngles, config: config)
256
290
 
257
- // 5. Handle hold-based exercises
258
291
  if config.type == .hold {
259
292
  handleHoldProgress(currentAngles: currentAngles, config: config)
260
293
  }
261
-
262
- // Temporary debug logging — remove after testing
263
- for (name, angle) in currentAngles {
264
- print("[PoseExercise] Angle \(name): \(String(format: "%.1f", angle))°")
265
- }
266
- print("[PoseExercise] Detected phase: \(detectedPhase), Current phase: \(_currentPhase)")
267
294
  }
268
295
 
269
296
  // ═══════════════════════════════════════════════════════════
@@ -284,9 +311,7 @@ print("[PoseExercise] Detected phase: \(detectedPhase), Current phase: \(_curren
284
311
 
285
312
  let cosAngle = max(-1.0, min(1.0, dot / (magA * magC)))
286
313
  let angleRad = acos(cosAngle)
287
- let angleDeg = angleRad * (180.0 / .pi)
288
-
289
- return angleDeg
314
+ return angleRad * (180.0 / .pi)
290
315
  }
291
316
 
292
317
  // ═══════════════════════════════════════════════════════════
@@ -296,7 +321,6 @@ print("[PoseExercise] Detected phase: \(detectedPhase), Current phase: \(_curren
296
321
  private func determinePhase(from angles: [String: Double], config: ExerciseConfig) -> ExercisePhase {
297
322
  for phaseThreshold in config.phases {
298
323
  guard let angle = angles[phaseThreshold.angleName] else { continue }
299
-
300
324
  if angle >= phaseThreshold.minAngle && angle <= phaseThreshold.maxAngle {
301
325
  return phaseThreshold.phase
302
326
  }
@@ -308,7 +332,7 @@ print("[PoseExercise] Detected phase: \(detectedPhase), Current phase: \(_curren
308
332
  // MARK: - Rep Counting State Machine
309
333
  // ═══════════════════════════════════════════════════════════
310
334
 
311
- private func handlePhaseTransition(from previousPhase: ExercisePhase, to newPhase: ExercisePhase, config: ExerciseConfig) {
335
+ private func handlePhaseTransition(from previousPhase: ExercisePhase, to newPhase: ExercisePhase, config: ExerciseConfig) {
312
336
  guard config.type == .rep else { return }
313
337
 
314
338
  phaseHistory.append(newPhase)
@@ -523,5 +547,6 @@ private func handlePhaseTransition(from previousPhase: ExercisePhase, to newPhas
523
547
  countdownSeconds = 0
524
548
  countdownTimer?.invalidate()
525
549
  countdownTimer = nil
550
+ frameCount = 0
526
551
  }
527
- }
552
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ // MediaPipe Pose Landmark indices
4
+ // 11 = left shoulder, 13 = left elbow, 15 = left wrist
5
+ // 12 = right shoulder, 14 = right elbow, 16 = right wrist
6
+
7
+ export const BICEP_CURL_CONFIG = {
8
+ name: 'Bicep Curl',
9
+ type: 'rep',
10
+ angles: [{
11
+ name: 'leftElbow',
12
+ landmarkA: 11,
13
+ // left shoulder
14
+ landmarkB: 13,
15
+ // left elbow (vertex)
16
+ landmarkC: 15 // left wrist
17
+ }, {
18
+ name: 'rightElbow',
19
+ landmarkA: 12,
20
+ // right shoulder
21
+ landmarkB: 14,
22
+ // right elbow (vertex)
23
+ landmarkC: 16 // right wrist
24
+ }, {
25
+ name: 'leftShoulder',
26
+ landmarkA: 23,
27
+ // left hip
28
+ landmarkB: 11,
29
+ // left shoulder (vertex)
30
+ landmarkC: 13 // left elbow
31
+ }],
32
+ phases: [{
33
+ phase: 'down',
34
+ angleName: 'leftElbow',
35
+ minAngle: 150,
36
+ maxAngle: 180
37
+ }, {
38
+ phase: 'up',
39
+ angleName: 'leftElbow',
40
+ minAngle: 25,
41
+ maxAngle: 70
42
+ }],
43
+ repSequence: ['down', 'up', 'down'],
44
+ formRules: [{
45
+ name: 'elbowFlare',
46
+ message: 'Keep your elbows pinned to your sides',
47
+ severity: 'warning',
48
+ angleName: 'leftShoulder',
49
+ minAngle: 0,
50
+ maxAngle: 30
51
+ }, {
52
+ name: 'swinging',
53
+ message: "Don't swing — control the movement",
54
+ severity: 'error',
55
+ angleName: 'leftShoulder',
56
+ minAngle: 0,
57
+ maxAngle: 45
58
+ }],
59
+ holdDurationMs: 0
60
+ };
61
+ //# sourceMappingURL=bicep-curl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["BICEP_CURL_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/bicep-curl.ts"],"mappings":";;AAEA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,YAAY;EAClBC,IAAI,EAAE,KAAK;EACXC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,WAAW;IACjBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,YAAY;IAClBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,cAAc;IACpBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEH,KAAK,EAAE,IAAI;IACXC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;EACnCC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,YAAY;IAClBa,OAAO,EAAE,uCAAuC;IAChDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,oCAAoC;IAC7CC,QAAQ,EAAE,OAAO;IACjBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ // MediaPipe Pose Landmark indices
4
+ // 11 = left shoulder, 13 = left elbow, 15 = left wrist
5
+ // 23 = left hip, 25 = left knee, 27 = left ankle
6
+ // 12 = right shoulder, 24 = right hip
7
+
8
+ export const CHAIR_POSE_CONFIG = {
9
+ name: 'Chair Pose (Utkatasana)',
10
+ type: 'hold',
11
+ angles: [{
12
+ name: 'leftKnee',
13
+ landmarkA: 23,
14
+ // left hip
15
+ landmarkB: 25,
16
+ // left knee (vertex)
17
+ landmarkC: 27 // left ankle
18
+ }, {
19
+ name: 'leftHip',
20
+ landmarkA: 11,
21
+ // left shoulder
22
+ landmarkB: 23,
23
+ // left hip (vertex)
24
+ landmarkC: 25 // left knee
25
+ }, {
26
+ name: 'leftArm',
27
+ landmarkA: 11,
28
+ // left shoulder
29
+ landmarkB: 13,
30
+ // left elbow (vertex)
31
+ landmarkC: 15 // left wrist
32
+ }],
33
+ phases: [{
34
+ phase: 'hold',
35
+ angleName: 'leftKnee',
36
+ minAngle: 90,
37
+ maxAngle: 130
38
+ }],
39
+ repSequence: [],
40
+ formRules: [{
41
+ name: 'kneesTooStraight',
42
+ message: 'Sit deeper — bend your knees more',
43
+ severity: 'warning',
44
+ angleName: 'leftKnee',
45
+ minAngle: 90,
46
+ maxAngle: 130
47
+ }, {
48
+ name: 'leaningForward',
49
+ message: "Keep your chest lifted — don't lean too far forward",
50
+ severity: 'warning',
51
+ angleName: 'leftHip',
52
+ minAngle: 70,
53
+ maxAngle: 130
54
+ }, {
55
+ name: 'armsNotUp',
56
+ message: 'Reach your arms up overhead',
57
+ severity: 'info',
58
+ angleName: 'leftArm',
59
+ minAngle: 160,
60
+ maxAngle: 180
61
+ }],
62
+ holdDurationMs: 30000 // 30 seconds
63
+ };
64
+ //# sourceMappingURL=chair-pose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["CHAIR_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/chair-pose.ts"],"mappings":";;AAEA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,yBAAyB;EAC/BC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,UAAU;IAChBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,kBAAkB;IACxBa,OAAO,EAAE,mCAAmC;IAC5CC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,gBAAgB;IACtBa,OAAO,EAAE,qDAAqD;IAC9DC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,WAAW;IACjBa,OAAO,EAAE,6BAA6B;IACtCC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ // MediaPipe Pose Landmark indices
4
+ // 11 = left shoulder, 13 = left elbow, 15 = left wrist
5
+ // 12 = right shoulder, 14 = right elbow, 16 = right wrist
6
+ // 23 = left hip, 25 = left knee, 27 = left ankle
7
+
8
+ export const COBRA_POSE_CONFIG = {
9
+ name: 'Cobra Pose (Bhujangasana)',
10
+ type: 'hold',
11
+ angles: [{
12
+ name: 'leftElbow',
13
+ landmarkA: 11,
14
+ // left shoulder
15
+ landmarkB: 13,
16
+ // left elbow (vertex)
17
+ landmarkC: 15 // left wrist
18
+ }, {
19
+ name: 'hipExtension',
20
+ landmarkA: 11,
21
+ // left shoulder
22
+ landmarkB: 23,
23
+ // left hip (vertex)
24
+ landmarkC: 25 // left knee
25
+ }, {
26
+ name: 'legStraight',
27
+ landmarkA: 23,
28
+ // left hip
29
+ landmarkB: 25,
30
+ // left knee (vertex)
31
+ landmarkC: 27 // left ankle
32
+ }],
33
+ phases: [{
34
+ phase: 'hold',
35
+ angleName: 'hipExtension',
36
+ minAngle: 120,
37
+ maxAngle: 170
38
+ }],
39
+ repSequence: [],
40
+ formRules: [{
41
+ name: 'shouldersTensed',
42
+ message: 'Relax your shoulders away from your ears',
43
+ severity: 'info',
44
+ angleName: 'leftElbow',
45
+ minAngle: 140,
46
+ maxAngle: 180
47
+ }, {
48
+ name: 'legsBending',
49
+ message: 'Keep your legs straight and pressed into the floor',
50
+ severity: 'warning',
51
+ angleName: 'legStraight',
52
+ minAngle: 160,
53
+ maxAngle: 180
54
+ }],
55
+ holdDurationMs: 30000 // 30 seconds
56
+ };
57
+ //# sourceMappingURL=cobra-pose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["COBRA_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/cobra-pose.ts"],"mappings":";;AAEA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,2BAA2B;EACjCC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,WAAW;IACjBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,cAAc;IACpBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,aAAa;IACnBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,iBAAiB;IACvBa,OAAO,EAAE,0CAA0C;IACnDC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,aAAa;IACnBa,OAAO,EAAE,oDAAoD;IAC7DC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,aAAa;IACxBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ // MediaPipe Pose Landmark indices
4
+ // 11 = left shoulder, 13 = left elbow, 15 = left wrist
5
+ // 12 = right shoulder, 14 = right elbow, 16 = right wrist
6
+ // 23 = left hip, 25 = left knee, 27 = left ankle
7
+ // 24 = right hip, 26 = right knee, 28 = right ankle
8
+
9
+ export const DOWNWARD_DOG_CONFIG = {
10
+ name: 'Downward Dog (Adho Mukha Svanasana)',
11
+ type: 'hold',
12
+ angles: [{
13
+ name: 'hipAngle',
14
+ landmarkA: 11,
15
+ // left shoulder
16
+ landmarkB: 23,
17
+ // left hip (vertex)
18
+ landmarkC: 27 // left ankle
19
+ }, {
20
+ name: 'leftArm',
21
+ landmarkA: 11,
22
+ // left shoulder
23
+ landmarkB: 13,
24
+ // left elbow (vertex)
25
+ landmarkC: 15 // left wrist
26
+ }, {
27
+ name: 'leftLeg',
28
+ landmarkA: 23,
29
+ // left hip
30
+ landmarkB: 25,
31
+ // left knee (vertex)
32
+ landmarkC: 27 // left ankle
33
+ }],
34
+ phases: [{
35
+ phase: 'hold',
36
+ angleName: 'hipAngle',
37
+ minAngle: 55,
38
+ maxAngle: 100
39
+ }],
40
+ repSequence: [],
41
+ formRules: [{
42
+ name: 'armsBent',
43
+ message: 'Straighten your arms — push the floor away',
44
+ severity: 'warning',
45
+ angleName: 'leftArm',
46
+ minAngle: 160,
47
+ maxAngle: 180
48
+ }, {
49
+ name: 'legsBent',
50
+ message: 'Straighten your legs — press your heels toward the ground',
51
+ severity: 'info',
52
+ angleName: 'leftLeg',
53
+ minAngle: 155,
54
+ maxAngle: 180
55
+ }, {
56
+ name: 'hipsTooLow',
57
+ message: 'Push your hips up higher toward the ceiling',
58
+ severity: 'warning',
59
+ angleName: 'hipAngle',
60
+ minAngle: 55,
61
+ maxAngle: 100
62
+ }],
63
+ holdDurationMs: 30000 // 30 seconds
64
+ };
65
+ //# sourceMappingURL=downward-dog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["DOWNWARD_DOG_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/downward-dog.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,mBAAmC,GAAG;EACjDC,IAAI,EAAE,qCAAqC;EAC3CC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,UAAU;IAChBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,4CAA4C;IACrDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,2DAA2D;IACpEC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,YAAY;IAClBa,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ // MediaPipe Pose Landmark indices
4
+ // 23 = left hip, 25 = left knee, 27 = left ankle
5
+ // 24 = right hip, 26 = right knee, 28 = right ankle
6
+ // 11 = left shoulder, 12 = right shoulder
7
+
8
+ export const LUNGE_CONFIG = {
9
+ name: 'Lunge',
10
+ type: 'rep',
11
+ angles: [{
12
+ name: 'frontKnee',
13
+ landmarkA: 23,
14
+ // left hip
15
+ landmarkB: 25,
16
+ // left knee (vertex)
17
+ landmarkC: 27 // left ankle
18
+ }, {
19
+ name: 'backKnee',
20
+ landmarkA: 24,
21
+ // right hip
22
+ landmarkB: 26,
23
+ // right knee (vertex)
24
+ landmarkC: 28 // right ankle
25
+ }, {
26
+ name: 'torsoAngle',
27
+ landmarkA: 11,
28
+ // left shoulder
29
+ landmarkB: 23,
30
+ // left hip (vertex)
31
+ landmarkC: 25 // left knee
32
+ }],
33
+ phases: [{
34
+ phase: 'up',
35
+ angleName: 'frontKnee',
36
+ minAngle: 155,
37
+ maxAngle: 180
38
+ }, {
39
+ phase: 'down',
40
+ angleName: 'frontKnee',
41
+ minAngle: 70,
42
+ maxAngle: 110
43
+ }],
44
+ repSequence: ['up', 'down', 'up'],
45
+ formRules: [{
46
+ name: 'kneeOverToe',
47
+ message: "Don't let your front knee go past your toes",
48
+ severity: 'warning',
49
+ angleName: 'frontKnee',
50
+ minAngle: 75,
51
+ maxAngle: 180
52
+ }, {
53
+ name: 'torsoLean',
54
+ message: 'Keep your torso upright',
55
+ severity: 'warning',
56
+ angleName: 'torsoAngle',
57
+ minAngle: 75,
58
+ maxAngle: 180
59
+ }],
60
+ holdDurationMs: 0
61
+ };
62
+ //# sourceMappingURL=lunge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["LUNGE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/lunge.ts"],"mappings":";;AAEA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,YAA4B,GAAG;EAC1CC,IAAI,EAAE,OAAO;EACbC,IAAI,EAAE,KAAK;EACXC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,WAAW;IACjBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,UAAU;IAChBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,YAAY;IAClBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,IAAI;IACXC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEH,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;EACjCC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,aAAa;IACnBa,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,WAAW;IACjBa,OAAO,EAAE,yBAAyB;IAClCC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,YAAY;IACvBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}