@pose-tracker/pose-estimation-web 0.3.4 → 0.3.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.
package/README.md CHANGED
@@ -45,6 +45,39 @@ Full tables (RN parity, CORS, React): monorepo
45
45
  [`docs/MEDIA_SOURCES.md`](../../docs/MEDIA_SOURCES.md) · public
46
46
  https://docs.posetracker.com/media-sources
47
47
 
48
+ ## External frames (your camera, our data)
49
+
50
+ Full guide (`getUserMedia`, React, native cameras): https://docs.posetracker.com/external-frames
51
+
52
+ Optional. Use it when your page already owns the camera or the frames (your
53
+ own `<video>`, WebRTC, a canvas pipeline) and you only need the data. No
54
+ `mount()`, no `getUserMedia`, and **nothing is drawn**. Pose estimation and
55
+ the exercise engine run on every frame, and every event you would get from
56
+ the camera flow is emitted: keypoints, posture/placement, counter, form score.
57
+
58
+ ```ts
59
+ const pt = createPoseTracker({ model: 'movenet' });
60
+ await pt.configure('YOUR_API_KEY'); // only needed for exercises
61
+ await pt.warmupExternal();
62
+ pt.startExercise('squat');
63
+ pt.on('counter', (e) => console.log(e.count)); // listeners still fire
64
+
65
+ // For each frame (ImageBitmap / <img> / <canvas> / <video>, or base64 / uri):
66
+ const { dropped, pose, events } = await pt.processFrame({
67
+ image: bitmap,
68
+ width: bitmap.width,
69
+ height: bitmap.height,
70
+ timestampMs: performance.now(),
71
+ mirrored: true, // front camera (default)
72
+ });
73
+ ```
74
+
75
+ - One frame in flight at a time: an overlapping call resolves at once with
76
+ `{ dropped: true, pose: lastPose, events: [] }`.
77
+ - Prefer `image` over `base64`, and keep frames small (longest side about 256 px).
78
+ - The engine is temporal: push frames in order and keep the same session.
79
+ - Don't mix the two flows. `processFrame` throws while `start()` is running.
80
+
48
81
  ## Script tag (CDN)
49
82
 
50
83
  The IIFE build exposes a **`PoseTracker`** global (`PoseTracker.createPoseTracker`, …).
package/dist/index.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  var jsSha256 = require('js-sha256');
4
4
 
5
5
  // src/version.ts
6
- var SDK_VERSION = "0.3.4";
6
+ var SDK_VERSION = "0.3.6";
7
7
  var SDK_NAME = "posetracker-web";
8
8
 
9
9
  // src/api/configure.ts
@@ -1679,12 +1679,20 @@ var SHELL_CSS = `
1679
1679
  }
1680
1680
  .pt-hud.debug { display: block; }
1681
1681
  .pt-placement {
1682
- position: absolute; z-index: 2; pointer-events: none; display: none;
1683
- border: 2px dashed rgba(255, 195, 0, 0.85);
1684
- box-sizing: border-box;
1685
- border-radius: 4px;
1682
+ position: absolute; inset: 0; z-index: 2; pointer-events: none; display: none;
1686
1683
  }
1687
1684
  .pt-placement.show { display: block; }
1685
+ .pt-placement::before {
1686
+ content: "";
1687
+ position: absolute;
1688
+ left: var(--pt-pad, 10%);
1689
+ top: var(--pt-pad, 10%);
1690
+ right: var(--pt-pad, 10%);
1691
+ bottom: var(--pt-pad, 10%);
1692
+ box-sizing: border-box;
1693
+ border: 2px solid #4DD21D;
1694
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
1695
+ }
1688
1696
  `;
1689
1697
  var styleInjected = false;
1690
1698
  function ensureStyles() {
@@ -1772,10 +1780,7 @@ function mountDomShell(host, options = {}) {
1772
1780
  placement.classList.toggle("show", visible);
1773
1781
  if (visible) {
1774
1782
  const p = Math.max(0, Math.min(40, paddingPercent));
1775
- placement.style.left = `${p}%`;
1776
- placement.style.top = `${p}%`;
1777
- placement.style.right = `${p}%`;
1778
- placement.style.bottom = `${p}%`;
1783
+ placement.style.setProperty("--pt-pad", `${p}%`);
1779
1784
  }
1780
1785
  },
1781
1786
  applyMirror(facingMode) {
@@ -1955,6 +1960,19 @@ function median(arr) {
1955
1960
  const s = [...arr].sort((a, b) => a - b);
1956
1961
  return s[Math.floor(s.length / 2)] ?? null;
1957
1962
  }
1963
+ async function decodeExternalFrame(frame) {
1964
+ const url = frame.base64 ? `data:${frame.mime ?? "image/jpeg"};base64,${frame.base64}` : frame.uri;
1965
+ if (!url) throw new Error("processFrame: pass image, base64, or uri.");
1966
+ const res = await fetch(url);
1967
+ if (!res.ok) throw new Error(`processFrame: could not load frame (HTTP ${res.status}).`);
1968
+ return createImageBitmap(await res.blob());
1969
+ }
1970
+ function frameInputSize(input) {
1971
+ if (typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) {
1972
+ return { width: input.width, height: input.height };
1973
+ }
1974
+ return mediaSize(input);
1975
+ }
1958
1976
  function facingFromOptions(opts) {
1959
1977
  if (opts.facingMode) return opts.facingMode;
1960
1978
  if (opts.position === "back") return "environment";
@@ -2006,6 +2024,11 @@ var PoseTrackerClient = class {
2006
2024
  this.inferMs = [];
2007
2025
  this.fpsWindow = 0;
2008
2026
  this.fpsTimer = 0;
2027
+ /** External frames (opt-in): set by warmupExternal(), never by start(). */
2028
+ this.externalReady = false;
2029
+ this.lastExternalPose = null;
2030
+ /** Events emitted while one external frame is in flight. */
2031
+ this.externalCollector = null;
2009
2032
  const token = apiToken ?? (typeof options.apiToken === "string" ? options.apiToken : null) ?? null;
2010
2033
  this.opts = { ...options, onDiagnostic: options.onDiagnostic };
2011
2034
  this.apiToken = token;
@@ -2200,6 +2223,84 @@ var PoseTrackerClient = class {
2200
2223
  }
2201
2224
  return this.runOneFrame();
2202
2225
  }
2226
+ /**
2227
+ * Opt-in: load the pose model for frames from your own camera. No
2228
+ * `mount()`, no camera, nothing drawn. Idempotent.
2229
+ */
2230
+ async warmupExternal() {
2231
+ if (this.disposed) throw new Error("PoseTracker disposed");
2232
+ if (this.preloadPromise) {
2233
+ await this.preloadPromise;
2234
+ } else {
2235
+ await this.preload({ coldStart: "basic" });
2236
+ }
2237
+ await this.ensureAdapter();
2238
+ if (!this.externalReady) {
2239
+ this.externalReady = true;
2240
+ void this.handleCameraStart({
2241
+ backend: this.acceleration.backend ?? "webgl",
2242
+ profileId: "external-frames"
2243
+ });
2244
+ }
2245
+ }
2246
+ /**
2247
+ * Infer one frame from your own camera and run the active exercise engine
2248
+ * on the same session. Resolves with the pose and the events this frame
2249
+ * produced; those events also reach `on()` listeners. Nothing is drawn.
2250
+ * One frame in flight: a call made while one is running resolves
2251
+ * `{ dropped: true }` at once.
2252
+ */
2253
+ async processFrame(frame) {
2254
+ if (this.disposed) throw new Error("PoseTracker disposed");
2255
+ if (!this.externalReady || !this.adapter) {
2256
+ throw new Error("processFrame: call warmupExternal() and wait for it to resolve first.");
2257
+ }
2258
+ if (this.running || this.stream) {
2259
+ throw new Error(
2260
+ "processFrame: the SDK camera or a media source is running on this client. External frames and start() cannot run together."
2261
+ );
2262
+ }
2263
+ if (this.busy) {
2264
+ return { dropped: true, pose: this.lastExternalPose, events: [] };
2265
+ }
2266
+ this.busy = true;
2267
+ const events = [];
2268
+ this.externalCollector = events;
2269
+ let decoded = null;
2270
+ try {
2271
+ let input;
2272
+ if (frame.image) {
2273
+ input = frame.image;
2274
+ } else {
2275
+ decoded = await decodeExternalFrame(frame);
2276
+ input = decoded;
2277
+ }
2278
+ const size = frameInputSize(input);
2279
+ if (!(size.width > 0 && size.height > 0)) {
2280
+ throw new Error("processFrame: frame has no pixels.");
2281
+ }
2282
+ const result = await this.adapter.estimate(input, {
2283
+ facingMode: frame.mirrored === false ? "environment" : "user",
2284
+ displayWidth: size.width,
2285
+ displayHeight: size.height,
2286
+ temporalSmooth: true
2287
+ });
2288
+ if (!result) return { dropped: false, pose: null, events };
2289
+ const pose = {
2290
+ keypoints: result.keypoints,
2291
+ score: result.score,
2292
+ timestampMs: typeof frame.timestampMs === "number" ? frame.timestampMs : Date.now()
2293
+ };
2294
+ this.lastExternalPose = pose;
2295
+ this.ingestPose(pose);
2296
+ this.recordInference(result, size);
2297
+ return { dropped: false, pose, events };
2298
+ } finally {
2299
+ decoded?.close();
2300
+ this.externalCollector = null;
2301
+ this.busy = false;
2302
+ }
2303
+ }
2203
2304
  stop() {
2204
2305
  this.running = false;
2205
2306
  this.loopToken += 1;
@@ -2251,6 +2352,9 @@ var PoseTrackerClient = class {
2251
2352
  this.lastCameraStartInfo = null;
2252
2353
  this.featureGateReported = { unsupported: false, freeBlock: false, missingToken: false };
2253
2354
  this.keypointsSuppressionLogged = false;
2355
+ this.externalReady = false;
2356
+ this.lastExternalPose = null;
2357
+ this.externalCollector = null;
2254
2358
  this.engine = null;
2255
2359
  this.engineSource = null;
2256
2360
  this.manifest = null;
@@ -2801,39 +2905,45 @@ var PoseTrackerClient = class {
2801
2905
  timestampMs: Date.now()
2802
2906
  };
2803
2907
  this.ingestPose(pose);
2908
+ this.recordInference(
2909
+ result,
2910
+ mediaSize(input)
2911
+ );
2912
+ this.revealCamera();
2913
+ return pose;
2914
+ }
2915
+ /** Inference timing + the 1 Hz `stats` event (camera and external frames). */
2916
+ recordInference(result, size) {
2804
2917
  this.inferMs.push(result.inferenceMs);
2805
2918
  if (this.inferMs.length > 30) this.inferMs.shift();
2806
2919
  this.fpsWindow += 1;
2807
2920
  const now = performance.now();
2808
- if (now - this.fpsTimer >= 1e3) {
2809
- const fps = this.fpsWindow;
2810
- this.fpsWindow = 0;
2811
- this.fpsTimer = now;
2812
- const med = median(this.inferMs);
2813
- this.acceleration.medianInferenceMs = med;
2814
- const above = result.keypoints.filter((k) => k.score >= 0.3).length;
2815
- const backend = "getBackend" in this.adapter && typeof this.adapter.getBackend === "function" ? this.adapter.getBackend() : null;
2816
- this.acceleration.backend = backend;
2817
- const size = mediaSize(input);
2818
- if (this.opts.debugHud) {
2819
- this.shell.setHud(
2820
- `${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
2821
- true
2822
- );
2823
- }
2824
- this.emit({
2825
- type: "stats",
2826
- fps,
2827
- medianInferenceMs: med,
2828
- backend,
2829
- keypointsAbove03: above,
2830
- meanScore: result.score,
2831
- videoSize: `${size.width}x${size.height}`,
2832
- timestampMs: Date.now()
2833
- });
2921
+ if (now - this.fpsTimer < 1e3) return;
2922
+ const fps = this.fpsWindow;
2923
+ this.fpsWindow = 0;
2924
+ this.fpsTimer = now;
2925
+ const med = median(this.inferMs);
2926
+ this.acceleration.medianInferenceMs = med;
2927
+ const above = result.keypoints.filter((k) => k.score >= 0.3).length;
2928
+ const adapter = this.adapter;
2929
+ const backend = adapter && "getBackend" in adapter && typeof adapter.getBackend === "function" ? adapter.getBackend() : null;
2930
+ this.acceleration.backend = backend;
2931
+ if (this.opts.debugHud && this.shell) {
2932
+ this.shell.setHud(
2933
+ `${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
2934
+ true
2935
+ );
2834
2936
  }
2835
- this.revealCamera();
2836
- return pose;
2937
+ this.emit({
2938
+ type: "stats",
2939
+ fps,
2940
+ medianInferenceMs: med,
2941
+ backend,
2942
+ keypointsAbove03: above,
2943
+ meanScore: result.score,
2944
+ videoSize: `${size.width}x${size.height}`,
2945
+ timestampMs: Date.now()
2946
+ });
2837
2947
  }
2838
2948
  async loop(token) {
2839
2949
  if (token !== this.loopToken || !this.running || !this.shell || this.disposed) {
@@ -3182,6 +3292,7 @@ var PoseTrackerClient = class {
3182
3292
  });
3183
3293
  }
3184
3294
  emit(event) {
3295
+ this.externalCollector?.push(event);
3185
3296
  this.listeners.forEach((l) => {
3186
3297
  try {
3187
3298
  l(event);
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _tensorflow_tfjs from '@tensorflow/tfjs';
2
2
 
3
3
  /** Bumped with package.json version (keep in sync). */
4
- declare const SDK_VERSION = "0.3.4";
4
+ declare const SDK_VERSION = "0.3.6";
5
5
  declare const SDK_NAME: "posetracker-web";
6
6
 
7
7
  /**
@@ -1047,6 +1047,48 @@ interface SkeletonDefinition {
1047
1047
  /** PoseTracker API default (navy `#010A73` + gold `#FFC300`). */
1048
1048
  declare const DEFAULT_SKELETON_DEFINITION: SkeletonDefinition;
1049
1049
 
1050
+ /**
1051
+ * External frames: the host page owns the camera and the preview, and pushes
1052
+ * frames to {@link PoseTrackerClient.processFrame}. The SDK infers, runs the
1053
+ * active exercise engine, and returns data. It mounts and draws nothing.
1054
+ */
1055
+
1056
+ interface ExternalFrame {
1057
+ /** Decoded frame, used as is. Takes precedence over `base64` / `uri`. */
1058
+ image?: ImageBitmap | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement;
1059
+ /** Encoded image (JPEG / PNG) without the `data:` prefix. */
1060
+ base64?: string;
1061
+ /** Image URL (`blob:`, `data:`, or same-origin / CORS-enabled `https:`). */
1062
+ uri?: string;
1063
+ /** Width of the frame you send. Keep the longest side at 256 px or less. */
1064
+ width: number;
1065
+ height: number;
1066
+ /** Capture time of the frame. The exercise engine uses it for timing. */
1067
+ timestampMs: number;
1068
+ /**
1069
+ * Mirror keypoints horizontally, as for a selfie preview. Default `true`.
1070
+ * Pass `false` for a back camera or an unmirrored preview.
1071
+ */
1072
+ mirrored?: boolean;
1073
+ /** MIME type of `base64`. Default `image/jpeg`. */
1074
+ mime?: string;
1075
+ }
1076
+ interface ExternalFrameResult {
1077
+ /** True when a previous frame was still running. Nothing was inferred. */
1078
+ dropped: boolean;
1079
+ /**
1080
+ * Pose for this frame, or the last pose when `dropped`. Keypoints are
1081
+ * normalized 0–1 to the frame you sent.
1082
+ */
1083
+ pose: Pose | null;
1084
+ /**
1085
+ * Events produced by this frame (keypoints, counter, posture, angles,
1086
+ * form score, …), in emission order. They are also delivered to `on()`
1087
+ * listeners. Empty when `dropped`.
1088
+ */
1089
+ events: PoseTrackerEvent[];
1090
+ }
1091
+
1050
1092
  /**
1051
1093
  * Browser PoseTracker client — DOM camera + TF.js keypoints + remote engine.
1052
1094
  * Public surface mirrors RN light `PoseTrackerClient` where the browser allows.
@@ -1146,6 +1188,11 @@ declare class PoseTrackerClient {
1146
1188
  private inferMs;
1147
1189
  private fpsWindow;
1148
1190
  private fpsTimer;
1191
+ /** External frames (opt-in): set by warmupExternal(), never by start(). */
1192
+ private externalReady;
1193
+ private lastExternalPose;
1194
+ /** Events emitted while one external frame is in flight. */
1195
+ private externalCollector;
1149
1196
  constructor(apiToken?: string, options?: PoseTrackerClientOptions);
1150
1197
  getStatus(): PoseTrackerStatus;
1151
1198
  getMode(): PoseTrackerMode;
@@ -1190,6 +1237,19 @@ declare class PoseTrackerClient {
1190
1237
  * while the loop is already running — for those, call {@link start}).
1191
1238
  */
1192
1239
  analyze(): Promise<Pose | null>;
1240
+ /**
1241
+ * Opt-in: load the pose model for frames from your own camera. No
1242
+ * `mount()`, no camera, nothing drawn. Idempotent.
1243
+ */
1244
+ warmupExternal(): Promise<void>;
1245
+ /**
1246
+ * Infer one frame from your own camera and run the active exercise engine
1247
+ * on the same session. Resolves with the pose and the events this frame
1248
+ * produced; those events also reach `on()` listeners. Nothing is drawn.
1249
+ * One frame in flight: a call made while one is running resolves
1250
+ * `{ dropped: true }` at once.
1251
+ */
1252
+ processFrame(frame: ExternalFrame): Promise<ExternalFrameResult>;
1193
1253
  stop(): void;
1194
1254
  private stopMediaOnly;
1195
1255
  dispose(): Promise<void>;
@@ -1217,6 +1277,8 @@ declare class PoseTrackerClient {
1217
1277
  private beginLoop;
1218
1278
  private frameReady;
1219
1279
  private runOneFrame;
1280
+ /** Inference timing + the 1 Hz `stats` event (camera and external frames). */
1281
+ private recordInference;
1220
1282
  private loop;
1221
1283
  private resolveSkeletonFromOptions;
1222
1284
  private resolveManifestOnce;
@@ -1517,4 +1579,4 @@ declare function findExerciseByIdOrAlias<T extends {
1517
1579
  id: string;
1518
1580
  }>(exerciseId: string, exercises: readonly T[]): T | undefined;
1519
1581
 
1520
- export { type AccelerationDiagnostics, type AccelerationState, type AngleValue, type AnglesEvent, BRAND_COLORS, BlazePoseAdapter, type BlazePoseModelType, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, type CameraFacingMode, type CameraOptions, type CameraPoseSource, type ClassicMessageListener, type ClassicNativeMessage, type CocoKeypointName, type ColdStartMode, ConfigureError, type ConfigureOptions, type ConfigureRequest, type CounterEvent, type CounterFormScore, type CustomExerciseDescriptor, type CustomSessionOptions, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, type DetectorFrameResult, type DiagnosticListener, EXERCISE_ALIASES, type EngineBundleDescriptor, type EngineChannel, type EngineLoadResult, EngineLoader, type EngineLoaderOptions, type EngineSession, type EngineSessionFeatures, type EngineSessionOptions, type ErrorEvent, type EventSink, type ExerciseConfig, type ExerciseSummaryEvent, FREE_PLAN_FEATURES_MESSAGE, type FetchSkeletonOptions, type FileStore, type FormGrade, type FormScoreEvent, INVALID_TOKEN_MESSAGE, type ImagePoseSource, type InferenceRuntime, type InitialPosture, type InitializationEvent, type InitializationStep, type JumpCalibrationEvent, type JumpDiscardedEvent, type JumpHeightEvent, type JumpResultEvent, type JumpStartedEvent, type JumpSummaryEntry, type JumpSummaryEvent, type KeyValueStore, type Keypoint, type KeypointName, type KeypointsEvent, type MinGrade, type ModelDescriptor, type ModelFormat, type ModelsByProfile, MoveNetGraphAdapter, type MovementAnalysis, type MovementDefinition, type MovementRecommendation, type MovementStep, type MovementStepParameter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PRODUCTION_SQUAT_IDS, type PerformanceWarningEvent, type PlanInfo, type Pose, PoseCamera, PoseCamera_mount, type PoseDetectionModule, type PoseDetectorAdapter, type PoseEstimateInput, type PoseModelAlias, type PoseModelKind, type PoseModelProfile, type PoseRuntimeDescriptor, type PoseRuntimePartDescriptor, type PoseSource, type PoseSourceProps, type PoseSourceType, type PoseTracker, type PoseTrackerCallbacks, PoseTrackerClient, type PoseTrackerClientOptions, type PoseTrackerEngine, type PoseTrackerEvent, type PoseTrackerEventListener, type PoseTrackerEventType, type PoseTrackerFeatures, type PoseTrackerMode, type PoseTrackerOptions, type PoseTrackerStatus, type PostureEvent, type PreloadOptions, type ProgressionEvent, type QualityChangedEvent, type RecommendationsEvent, type ReferenceMovementDescriptor, type RepSummary, type ResolvePoseModelOptions, type ResolvedFeatures, type ResolvedPoseModel, type RuntimeDownloadProgressEvent, SDK_NAME, SDK_VERSION, type SdkManifest, type SkeletonAnglesStyle, type SkeletonCirclesStyle, type SkeletonDefinition, SkeletonFetchError, type SkeletonLinesStyle, type StartExerciseOptions, type StatsEvent, type TfModule, type TfjsCdnUrls, TrackError, type TrackRequest, type TrackResponse, UsageTracker, type UsageTrackerOptions, V4_ONLY_EXERCISE_IDS, type VideoPoseSource, type VisibleHipsInfo, type WarningEvent, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, isProductionSquatId, mapVideoKeypointsToDisplay, mountDomShell, normalizeEngineChannel, normalizePoseSource, openCameraStream, requiresEngineV4, resolveExerciseName, resolveFeatures, resolveMovementEngine, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
1582
+ export { type AccelerationDiagnostics, type AccelerationState, type AngleValue, type AnglesEvent, BRAND_COLORS, BlazePoseAdapter, type BlazePoseModelType, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, type CameraFacingMode, type CameraOptions, type CameraPoseSource, type ClassicMessageListener, type ClassicNativeMessage, type CocoKeypointName, type ColdStartMode, ConfigureError, type ConfigureOptions, type ConfigureRequest, type CounterEvent, type CounterFormScore, type CustomExerciseDescriptor, type CustomSessionOptions, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, type DetectorFrameResult, type DiagnosticListener, EXERCISE_ALIASES, type EngineBundleDescriptor, type EngineChannel, type EngineLoadResult, EngineLoader, type EngineLoaderOptions, type EngineSession, type EngineSessionFeatures, type EngineSessionOptions, type ErrorEvent, type EventSink, type ExerciseConfig, type ExerciseSummaryEvent, type ExternalFrame, type ExternalFrameResult, FREE_PLAN_FEATURES_MESSAGE, type FetchSkeletonOptions, type FileStore, type FormGrade, type FormScoreEvent, INVALID_TOKEN_MESSAGE, type ImagePoseSource, type InferenceRuntime, type InitialPosture, type InitializationEvent, type InitializationStep, type JumpCalibrationEvent, type JumpDiscardedEvent, type JumpHeightEvent, type JumpResultEvent, type JumpStartedEvent, type JumpSummaryEntry, type JumpSummaryEvent, type KeyValueStore, type Keypoint, type KeypointName, type KeypointsEvent, type MinGrade, type ModelDescriptor, type ModelFormat, type ModelsByProfile, MoveNetGraphAdapter, type MovementAnalysis, type MovementDefinition, type MovementRecommendation, type MovementStep, type MovementStepParameter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PRODUCTION_SQUAT_IDS, type PerformanceWarningEvent, type PlanInfo, type Pose, PoseCamera, PoseCamera_mount, type PoseDetectionModule, type PoseDetectorAdapter, type PoseEstimateInput, type PoseModelAlias, type PoseModelKind, type PoseModelProfile, type PoseRuntimeDescriptor, type PoseRuntimePartDescriptor, type PoseSource, type PoseSourceProps, type PoseSourceType, type PoseTracker, type PoseTrackerCallbacks, PoseTrackerClient, type PoseTrackerClientOptions, type PoseTrackerEngine, type PoseTrackerEvent, type PoseTrackerEventListener, type PoseTrackerEventType, type PoseTrackerFeatures, type PoseTrackerMode, type PoseTrackerOptions, type PoseTrackerStatus, type PostureEvent, type PreloadOptions, type ProgressionEvent, type QualityChangedEvent, type RecommendationsEvent, type ReferenceMovementDescriptor, type RepSummary, type ResolvePoseModelOptions, type ResolvedFeatures, type ResolvedPoseModel, type RuntimeDownloadProgressEvent, SDK_NAME, SDK_VERSION, type SdkManifest, type SkeletonAnglesStyle, type SkeletonCirclesStyle, type SkeletonDefinition, SkeletonFetchError, type SkeletonLinesStyle, type StartExerciseOptions, type StatsEvent, type TfModule, type TfjsCdnUrls, TrackError, type TrackRequest, type TrackResponse, UsageTracker, type UsageTrackerOptions, V4_ONLY_EXERCISE_IDS, type VideoPoseSource, type VisibleHipsInfo, type WarningEvent, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, isProductionSquatId, mapVideoKeypointsToDisplay, mountDomShell, normalizeEngineChannel, normalizePoseSource, openCameraStream, requiresEngineV4, resolveExerciseName, resolveFeatures, resolveMovementEngine, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _tensorflow_tfjs from '@tensorflow/tfjs';
2
2
 
3
3
  /** Bumped with package.json version (keep in sync). */
4
- declare const SDK_VERSION = "0.3.4";
4
+ declare const SDK_VERSION = "0.3.6";
5
5
  declare const SDK_NAME: "posetracker-web";
6
6
 
7
7
  /**
@@ -1047,6 +1047,48 @@ interface SkeletonDefinition {
1047
1047
  /** PoseTracker API default (navy `#010A73` + gold `#FFC300`). */
1048
1048
  declare const DEFAULT_SKELETON_DEFINITION: SkeletonDefinition;
1049
1049
 
1050
+ /**
1051
+ * External frames: the host page owns the camera and the preview, and pushes
1052
+ * frames to {@link PoseTrackerClient.processFrame}. The SDK infers, runs the
1053
+ * active exercise engine, and returns data. It mounts and draws nothing.
1054
+ */
1055
+
1056
+ interface ExternalFrame {
1057
+ /** Decoded frame, used as is. Takes precedence over `base64` / `uri`. */
1058
+ image?: ImageBitmap | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement;
1059
+ /** Encoded image (JPEG / PNG) without the `data:` prefix. */
1060
+ base64?: string;
1061
+ /** Image URL (`blob:`, `data:`, or same-origin / CORS-enabled `https:`). */
1062
+ uri?: string;
1063
+ /** Width of the frame you send. Keep the longest side at 256 px or less. */
1064
+ width: number;
1065
+ height: number;
1066
+ /** Capture time of the frame. The exercise engine uses it for timing. */
1067
+ timestampMs: number;
1068
+ /**
1069
+ * Mirror keypoints horizontally, as for a selfie preview. Default `true`.
1070
+ * Pass `false` for a back camera or an unmirrored preview.
1071
+ */
1072
+ mirrored?: boolean;
1073
+ /** MIME type of `base64`. Default `image/jpeg`. */
1074
+ mime?: string;
1075
+ }
1076
+ interface ExternalFrameResult {
1077
+ /** True when a previous frame was still running. Nothing was inferred. */
1078
+ dropped: boolean;
1079
+ /**
1080
+ * Pose for this frame, or the last pose when `dropped`. Keypoints are
1081
+ * normalized 0–1 to the frame you sent.
1082
+ */
1083
+ pose: Pose | null;
1084
+ /**
1085
+ * Events produced by this frame (keypoints, counter, posture, angles,
1086
+ * form score, …), in emission order. They are also delivered to `on()`
1087
+ * listeners. Empty when `dropped`.
1088
+ */
1089
+ events: PoseTrackerEvent[];
1090
+ }
1091
+
1050
1092
  /**
1051
1093
  * Browser PoseTracker client — DOM camera + TF.js keypoints + remote engine.
1052
1094
  * Public surface mirrors RN light `PoseTrackerClient` where the browser allows.
@@ -1146,6 +1188,11 @@ declare class PoseTrackerClient {
1146
1188
  private inferMs;
1147
1189
  private fpsWindow;
1148
1190
  private fpsTimer;
1191
+ /** External frames (opt-in): set by warmupExternal(), never by start(). */
1192
+ private externalReady;
1193
+ private lastExternalPose;
1194
+ /** Events emitted while one external frame is in flight. */
1195
+ private externalCollector;
1149
1196
  constructor(apiToken?: string, options?: PoseTrackerClientOptions);
1150
1197
  getStatus(): PoseTrackerStatus;
1151
1198
  getMode(): PoseTrackerMode;
@@ -1190,6 +1237,19 @@ declare class PoseTrackerClient {
1190
1237
  * while the loop is already running — for those, call {@link start}).
1191
1238
  */
1192
1239
  analyze(): Promise<Pose | null>;
1240
+ /**
1241
+ * Opt-in: load the pose model for frames from your own camera. No
1242
+ * `mount()`, no camera, nothing drawn. Idempotent.
1243
+ */
1244
+ warmupExternal(): Promise<void>;
1245
+ /**
1246
+ * Infer one frame from your own camera and run the active exercise engine
1247
+ * on the same session. Resolves with the pose and the events this frame
1248
+ * produced; those events also reach `on()` listeners. Nothing is drawn.
1249
+ * One frame in flight: a call made while one is running resolves
1250
+ * `{ dropped: true }` at once.
1251
+ */
1252
+ processFrame(frame: ExternalFrame): Promise<ExternalFrameResult>;
1193
1253
  stop(): void;
1194
1254
  private stopMediaOnly;
1195
1255
  dispose(): Promise<void>;
@@ -1217,6 +1277,8 @@ declare class PoseTrackerClient {
1217
1277
  private beginLoop;
1218
1278
  private frameReady;
1219
1279
  private runOneFrame;
1280
+ /** Inference timing + the 1 Hz `stats` event (camera and external frames). */
1281
+ private recordInference;
1220
1282
  private loop;
1221
1283
  private resolveSkeletonFromOptions;
1222
1284
  private resolveManifestOnce;
@@ -1517,4 +1579,4 @@ declare function findExerciseByIdOrAlias<T extends {
1517
1579
  id: string;
1518
1580
  }>(exerciseId: string, exercises: readonly T[]): T | undefined;
1519
1581
 
1520
- export { type AccelerationDiagnostics, type AccelerationState, type AngleValue, type AnglesEvent, BRAND_COLORS, BlazePoseAdapter, type BlazePoseModelType, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, type CameraFacingMode, type CameraOptions, type CameraPoseSource, type ClassicMessageListener, type ClassicNativeMessage, type CocoKeypointName, type ColdStartMode, ConfigureError, type ConfigureOptions, type ConfigureRequest, type CounterEvent, type CounterFormScore, type CustomExerciseDescriptor, type CustomSessionOptions, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, type DetectorFrameResult, type DiagnosticListener, EXERCISE_ALIASES, type EngineBundleDescriptor, type EngineChannel, type EngineLoadResult, EngineLoader, type EngineLoaderOptions, type EngineSession, type EngineSessionFeatures, type EngineSessionOptions, type ErrorEvent, type EventSink, type ExerciseConfig, type ExerciseSummaryEvent, FREE_PLAN_FEATURES_MESSAGE, type FetchSkeletonOptions, type FileStore, type FormGrade, type FormScoreEvent, INVALID_TOKEN_MESSAGE, type ImagePoseSource, type InferenceRuntime, type InitialPosture, type InitializationEvent, type InitializationStep, type JumpCalibrationEvent, type JumpDiscardedEvent, type JumpHeightEvent, type JumpResultEvent, type JumpStartedEvent, type JumpSummaryEntry, type JumpSummaryEvent, type KeyValueStore, type Keypoint, type KeypointName, type KeypointsEvent, type MinGrade, type ModelDescriptor, type ModelFormat, type ModelsByProfile, MoveNetGraphAdapter, type MovementAnalysis, type MovementDefinition, type MovementRecommendation, type MovementStep, type MovementStepParameter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PRODUCTION_SQUAT_IDS, type PerformanceWarningEvent, type PlanInfo, type Pose, PoseCamera, PoseCamera_mount, type PoseDetectionModule, type PoseDetectorAdapter, type PoseEstimateInput, type PoseModelAlias, type PoseModelKind, type PoseModelProfile, type PoseRuntimeDescriptor, type PoseRuntimePartDescriptor, type PoseSource, type PoseSourceProps, type PoseSourceType, type PoseTracker, type PoseTrackerCallbacks, PoseTrackerClient, type PoseTrackerClientOptions, type PoseTrackerEngine, type PoseTrackerEvent, type PoseTrackerEventListener, type PoseTrackerEventType, type PoseTrackerFeatures, type PoseTrackerMode, type PoseTrackerOptions, type PoseTrackerStatus, type PostureEvent, type PreloadOptions, type ProgressionEvent, type QualityChangedEvent, type RecommendationsEvent, type ReferenceMovementDescriptor, type RepSummary, type ResolvePoseModelOptions, type ResolvedFeatures, type ResolvedPoseModel, type RuntimeDownloadProgressEvent, SDK_NAME, SDK_VERSION, type SdkManifest, type SkeletonAnglesStyle, type SkeletonCirclesStyle, type SkeletonDefinition, SkeletonFetchError, type SkeletonLinesStyle, type StartExerciseOptions, type StatsEvent, type TfModule, type TfjsCdnUrls, TrackError, type TrackRequest, type TrackResponse, UsageTracker, type UsageTrackerOptions, V4_ONLY_EXERCISE_IDS, type VideoPoseSource, type VisibleHipsInfo, type WarningEvent, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, isProductionSquatId, mapVideoKeypointsToDisplay, mountDomShell, normalizeEngineChannel, normalizePoseSource, openCameraStream, requiresEngineV4, resolveExerciseName, resolveFeatures, resolveMovementEngine, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
1582
+ export { type AccelerationDiagnostics, type AccelerationState, type AngleValue, type AnglesEvent, BRAND_COLORS, BlazePoseAdapter, type BlazePoseModelType, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, type CameraFacingMode, type CameraOptions, type CameraPoseSource, type ClassicMessageListener, type ClassicNativeMessage, type CocoKeypointName, type ColdStartMode, ConfigureError, type ConfigureOptions, type ConfigureRequest, type CounterEvent, type CounterFormScore, type CustomExerciseDescriptor, type CustomSessionOptions, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, type DetectorFrameResult, type DiagnosticListener, EXERCISE_ALIASES, type EngineBundleDescriptor, type EngineChannel, type EngineLoadResult, EngineLoader, type EngineLoaderOptions, type EngineSession, type EngineSessionFeatures, type EngineSessionOptions, type ErrorEvent, type EventSink, type ExerciseConfig, type ExerciseSummaryEvent, type ExternalFrame, type ExternalFrameResult, FREE_PLAN_FEATURES_MESSAGE, type FetchSkeletonOptions, type FileStore, type FormGrade, type FormScoreEvent, INVALID_TOKEN_MESSAGE, type ImagePoseSource, type InferenceRuntime, type InitialPosture, type InitializationEvent, type InitializationStep, type JumpCalibrationEvent, type JumpDiscardedEvent, type JumpHeightEvent, type JumpResultEvent, type JumpStartedEvent, type JumpSummaryEntry, type JumpSummaryEvent, type KeyValueStore, type Keypoint, type KeypointName, type KeypointsEvent, type MinGrade, type ModelDescriptor, type ModelFormat, type ModelsByProfile, MoveNetGraphAdapter, type MovementAnalysis, type MovementDefinition, type MovementRecommendation, type MovementStep, type MovementStepParameter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PRODUCTION_SQUAT_IDS, type PerformanceWarningEvent, type PlanInfo, type Pose, PoseCamera, PoseCamera_mount, type PoseDetectionModule, type PoseDetectorAdapter, type PoseEstimateInput, type PoseModelAlias, type PoseModelKind, type PoseModelProfile, type PoseRuntimeDescriptor, type PoseRuntimePartDescriptor, type PoseSource, type PoseSourceProps, type PoseSourceType, type PoseTracker, type PoseTrackerCallbacks, PoseTrackerClient, type PoseTrackerClientOptions, type PoseTrackerEngine, type PoseTrackerEvent, type PoseTrackerEventListener, type PoseTrackerEventType, type PoseTrackerFeatures, type PoseTrackerMode, type PoseTrackerOptions, type PoseTrackerStatus, type PostureEvent, type PreloadOptions, type ProgressionEvent, type QualityChangedEvent, type RecommendationsEvent, type ReferenceMovementDescriptor, type RepSummary, type ResolvePoseModelOptions, type ResolvedFeatures, type ResolvedPoseModel, type RuntimeDownloadProgressEvent, SDK_NAME, SDK_VERSION, type SdkManifest, type SkeletonAnglesStyle, type SkeletonCirclesStyle, type SkeletonDefinition, SkeletonFetchError, type SkeletonLinesStyle, type StartExerciseOptions, type StatsEvent, type TfModule, type TfjsCdnUrls, TrackError, type TrackRequest, type TrackResponse, UsageTracker, type UsageTrackerOptions, V4_ONLY_EXERCISE_IDS, type VideoPoseSource, type VisibleHipsInfo, type WarningEvent, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, isProductionSquatId, mapVideoKeypointsToDisplay, mountDomShell, normalizeEngineChannel, normalizePoseSource, openCameraStream, requiresEngineV4, resolveExerciseName, resolveFeatures, resolveMovementEngine, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sha256 } from 'js-sha256';
2
2
 
3
3
  // src/version.ts
4
- var SDK_VERSION = "0.3.4";
4
+ var SDK_VERSION = "0.3.6";
5
5
  var SDK_NAME = "posetracker-web";
6
6
 
7
7
  // src/api/configure.ts
@@ -1677,12 +1677,20 @@ var SHELL_CSS = `
1677
1677
  }
1678
1678
  .pt-hud.debug { display: block; }
1679
1679
  .pt-placement {
1680
- position: absolute; z-index: 2; pointer-events: none; display: none;
1681
- border: 2px dashed rgba(255, 195, 0, 0.85);
1682
- box-sizing: border-box;
1683
- border-radius: 4px;
1680
+ position: absolute; inset: 0; z-index: 2; pointer-events: none; display: none;
1684
1681
  }
1685
1682
  .pt-placement.show { display: block; }
1683
+ .pt-placement::before {
1684
+ content: "";
1685
+ position: absolute;
1686
+ left: var(--pt-pad, 10%);
1687
+ top: var(--pt-pad, 10%);
1688
+ right: var(--pt-pad, 10%);
1689
+ bottom: var(--pt-pad, 10%);
1690
+ box-sizing: border-box;
1691
+ border: 2px solid #4DD21D;
1692
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
1693
+ }
1686
1694
  `;
1687
1695
  var styleInjected = false;
1688
1696
  function ensureStyles() {
@@ -1770,10 +1778,7 @@ function mountDomShell(host, options = {}) {
1770
1778
  placement.classList.toggle("show", visible);
1771
1779
  if (visible) {
1772
1780
  const p = Math.max(0, Math.min(40, paddingPercent));
1773
- placement.style.left = `${p}%`;
1774
- placement.style.top = `${p}%`;
1775
- placement.style.right = `${p}%`;
1776
- placement.style.bottom = `${p}%`;
1781
+ placement.style.setProperty("--pt-pad", `${p}%`);
1777
1782
  }
1778
1783
  },
1779
1784
  applyMirror(facingMode) {
@@ -1953,6 +1958,19 @@ function median(arr) {
1953
1958
  const s = [...arr].sort((a, b) => a - b);
1954
1959
  return s[Math.floor(s.length / 2)] ?? null;
1955
1960
  }
1961
+ async function decodeExternalFrame(frame) {
1962
+ const url = frame.base64 ? `data:${frame.mime ?? "image/jpeg"};base64,${frame.base64}` : frame.uri;
1963
+ if (!url) throw new Error("processFrame: pass image, base64, or uri.");
1964
+ const res = await fetch(url);
1965
+ if (!res.ok) throw new Error(`processFrame: could not load frame (HTTP ${res.status}).`);
1966
+ return createImageBitmap(await res.blob());
1967
+ }
1968
+ function frameInputSize(input) {
1969
+ if (typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) {
1970
+ return { width: input.width, height: input.height };
1971
+ }
1972
+ return mediaSize(input);
1973
+ }
1956
1974
  function facingFromOptions(opts) {
1957
1975
  if (opts.facingMode) return opts.facingMode;
1958
1976
  if (opts.position === "back") return "environment";
@@ -2004,6 +2022,11 @@ var PoseTrackerClient = class {
2004
2022
  this.inferMs = [];
2005
2023
  this.fpsWindow = 0;
2006
2024
  this.fpsTimer = 0;
2025
+ /** External frames (opt-in): set by warmupExternal(), never by start(). */
2026
+ this.externalReady = false;
2027
+ this.lastExternalPose = null;
2028
+ /** Events emitted while one external frame is in flight. */
2029
+ this.externalCollector = null;
2007
2030
  const token = apiToken ?? (typeof options.apiToken === "string" ? options.apiToken : null) ?? null;
2008
2031
  this.opts = { ...options, onDiagnostic: options.onDiagnostic };
2009
2032
  this.apiToken = token;
@@ -2198,6 +2221,84 @@ var PoseTrackerClient = class {
2198
2221
  }
2199
2222
  return this.runOneFrame();
2200
2223
  }
2224
+ /**
2225
+ * Opt-in: load the pose model for frames from your own camera. No
2226
+ * `mount()`, no camera, nothing drawn. Idempotent.
2227
+ */
2228
+ async warmupExternal() {
2229
+ if (this.disposed) throw new Error("PoseTracker disposed");
2230
+ if (this.preloadPromise) {
2231
+ await this.preloadPromise;
2232
+ } else {
2233
+ await this.preload({ coldStart: "basic" });
2234
+ }
2235
+ await this.ensureAdapter();
2236
+ if (!this.externalReady) {
2237
+ this.externalReady = true;
2238
+ void this.handleCameraStart({
2239
+ backend: this.acceleration.backend ?? "webgl",
2240
+ profileId: "external-frames"
2241
+ });
2242
+ }
2243
+ }
2244
+ /**
2245
+ * Infer one frame from your own camera and run the active exercise engine
2246
+ * on the same session. Resolves with the pose and the events this frame
2247
+ * produced; those events also reach `on()` listeners. Nothing is drawn.
2248
+ * One frame in flight: a call made while one is running resolves
2249
+ * `{ dropped: true }` at once.
2250
+ */
2251
+ async processFrame(frame) {
2252
+ if (this.disposed) throw new Error("PoseTracker disposed");
2253
+ if (!this.externalReady || !this.adapter) {
2254
+ throw new Error("processFrame: call warmupExternal() and wait for it to resolve first.");
2255
+ }
2256
+ if (this.running || this.stream) {
2257
+ throw new Error(
2258
+ "processFrame: the SDK camera or a media source is running on this client. External frames and start() cannot run together."
2259
+ );
2260
+ }
2261
+ if (this.busy) {
2262
+ return { dropped: true, pose: this.lastExternalPose, events: [] };
2263
+ }
2264
+ this.busy = true;
2265
+ const events = [];
2266
+ this.externalCollector = events;
2267
+ let decoded = null;
2268
+ try {
2269
+ let input;
2270
+ if (frame.image) {
2271
+ input = frame.image;
2272
+ } else {
2273
+ decoded = await decodeExternalFrame(frame);
2274
+ input = decoded;
2275
+ }
2276
+ const size = frameInputSize(input);
2277
+ if (!(size.width > 0 && size.height > 0)) {
2278
+ throw new Error("processFrame: frame has no pixels.");
2279
+ }
2280
+ const result = await this.adapter.estimate(input, {
2281
+ facingMode: frame.mirrored === false ? "environment" : "user",
2282
+ displayWidth: size.width,
2283
+ displayHeight: size.height,
2284
+ temporalSmooth: true
2285
+ });
2286
+ if (!result) return { dropped: false, pose: null, events };
2287
+ const pose = {
2288
+ keypoints: result.keypoints,
2289
+ score: result.score,
2290
+ timestampMs: typeof frame.timestampMs === "number" ? frame.timestampMs : Date.now()
2291
+ };
2292
+ this.lastExternalPose = pose;
2293
+ this.ingestPose(pose);
2294
+ this.recordInference(result, size);
2295
+ return { dropped: false, pose, events };
2296
+ } finally {
2297
+ decoded?.close();
2298
+ this.externalCollector = null;
2299
+ this.busy = false;
2300
+ }
2301
+ }
2201
2302
  stop() {
2202
2303
  this.running = false;
2203
2304
  this.loopToken += 1;
@@ -2249,6 +2350,9 @@ var PoseTrackerClient = class {
2249
2350
  this.lastCameraStartInfo = null;
2250
2351
  this.featureGateReported = { unsupported: false, freeBlock: false, missingToken: false };
2251
2352
  this.keypointsSuppressionLogged = false;
2353
+ this.externalReady = false;
2354
+ this.lastExternalPose = null;
2355
+ this.externalCollector = null;
2252
2356
  this.engine = null;
2253
2357
  this.engineSource = null;
2254
2358
  this.manifest = null;
@@ -2799,39 +2903,45 @@ var PoseTrackerClient = class {
2799
2903
  timestampMs: Date.now()
2800
2904
  };
2801
2905
  this.ingestPose(pose);
2906
+ this.recordInference(
2907
+ result,
2908
+ mediaSize(input)
2909
+ );
2910
+ this.revealCamera();
2911
+ return pose;
2912
+ }
2913
+ /** Inference timing + the 1 Hz `stats` event (camera and external frames). */
2914
+ recordInference(result, size) {
2802
2915
  this.inferMs.push(result.inferenceMs);
2803
2916
  if (this.inferMs.length > 30) this.inferMs.shift();
2804
2917
  this.fpsWindow += 1;
2805
2918
  const now = performance.now();
2806
- if (now - this.fpsTimer >= 1e3) {
2807
- const fps = this.fpsWindow;
2808
- this.fpsWindow = 0;
2809
- this.fpsTimer = now;
2810
- const med = median(this.inferMs);
2811
- this.acceleration.medianInferenceMs = med;
2812
- const above = result.keypoints.filter((k) => k.score >= 0.3).length;
2813
- const backend = "getBackend" in this.adapter && typeof this.adapter.getBackend === "function" ? this.adapter.getBackend() : null;
2814
- this.acceleration.backend = backend;
2815
- const size = mediaSize(input);
2816
- if (this.opts.debugHud) {
2817
- this.shell.setHud(
2818
- `${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
2819
- true
2820
- );
2821
- }
2822
- this.emit({
2823
- type: "stats",
2824
- fps,
2825
- medianInferenceMs: med,
2826
- backend,
2827
- keypointsAbove03: above,
2828
- meanScore: result.score,
2829
- videoSize: `${size.width}x${size.height}`,
2830
- timestampMs: Date.now()
2831
- });
2919
+ if (now - this.fpsTimer < 1e3) return;
2920
+ const fps = this.fpsWindow;
2921
+ this.fpsWindow = 0;
2922
+ this.fpsTimer = now;
2923
+ const med = median(this.inferMs);
2924
+ this.acceleration.medianInferenceMs = med;
2925
+ const above = result.keypoints.filter((k) => k.score >= 0.3).length;
2926
+ const adapter = this.adapter;
2927
+ const backend = adapter && "getBackend" in adapter && typeof adapter.getBackend === "function" ? adapter.getBackend() : null;
2928
+ this.acceleration.backend = backend;
2929
+ if (this.opts.debugHud && this.shell) {
2930
+ this.shell.setHud(
2931
+ `${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${backend ?? "?"} \xB7 ${fps} fps \xB7 ${med != null ? Math.round(med) + " ms" : "?"} \xB7 kp\u22650.3=${above}/17`,
2932
+ true
2933
+ );
2832
2934
  }
2833
- this.revealCamera();
2834
- return pose;
2935
+ this.emit({
2936
+ type: "stats",
2937
+ fps,
2938
+ medianInferenceMs: med,
2939
+ backend,
2940
+ keypointsAbove03: above,
2941
+ meanScore: result.score,
2942
+ videoSize: `${size.width}x${size.height}`,
2943
+ timestampMs: Date.now()
2944
+ });
2835
2945
  }
2836
2946
  async loop(token) {
2837
2947
  if (token !== this.loopToken || !this.running || !this.shell || this.disposed) {
@@ -3180,6 +3290,7 @@ var PoseTrackerClient = class {
3180
3290
  });
3181
3291
  }
3182
3292
  emit(event) {
3293
+ this.externalCollector?.push(event);
3183
3294
  this.listeners.forEach((l) => {
3184
3295
  try {
3185
3296
  l(event);
@@ -1,4 +1,4 @@
1
- "use strict";var PoseTracker=(()=>{var Ft=Object.create;var me=Object.defineProperty;var Ct=Object.getOwnPropertyDescriptor;var It=Object.getOwnPropertyNames;var Lt=Object.getPrototypeOf,At=Object.prototype.hasOwnProperty;var dt=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var Rt=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Dt=(t,e)=>{for(var r in e)me(t,r,{get:e[r],enumerable:!0})},ht=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of It(e))!At.call(t,i)&&i!==r&&me(t,i,{get:()=>e[i],enumerable:!(s=Ct(e,i))||s.enumerable});return t};var ut=(t,e,r)=>(r=t!=null?Ft(Lt(t)):{},ht(e||!t||!t.__esModule?me(r,"default",{value:t,enumerable:!0}):r,t)),Ot=t=>ht(me({},"__esModule",{value:!0}),t);var De=Rt((yr,fe)=>{"use strict";(function(){"use strict";var t="input is invalid type",e=typeof window=="object",r=e?window:{};r.JS_SHA256_NO_WINDOW&&(e=!1);var s=!e&&typeof self=="object",i=!r.JS_SHA256_NO_NODE_JS&&typeof process=="object"&&process.versions&&process.versions.node&&process.type!="renderer";i?r=global:s&&(r=self);var n=!r.JS_SHA256_NO_COMMON_JS&&typeof fe=="object"&&fe.exports,u=typeof define=="function"&&define.amd,p=!r.JS_SHA256_NO_ARRAY_BUFFER&&typeof ArrayBuffer<"u",l="0123456789abcdef".split(""),x=[-2147483648,8388608,32768,128],v=[24,16,8,0],_=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],w=["hex","array","digest","arrayBuffer"],g=[];(r.JS_SHA256_NO_NODE_JS||!Array.isArray)&&(Array.isArray=function(o){return Object.prototype.toString.call(o)==="[object Array]"}),p&&(r.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW||!ArrayBuffer.isView)&&(ArrayBuffer.isView=function(o){return typeof o=="object"&&o.buffer&&o.buffer.constructor===ArrayBuffer});var b=function(o,d){return function(f){return new k(d,!0).update(f)[o]()}},E=function(o){var d=b("hex",o);i&&(d=T(d,o)),d.create=function(){return new k(o)},d.update=function(m){return d.create().update(m)};for(var f=0;f<w.length;++f){var a=w[f];d[a]=b(a,o)}return d},T=function(o,d){var f=dt("crypto"),a=dt("buffer").Buffer,m=d?"sha224":"sha256",h;a.from&&!r.JS_SHA256_NO_BUFFER_FROM?h=a.from:h=function(c){return new a(c)};var y=function(c){if(typeof c=="string")return f.createHash(m).update(c,"utf8").digest("hex");if(c==null)throw new Error(t);return c.constructor===ArrayBuffer&&(c=new Uint8Array(c)),Array.isArray(c)||ArrayBuffer.isView(c)||c.constructor===a?f.createHash(m).update(h(c)).digest("hex"):o(c)};return y},C=function(o,d){return function(f,a){return new L(f,d,!0).update(a)[o]()}},F=function(o){var d=C("hex",o);d.create=function(m){return new L(m,o)},d.update=function(m,h){return d.create(m).update(h)};for(var f=0;f<w.length;++f){var a=w[f];d[a]=C(a,o)}return d};function k(o,d){d?(g[0]=g[16]=g[1]=g[2]=g[3]=g[4]=g[5]=g[6]=g[7]=g[8]=g[9]=g[10]=g[11]=g[12]=g[13]=g[14]=g[15]=0,this.blocks=g):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o?(this.h0=3238371032,this.h1=914150663,this.h2=812702999,this.h3=4144912697,this.h4=4290775857,this.h5=1750603025,this.h6=1694076839,this.h7=3204075428):(this.h0=1779033703,this.h1=3144134277,this.h2=1013904242,this.h3=2773480762,this.h4=1359893119,this.h5=2600822924,this.h6=528734635,this.h7=1541459225),this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0,this.is224=o}k.prototype.update=function(o){if(!this.finalized){var d,f=typeof o;if(f!=="string"){if(f==="object"){if(o===null)throw new Error(t);if(p&&o.constructor===ArrayBuffer)o=new Uint8Array(o);else if(!Array.isArray(o)&&(!p||!ArrayBuffer.isView(o)))throw new Error(t)}else throw new Error(t);d=!0}for(var a,m=0,h,y=o.length,c=this.blocks;m<y;){if(this.hashed&&(this.hashed=!1,c[0]=this.block,this.block=c[16]=c[1]=c[2]=c[3]=c[4]=c[5]=c[6]=c[7]=c[8]=c[9]=c[10]=c[11]=c[12]=c[13]=c[14]=c[15]=0),d)for(h=this.start;m<y&&h<64;++m)c[h>>>2]|=o[m]<<v[h++&3];else for(h=this.start;m<y&&h<64;++m)a=o.charCodeAt(m),a<128?c[h>>>2]|=a<<v[h++&3]:a<2048?(c[h>>>2]|=(192|a>>>6)<<v[h++&3],c[h>>>2]|=(128|a&63)<<v[h++&3]):a<55296||a>=57344?(c[h>>>2]|=(224|a>>>12)<<v[h++&3],c[h>>>2]|=(128|a>>>6&63)<<v[h++&3],c[h>>>2]|=(128|a&63)<<v[h++&3]):(a=65536+((a&1023)<<10|o.charCodeAt(++m)&1023),c[h>>>2]|=(240|a>>>18)<<v[h++&3],c[h>>>2]|=(128|a>>>12&63)<<v[h++&3],c[h>>>2]|=(128|a>>>6&63)<<v[h++&3],c[h>>>2]|=(128|a&63)<<v[h++&3]);this.lastByteIndex=h,this.bytes+=h-this.start,h>=64?(this.block=c[16],this.start=h-64,this.hash(),this.hashed=!0):this.start=h}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this}},k.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var o=this.blocks,d=this.lastByteIndex;o[16]=this.block,o[d>>>2]|=x[d&3],this.block=o[16],d>=56&&(this.hashed||this.hash(),o[0]=this.block,o[16]=o[1]=o[2]=o[3]=o[4]=o[5]=o[6]=o[7]=o[8]=o[9]=o[10]=o[11]=o[12]=o[13]=o[14]=o[15]=0),o[14]=this.hBytes<<3|this.bytes>>>29,o[15]=this.bytes<<3,this.hash()}},k.prototype.hash=function(){var o=this.h0,d=this.h1,f=this.h2,a=this.h3,m=this.h4,h=this.h5,y=this.h6,c=this.h7,S=this.blocks,P,R,D,H,M,B,V,Y,Ae,Re,pe;for(P=16;P<64;++P)M=S[P-15],R=(M>>>7|M<<25)^(M>>>18|M<<14)^M>>>3,M=S[P-2],D=(M>>>17|M<<15)^(M>>>19|M<<13)^M>>>10,S[P]=S[P-16]+R+S[P-7]+D<<0;for(pe=d&f,P=0;P<64;P+=4)this.first?(this.is224?(Y=300032,M=S[0]-1413257819,c=M-150054599<<0,a=M+24177077<<0):(Y=704751109,M=S[0]-210244248,c=M-1521486534<<0,a=M+143694565<<0),this.first=!1):(R=(o>>>2|o<<30)^(o>>>13|o<<19)^(o>>>22|o<<10),D=(m>>>6|m<<26)^(m>>>11|m<<21)^(m>>>25|m<<7),Y=o&d,H=Y^o&f^pe,V=m&h^~m&y,M=c+D+V+_[P]+S[P],B=R+H,c=a+M<<0,a=M+B<<0),R=(a>>>2|a<<30)^(a>>>13|a<<19)^(a>>>22|a<<10),D=(c>>>6|c<<26)^(c>>>11|c<<21)^(c>>>25|c<<7),Ae=a&o,H=Ae^a&d^Y,V=c&m^~c&h,M=y+D+V+_[P+1]+S[P+1],B=R+H,y=f+M<<0,f=M+B<<0,R=(f>>>2|f<<30)^(f>>>13|f<<19)^(f>>>22|f<<10),D=(y>>>6|y<<26)^(y>>>11|y<<21)^(y>>>25|y<<7),Re=f&a,H=Re^f&o^Ae,V=y&c^~y&m,M=h+D+V+_[P+2]+S[P+2],B=R+H,h=d+M<<0,d=M+B<<0,R=(d>>>2|d<<30)^(d>>>13|d<<19)^(d>>>22|d<<10),D=(h>>>6|h<<26)^(h>>>11|h<<21)^(h>>>25|h<<7),pe=d&f,H=pe^d&a^Re,V=h&y^~h&c,M=m+D+V+_[P+3]+S[P+3],B=R+H,m=o+M<<0,o=M+B<<0,this.chromeBugWorkAround=!0;this.h0=this.h0+o<<0,this.h1=this.h1+d<<0,this.h2=this.h2+f<<0,this.h3=this.h3+a<<0,this.h4=this.h4+m<<0,this.h5=this.h5+h<<0,this.h6=this.h6+y<<0,this.h7=this.h7+c<<0},k.prototype.hex=function(){this.finalize();var o=this.h0,d=this.h1,f=this.h2,a=this.h3,m=this.h4,h=this.h5,y=this.h6,c=this.h7,S=l[o>>>28&15]+l[o>>>24&15]+l[o>>>20&15]+l[o>>>16&15]+l[o>>>12&15]+l[o>>>8&15]+l[o>>>4&15]+l[o&15]+l[d>>>28&15]+l[d>>>24&15]+l[d>>>20&15]+l[d>>>16&15]+l[d>>>12&15]+l[d>>>8&15]+l[d>>>4&15]+l[d&15]+l[f>>>28&15]+l[f>>>24&15]+l[f>>>20&15]+l[f>>>16&15]+l[f>>>12&15]+l[f>>>8&15]+l[f>>>4&15]+l[f&15]+l[a>>>28&15]+l[a>>>24&15]+l[a>>>20&15]+l[a>>>16&15]+l[a>>>12&15]+l[a>>>8&15]+l[a>>>4&15]+l[a&15]+l[m>>>28&15]+l[m>>>24&15]+l[m>>>20&15]+l[m>>>16&15]+l[m>>>12&15]+l[m>>>8&15]+l[m>>>4&15]+l[m&15]+l[h>>>28&15]+l[h>>>24&15]+l[h>>>20&15]+l[h>>>16&15]+l[h>>>12&15]+l[h>>>8&15]+l[h>>>4&15]+l[h&15]+l[y>>>28&15]+l[y>>>24&15]+l[y>>>20&15]+l[y>>>16&15]+l[y>>>12&15]+l[y>>>8&15]+l[y>>>4&15]+l[y&15];return this.is224||(S+=l[c>>>28&15]+l[c>>>24&15]+l[c>>>20&15]+l[c>>>16&15]+l[c>>>12&15]+l[c>>>8&15]+l[c>>>4&15]+l[c&15]),S},k.prototype.toString=k.prototype.hex,k.prototype.digest=function(){this.finalize();var o=this.h0,d=this.h1,f=this.h2,a=this.h3,m=this.h4,h=this.h5,y=this.h6,c=this.h7,S=[o>>>24&255,o>>>16&255,o>>>8&255,o&255,d>>>24&255,d>>>16&255,d>>>8&255,d&255,f>>>24&255,f>>>16&255,f>>>8&255,f&255,a>>>24&255,a>>>16&255,a>>>8&255,a&255,m>>>24&255,m>>>16&255,m>>>8&255,m&255,h>>>24&255,h>>>16&255,h>>>8&255,h&255,y>>>24&255,y>>>16&255,y>>>8&255,y&255];return this.is224||S.push(c>>>24&255,c>>>16&255,c>>>8&255,c&255),S},k.prototype.array=k.prototype.digest,k.prototype.arrayBuffer=function(){this.finalize();var o=new ArrayBuffer(this.is224?28:32),d=new DataView(o);return d.setUint32(0,this.h0),d.setUint32(4,this.h1),d.setUint32(8,this.h2),d.setUint32(12,this.h3),d.setUint32(16,this.h4),d.setUint32(20,this.h5),d.setUint32(24,this.h6),this.is224||d.setUint32(28,this.h7),o};function L(o,d,f){var a,m=typeof o;if(m==="string"){var h=[],y=o.length,c=0,S;for(a=0;a<y;++a)S=o.charCodeAt(a),S<128?h[c++]=S:S<2048?(h[c++]=192|S>>>6,h[c++]=128|S&63):S<55296||S>=57344?(h[c++]=224|S>>>12,h[c++]=128|S>>>6&63,h[c++]=128|S&63):(S=65536+((S&1023)<<10|o.charCodeAt(++a)&1023),h[c++]=240|S>>>18,h[c++]=128|S>>>12&63,h[c++]=128|S>>>6&63,h[c++]=128|S&63);o=h}else if(m==="object"){if(o===null)throw new Error(t);if(p&&o.constructor===ArrayBuffer)o=new Uint8Array(o);else if(!Array.isArray(o)&&(!p||!ArrayBuffer.isView(o)))throw new Error(t)}else throw new Error(t);o.length>64&&(o=new k(d,!0).update(o).array());var P=[],R=[];for(a=0;a<64;++a){var D=o[a]||0;P[a]=92^D,R[a]=54^D}k.call(this,d,f),this.update(R),this.oKeyPad=P,this.inner=!0,this.sharedMemory=f}L.prototype=new k,L.prototype.finalize=function(){if(k.prototype.finalize.call(this),this.inner){this.inner=!1;var o=this.array();k.call(this,this.is224,this.sharedMemory),this.update(this.oKeyPad),this.update(o),k.prototype.finalize.call(this)}};var I=E();I.sha256=I,I.sha224=E(!0),I.sha256.hmac=F(),I.sha224.hmac=F(!0),n?fe.exports=I:(r.sha256=I.sha256,r.sha224=I.sha224,u&&define(function(){return I}))})()});var ur={};Dt(ur,{COCO_KEYPOINT_NAMES:()=>N,DEFAULT_MOVENET_LIGHTNING_URL:()=>Me,ONLINE_TFJS_VERSION:()=>be,PoseCamera:()=>ct,SDK_NAME:()=>X,SDK_VERSION:()=>K,configure:()=>G,createPoseTracker:()=>Le,defaultTfjsCdnUrls:()=>Je,resolvePoseModel:()=>z,shouldShowWatermark:()=>ue});var K="0.3.4",X="posetracker-web";var q="https://movelytics-strapi-c78a339b7070.herokuapp.com",U=class extends Error{constructor(r,s){super(s);this.code=r;this.name="ConfigureError"}};async function G(t,e={}){let r=(e.baseUrl??q).replace(/\/$/,""),s={...t?{apiToken:t}:{},sdkName:X,sdkVersion:K,targetPlatform:"web",poseModelProfile:e.poseModelProfile??"AdaptiveChoice",locale:e.locale,localVersions:e.localVersions,engineChannel:e.engine==="v3"?"v3":"v4"},i;try{i=await fetch(`${r}/api/sdk/configure`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}catch(n){throw new U("network",`Handshake request failed: ${String(n)}`)}if(i.status===401||i.status===403)throw new U("invalid_token","Invalid, revoked or unauthorized API token.");if(i.status===429)throw new U("quota_exceeded","API call quota exceeded for the current plan.");if(!i.ok)throw new U("internal",`Handshake failed with HTTP ${i.status}.`);return await i.json()}var Be=ut(De(),1);var Ue=ut(De(),1),Oe="ptc1:";function Ne(t){return(0,Ue.sha256)(`posetracker-sdk-cache:${t}`)}function Ut(t){let e=unescape(encodeURIComponent(t)),r=new Uint8Array(e.length);for(let s=0;s<e.length;s++)r[s]=e.charCodeAt(s);return r}function Nt(t){let e="";for(let r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return decodeURIComponent(escape(e))}function Ht(t){let e="";for(let r=0;r<t.length;r++)e+=t[r].toString(16).padStart(2,"0");return e}function Bt(t){if(t.length%2!==0||/[^0-9a-f]/.test(t))return null;let e=new Uint8Array(t.length/2);for(let r=0;r<e.length;r++)e[r]=parseInt(t.slice(r*2,r*2+2),16);return e}function pt(t,e){let r=new Uint8Array(t.length),s=0,i=0;for(;i<t.length;){let n=Ue.sha256.array(`${e}:${s++}`);for(let u=0;u<n.length&&i<t.length;u++,i++)r[i]=t[i]^n[u]}return r}function ge(t,e){return Oe+Ht(pt(Ut(t),e))}function ye(t,e){if(!t.startsWith(Oe))return null;let r=Bt(t.slice(Oe.length));if(!r)return null;try{return Nt(pt(r,e))}catch{return null}}var He="posetracker.web.";function Q(){return typeof localStorage>"u"?null:{async getItem(t){try{return localStorage.getItem(He+t)}catch{return null}},async setItem(t,e){try{localStorage.setItem(He+t,e)}catch{}},async removeItem(t){try{localStorage.removeItem(He+t)}catch{}}}}function ft(){let t=Q();return t?{read:e=>t.getItem(`file.${e}`),write:(e,r)=>t.setItem(`file.${e}`,r),remove:e=>t.removeItem(`file.${e}`)}:null}function gt(){let t=new Map;return{async read(e){return t.has(e)?t.get(e):null},async write(e,r){t.set(e,r)},async remove(e){t.delete(e)}}}function ve(){return ft()??gt()}var mt="posetracker.engine.guard.",J=class{constructor(e={}){this.lastError=null;this.files=e.fileStore!==void 0?e.fileStore:ve(),this.kv=e.keyValueStore!==void 0?e.keyValueStore:Q(),this.fetchFn=e.fetchFn??fetch,this.onDiagnostic=e.onDiagnostic,this.onDiagnostic?.(`[posetracker-web] EngineLoader fileStore=${this.files?"yes":"none"} kv=${this.kv?"localStorage":"none"}`)}async clearGuard(e){if(!e||!this.kv)return;let r=`${mt}${e.version}.${e.sha256}`;await this.kv.removeItem(r).catch(()=>{})}async load(e,r,s){if(this.lastError=null,!e)return this.lastError="manifest has no engine descriptor",null;let i=`${mt}${e.version}.${e.sha256}`;s?.forceRetry&&await this.kv?.removeItem(i).catch(()=>{});let n=await this.kv?.getItem(i);if(n==="probing"||n==="failed")return await this.kv?.setItem(i,"failed"),this.lastError=`engine crash-guard blocked version=${e.version} (state=${n}) \u2014 call configure() again to force retry`,this.onDiagnostic?.(`[posetracker-web] ${this.lastError}`),null;let u=`engine-${e.version}.sealed`,p=await this.files?.read(u);if(p!=null){let l=ye(p,r);if(l!==null&&(0,Be.sha256)(l)===e.sha256){let x=await this.evaluate(l,i);if(x)return this.onDiagnostic?.(`[posetracker-web] engine loaded from cache version=${e.version}`),{engine:x,source:"remote-cache"}}else this.onDiagnostic?.(`[posetracker-web] engine cache purged (unseal/integrity mismatch) key=${u}`),await this.files?.remove(u)}try{this.onDiagnostic?.(`[posetracker-web] downloading engine version=${e.version}\u2026`);let l=await this.fetchFn(e.signedUrl);if(!l.ok)throw new Error(`HTTP ${l.status}`);let x=await l.text(),v=(0,Be.sha256)(x);if(v!==e.sha256)throw new Error(`integrity check failed (got ${v.slice(0,12)}\u2026 expected ${e.sha256.slice(0,12)}\u2026, bytes=${x.length})`);let _=await this.evaluate(x,i);if(_){try{await this.files?.write(u,ge(x,r))}catch(w){this.onDiagnostic?.("[posetracker-web] engine cache write failed (non-fatal): "+(w instanceof Error?w.message:String(w)))}return this.onDiagnostic?.(`[posetracker-web] engine loaded from download version=${e.version} bytes=${x.length}`),{engine:_,source:"remote-download"}}}catch(l){this.lastError=l instanceof Error?l.message:String(l),this.onDiagnostic?.(`[posetracker-web] engine download/load failed: ${this.lastError}`)}return this.lastError||(this.lastError="engine evaluate returned null"),null}async evaluate(e,r){await this.kv?.setItem(r,"probing");try{let s={exports:{}};new Function("module","exports",e)(s,s.exports);let n=s.exports.createEngine;if(typeof n!="function")throw new Error(`engine bundle does not export createEngine() (got ${typeof n}, keys=${Object.keys(s.exports).join(",")})`);let u=n();return await this.kv?.setItem(r,"passed"),u}catch(s){return this.lastError="engine evaluate failed: "+(s instanceof Error?s.message:String(s)),this.onDiagnostic?.(`[posetracker-web] ${this.lastError}`),await this.kv?.setItem(r,"failed"),null}}};var Ve="posetracker.track.queue",Vt=100,O=class extends Error{constructor(r,s){super(s);this.code=r;this.name="TrackError"}},Z=class{constructor(e={}){this.baseUrl=(e.baseUrl??q).replace(/\/$/,""),this.kv=e.keyValueStore!==void 0?e.keyValueStore:Q(),this.fetchFn=e.fetchFn??fetch}async trackMetered(e){return this.post({event:e.event??"camera_start",apiToken:e.apiToken,platform:"web",sdkVersion:K,params:e.params})}async trackAnonymous(e={}){let r={event:e.event??"camera_start",platform:"web",sdkVersion:K,params:e.params,queuedAtMs:Date.now()};try{await this.post({...r}),this.flushQueue()}catch{await this.enqueue(r)}}async flushQueue(){let e=await this.readQueue();if(e.length===0)return;let r=[];for(let s of e)try{await this.post({...s,params:{...s.params??{},queuedAtMs:s.queuedAtMs}})}catch{r.push(s)}await this.writeQueue(r)}async post(e){let r;try{r=await this.fetchFn(`${this.baseUrl}/api/sdk/track`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch(s){throw new O("network",`Usage tracking request failed: ${String(s)}`)}if(r.status===401||r.status===403)throw new O("invalid_token","Invalid, revoked or unauthorized API token.");if(r.status===429)throw new O("quota_exceeded","API call quota exceeded for the current plan.");if(!r.ok)throw new O("internal",`Usage tracking failed with HTTP ${r.status}.`);return await r.json()}async readQueue(){try{let e=await this.kv?.getItem(Ve);if(!e)return[];let r=JSON.parse(e);return Array.isArray(r)?r:[]}catch{return[]}}async writeQueue(e){try{e.length===0?await this.kv?.removeItem(Ve):await this.kv?.setItem(Ve,JSON.stringify(e.slice(-Vt)))}catch{}}async enqueue(e){let r=await this.readQueue();r.push(e),await this.writeQueue(r)}};var $=class extends Error{constructor(r,s){super(s);this.code=r;this.name="SkeletonFetchError"}};async function Ke(t,e={}){let r=t.trim();if(!r||r==="true"||r==="false")throw new $("invalid","skeleton uuid is required");let s=(e.baseUrl??q).replace(/\/$/,""),i;try{i=await fetch(`${s}/api/sdk/skeleton?uuid=${encodeURIComponent(r)}`)}catch(u){throw new $("network",`Skeleton fetch failed: ${String(u)}`)}if(i.status===404)throw new $("not_found",`Skeleton '${r}' not found`);if(!i.ok)throw new $("network",`Skeleton fetch HTTP ${i.status}`);let n=await i.json();if(!n?.keypoints||!n?.keypoint_lines||!n?.circles||!n?.lines)throw new $("invalid","Skeleton payload incomplete");return{keypoints:n.keypoints,keypoint_lines:n.keypoint_lines,keypoint_angles:n.keypoint_angles??[],circles:n.circles,lines:n.lines,angles:n.angles}}async function $e(t={}){if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw new Error("getUserMedia is not available in this environment");let e=t.facingMode??"user",r={audio:!1,video:{facingMode:e,width:t.idealWidth?{ideal:t.idealWidth}:void 0,height:t.idealHeight?{ideal:t.idealHeight}:void 0,frameRate:t.idealFrameRate?{ideal:t.idealFrameRate}:void 0}};try{return await navigator.mediaDevices.getUserMedia(r)}catch{return navigator.mediaDevices.getUserMedia({audio:!1,video:{facingMode:e}})}}function We(t){if(t)for(let e of t.getTracks())try{e.stop()}catch{}}function ee(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement}function yt(t){return typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement}function ze(t){return typeof ImageBitmap<"u"&&t instanceof ImageBitmap}function te(t){return ze(t)?{width:t.width||0,height:t.height||0}:ee(t)?{width:t.videoWidth||0,height:t.videoHeight||0}:{width:t.naturalWidth||t.width||0,height:t.naturalHeight||t.height||0}}async function xe(t){if(typeof t=="string")return{url:t,revoke:()=>{}};let e=URL.createObjectURL(t);return{url:e,revoke:()=>{try{URL.revokeObjectURL(e)}catch{}}}}function je(t,e=15e3){return new Promise((r,s)=>{if(t.readyState>=2&&t.videoWidth>0){r();return}let i=()=>{p(),r()},n=()=>{p(),s(new Error("Failed to load video source"))},u=setTimeout(()=>{p(),s(new Error("Timed out waiting for video metadata"))},e),p=()=>{clearTimeout(u),t.removeEventListener("loadeddata",i),t.removeEventListener("error",n)};t.addEventListener("loadeddata",i),t.addEventListener("error",n)})}function we(t,e=15e3){return new Promise((r,s)=>{if(t.complete&&t.naturalWidth>0){r();return}if(t.complete){s(new Error("Image failed to decode (naturalWidth=0)"));return}let i=()=>{p(),r()},n=()=>{p(),s(new Error("Failed to load image source"))},u=setTimeout(()=>{p(),s(new Error("Timed out waiting for image"))},e),p=()=>{clearTimeout(u),t.removeEventListener("load",i),t.removeEventListener("error",n)};t.addEventListener("load",i),t.addEventListener("error",n)})}async function vt(t){let e=document.createElement("canvas");e.width=t.width,e.height=t.height;let r=e.getContext("2d");if(!r)throw new Error("2d context unavailable");r.drawImage(t,0,0);let s=await new Promise((i,n)=>{e.toBlob(u=>u?i(u):n(new Error("toBlob failed")),"image/png")});return xe(s)}function Ee(t,e){if(t&&typeof t=="object"&&"type"in t)return t;let r=t==="video"||t==="image"||t==="camera"?t:"camera";if(r==="camera")return{type:"camera",facingMode:e?.facingMode};let s=e?.sourceFile??e?.sourceUrl;if(!s)throw new Error(`PoseSource type "${r}" requires sourceUrl or sourceFile (or pass a full PoseSource object)`);return r==="video"?{type:"video",src:s}:{type:"image",src:s}}var qe={face_squat:"squat",face_pushup:"push_up",face_plank:"plank",face_lunge:"lunge",face_jumping_jack:"jumping_jack",face_low_impact_jack:"low_impact_jack",face_balance_leg:"balance_leg",face_balance_leg_left:"balance_leg_left",face_balance_leg_right:"balance_leg_right",jump:"air_time_jump"};function re(t){return qe[t]??t}function se(t,e){let r=e.find(n=>n.id===t);if(r)return r;let s=re(t),i=Object.entries(qe).filter(([,n])=>n===s).map(([n])=>n);for(let n of i){let u=e.find(p=>p.id===n);if(u)return u}return e.find(n=>re(n.id)===s)}var xt=["shoulder_roll","shoulder_deep_breath","chair_forward_fold","chair_side_stretch","back_flexibility_test"];function Se(t){let e=String(t||"").trim().toLowerCase();return e==="squat"||e==="face_squat"}function W(t){return t==="v3"?"v3":"v4"}function Ge(t={}){if(t.userExercise)return"v4";let e=String(t.engine||"").trim().toLowerCase();return Se(t.exercise)&&e!=="v4"?"v3":W(t.engine)}function Ye(t){return xt.includes(t)}var Kt={left_knee:"from_hip_to_ankle",right_knee:"from_hip_to_ankle",left_hip:"from_knee_to_shoulder",right_hip:"from_knee_to_shoulder",left_shoulder:"from_hip_to_elbow",right_shoulder:"from_hip_to_elbow",left_elbow:"from_shoulder_to_wrist",right_elbow:"from_shoulder_to_wrist"};function $t(t){let e={left_side:{},right_side:{}};for(let r of t){if(r.side!=="left"&&r.side!=="right")continue;let s=r.side==="left"?e.left_side:e.right_side,n=`${r.id.replace(/^(left_|right_)/,"")}_angle`,u=Kt[r.id]??"value";s[n]||(s[n]={}),s[n][u]=r.degrees,s[n][`score_${u}`]=r.score}return e}function Xe(t){switch(t.type){case"initialization":return{type:"initialization",message:t.step==="configuring"?"checking you plan and access":t.message??Wt(t.step),ready:t.ready,step:t.step,...t.mode!=null?{mode:t.mode}:{},...t.acceleration!=null?{acceleration:t.acceleration}:{}};case"error":return{type:"error",error:t.code,message:t.message};case"warning":return{type:"warning",error:t.code,message:t.message};case"keypoints":return{type:"keypoints",data:t.keypoints.map(e=>({name:e.name,x:e.x,y:e.y,...e.z!=null?{z:e.z}:{},score:e.score})),score:t.score,timestampMs:t.timestampMs};case"stats":return{type:"stats",fps:t.fps,medianInferenceMs:t.medianInferenceMs,backend:t.backend,keypointsAbove03:t.keypointsAbove03,meanScore:t.meanScore,videoSize:t.videoSize,timestampMs:t.timestampMs};case"angles":return{type:"angles",data:$t(t.angles),timestampMs:t.timestampMs};case"counter":return{type:"counter",current_count:t.count,...t.formScore!=null?{form_score:{score:t.formScore.score,avg_score:t.formScore.average,grade:t.formScore.grade}}:{},...t.referenceScore!=null?{reference_score:{overallScore:t.referenceScore}}:{},...t.analysis!=null?{analysis:t.analysis}:{},timestampMs:t.timestampMs};case"posture":return{type:"posture",message:t.hint,ready:t.ready,direction:t.direction??"",requirements:{missingKeypoints:t.missingKeypoints},timestampMs:t.timestampMs};case"progression":return{type:"progression",value:t.value,timestampMs:t.timestampMs};case"recommendations":return{type:"recommendations",data:t.recommendations,timestampMs:t.timestampMs};case"form_score":return{type:"form_score",score:t.score,avg_score:t.average,grade:t.grade,timestampMs:t.timestampMs};case"exercise_summary":return{type:"exercise_summary",exercise:t.exercise,counter:t.counter,avg_rep_score:t.averageFormScore,avg_similarity:t.averageSimilarity,grade:t.grade,history:t.history,durationMs:t.durationMs,...t.disclaimer!=null?{disclaimer:t.disclaimer}:{},...t.method!=null?{method:t.method}:{},timestampMs:t.timestampMs};case"jump_calibration":return{type:"jump_calibration",calibrated:t.calibrated,cmPerPixel:t.cmPerPixel,baselineY:t.baselineY,visibleHips:t.visibleHips,message:t.message,timestampMs:t.timestampMs};case"jump_started":return{type:"jump_started",message:t.message,timestampMs:t.timestampMs};case"jump_height":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"jump_discarded":return{type:"jump_discarded",reason:t.reason,userMessage:t.userMessage,timestampMs:t.timestampMs};case"jump_result":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"jump_summary":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"performance_warning":return{type:"error",error:t.code,message:t.message,meanFps:t.meanFps,thresholdFps:t.thresholdFps,activeProfile:t.activeProfile,medianInferenceMs:t.medianInferenceMs,videoSize:t.videoSize,timestampMs:t.timestampMs};case"quality_changed":return{type:"warning",error:"quality_downgraded",message:t.detail,previousProfile:t.previousProfile,activeProfile:t.activeProfile,reason:t.reason,timestampMs:t.timestampMs};case"runtime_download_progress":return{type:"initialization",message:`downloading pose runtime ${t.completedParts+1}/${t.totalParts}`,ready:!1,step:"downloading",part:t.part,completedParts:t.completedParts,totalParts:t.totalParts,timestampMs:t.timestampMs};default:return{type:"unknown",event:t}}}function Wt(t){switch(t){case"configuring":return"checking you plan and access";case"downloading":return"downloading engine";case"warming":return"loading pose model";case"ready":return"running";default:return t}}var N=["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"];var be="4.22.0",zt="https://cdn.jsdelivr.net/npm";function Je(t=zt,e=be){let r=t.replace(/\/$/,"");return{scriptUrls:[`${r}/@tensorflow/tfjs-core@${e}/dist/tf-core.min.js`,`${r}/@tensorflow/tfjs-converter@${e}/dist/tf-converter.min.js`,`${r}/@tensorflow/tfjs-backend-webgl@${e}/dist/tf-backend-webgl.min.js`,`${r}/@tensorflow/tfjs-backend-wasm@${e}/dist/tf-backend-wasm.min.js`],wasmPath:`${r}/@tensorflow/tfjs-backend-wasm@${e}/dist/`}}var ke=null;function jt(t){try{t.env().set("WEBGL_CPU_FORWARD",!1)}catch{}try{t.env().set("WEBGL_PACK",!1)}catch{}try{t.env().set("WEBGL_FORCE_F16_TEXTURES",!1)}catch{}try{t.env().set("WEBGL_RENDER_FLOAT32_ENABLED",!1)}catch{}try{t.env().set("WEBGL_FLUSH_THRESHOLD",1.75)}catch{}}function qt(){return new Function("s","return import(s)")("@tensorflow/tfjs")}async function oe(){if(ke)return ke;if(typeof window<"u"&&window.tf)return ke=window.tf,window.tf;try{let t=await qt();return ke=t,t}catch{}throw new Error("TensorFlow.js not found. Install peer `@tensorflow/tfjs` or load CDN scripts (see README) so `window.tf` is available.")}async function ie(t){jt(t);try{let e=await t.setBackend("webgl");if(await t.ready(),e!==!1&&t.getBackend()==="webgl")return"webgl"}catch{}return await t.setBackend("cpu"),await t.ready(),"cpu"}var wt="2.1.3",Gt="https://cdn.jsdelivr.net/npm",ne=null,ae=null;function Et(t=Gt,e=wt){return`${t.replace(/\/$/,"")}/@tensorflow-models/pose-detection@${e}/dist/pose-detection.min.js`}function Yt(){return new Function("s","return import(s)")("@tensorflow-models/pose-detection")}function Qe(){if(typeof window>"u")return null;let t=window.poseDetection;return t&&typeof t.createDetector=="function"&&t.SupportedModels?.BlazePose?t:null}function Xt(t){if(typeof document>"u")return Promise.reject(new Error("Cannot inject pose-detection outside a browser document"));let e=Qe();return e?Promise.resolve(e):ae||(ae=new Promise((r,s)=>{let n=document.querySelector('script[data-pt-pose-detection="1"]'),u=()=>{let l=Qe();if(l){r(l);return}s(new Error("pose-detection CDN script loaded but `window.poseDetection` is missing. Check script order (TF.js first) and network."))};if(n){if(n.dataset.ptLoaded==="1"){u();return}n.addEventListener("load",u,{once:!0}),n.addEventListener("error",()=>s(new Error(`Failed to load pose-detection from ${n.src}`)),{once:!0});return}let p=document.createElement("script");p.src=t,p.async=!0,p.dataset.ptPoseDetection="1",p.onload=()=>{p.dataset.ptLoaded="1",u()},p.onerror=()=>{ae=null,s(new Error(`Failed to load pose-detection from ${t}`))},document.head.appendChild(p)}),ae)}async function Ze(t){if(ne)return ne;let e=Qe();if(e)return ne=e,e;try{let s=await Yt();return ne=s,s}catch{}if(t?.allowCdn!==!1&&typeof window<"u"&&typeof document<"u"){let s=Et(t?.cdnBase,t?.version),i=await Xt(s);return ne=i,i}throw new Error('BlazePose requires optional peer `@tensorflow-models/pose-detection`. Install it, load the CDN script so `window.poseDetection` exists, or use model="movenet" / pass a TF.js modelUrl.')}function Jt(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement?{vw:t.videoWidth||0,vh:t.videoHeight||0}:typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement?{vw:t.naturalWidth||t.width||0,vh:t.naturalHeight||t.height||0}:typeof HTMLCanvasElement<"u"&&t instanceof HTMLCanvasElement?{vw:t.width||0,vh:t.height||0}:{vw:t.width||0,vh:t.height||0}}var Qt={nose:"nose",left_eye:"left_eye",right_eye:"right_eye",left_ear:"left_ear",right_ear:"right_ear",left_shoulder:"left_shoulder",right_shoulder:"right_shoulder",left_elbow:"left_elbow",right_elbow:"right_elbow",left_wrist:"left_wrist",right_wrist:"right_wrist",left_hip:"left_hip",right_hip:"right_hip",left_knee:"left_knee",right_knee:"right_knee",left_ankle:"left_ankle",right_ankle:"right_ankle"},Zt=new Set(["left_eye_inner","left_eye_outer","right_eye_inner","right_eye_outer","mouth_left","mouth_right"]),er=1280,tr=640;function rr(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)?tr:er}function sr(t,e,r,s,i,n,u){if(u<=1.5&&r>2)return{x:t*i,y:e*n};let p=r/i,l=s/n;return{x:p>0?t/p:t,y:l>0?e/l:e}}var le=class{constructor(e){this.modelId="blazepose";this.detector=null;this.backend=null;this.modelType="lite"}getBackend(){return this.backend}async load(){let e=await oe();this.backend=await ie(e);let r=await Ze();this.detector=await r.createDetector(r.SupportedModels.BlazePose,{runtime:"tfjs",modelType:this.modelType,enableSmoothing:!1})}async estimate(e,r){if(!this.detector)throw new Error("BlazePose adapter not loaded");let s=Jt(e);if(!(s.vw>0&&s.vh>0))return null;r.temporalSmooth===!1&&this.resetTemporal();let i=e,n=null,u=s.vw,p=s.vh;if(typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement||typeof HTMLVideoElement<"u"&&e instanceof HTMLVideoElement||typeof HTMLCanvasElement<"u"&&e instanceof HTMLCanvasElement){let a=Math.min(1,rr()/Math.max(s.vw,s.vh));u=Math.max(1,Math.round(s.vw*a)),p=Math.max(1,Math.round(s.vh*a)),n=document.createElement("canvas"),n.width=u,n.height=p;let m=n.getContext("2d");m&&(m.drawImage(e,0,0,u,p),i=n)}let l=performance.now(),x=await this.detector.estimatePoses(i,{flipHorizontal:!1,maxPoses:1}),v=performance.now()-l,_=x[0];if(!_?.keypoints?.length)return{keypoints:N.map(a=>({name:a,x:0,y:0,score:0})),score:0,inferenceMs:v,videoKeypoints:N.map(a=>({name:a,xPx:0,yPx:0,score:0})),letterbox:{offsetX:0,offsetY:0,drawW:s.vw,drawH:s.vh,vw:s.vw,vh:s.vh}};let w=s.vw||1,g=s.vh||1,b=r.displayWidth||1,E=r.displayHeight||1,T=0;for(let a of _.keypoints)T=Math.max(T,Math.abs(a.x||0),Math.abs(a.y||0));let C=new Map,F=[];for(let a of _.keypoints){let m=String(a.name||"").toLowerCase(),h=typeof a.score=="number"?a.score:0,y=sr(a.x,a.y,u,p,w,g,T),c=Qt[m];c?C.set(c,{x:y.x,y:y.y,score:h}):Zt.has(m)&&F.push({name:m,x:y.x,y:y.y,score:h})}let k=N.map(a=>{let m=C.get(a);return{name:a,xPx:m?.x??0,yPx:m?.y??0,score:m?.score??0}}),L=Math.max(b/w,E/g),I=(b-w*L)/2,o=(E-g*L)/2,d=0,f=k.map(a=>{let m=a.xPx*L+I,h=a.yPx*L+o,y=b>0?m/b:0,c=E>0?h/E:0;return r.facingMode==="user"&&(y=1-y),d+=a.score,{name:a.name,x:Math.min(1,Math.max(0,y)),y:Math.min(1,Math.max(0,c)),score:a.score}});for(let a of F){let m=a.x*L+I,h=a.y*L+o,y=b>0?m/b:0,c=E>0?h/E:0;r.facingMode==="user"&&(y=1-y),f.push({name:a.name,x:Math.min(1,Math.max(0,y)),y:Math.min(1,Math.max(0,c)),score:a.score})}return{keypoints:f,score:d/17,inferenceMs:v,videoKeypoints:k,letterbox:{offsetX:0,offsetY:0,drawW:w,drawH:g,vw:w,vh:g}}}resetTemporal(){try{this.detector?.reset?.()}catch{}}dispose(){try{this.detector?.dispose?.()}catch{}this.detector=null}};function St(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement?{vw:t.videoWidth||0,vh:t.videoHeight||0}:typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement?{vw:t.naturalWidth||t.width||0,vh:t.naturalHeight||t.height||0}:typeof HTMLCanvasElement<"u"&&t instanceof HTMLCanvasElement?{vw:t.width||0,vh:t.height||0}:{vw:t.width||0,vh:t.height||0}}var A=192,_e=.45,kt=.52;function or(t,e,r){let s=t*A,i=e*A;return{x:(s-r.offsetX)*(r.vw/r.drawW)+(r.originX??0),y:(i-r.offsetY)*(r.vh/r.drawH)+(r.originY??0)}}function bt(t,e,r,s,i){let n=Math.max(r/i.vw,s/i.vh),u=(r-i.vw*n)/2,p=(s-i.vh*n)/2;return{x:t*n+u,y:e*n+p}}var ce=class{constructor(e){this.tf=null;this.model=null;this.backend=null;this.offscreen=null;this.offCtx=null;this.smoothed=null;this.lastVideoKps=null;this.stillPass=0;this.inferLb={offsetX:0,offsetY:0,drawW:A,drawH:A,vw:1,vh:1,originX:0,originY:0};this.letterbox={offsetX:0,offsetY:0,drawW:A,drawH:A,vw:1,vh:1};if(!e.resolved.modelUrl)throw new Error(e.resolved.unsupportedReason??"MoveNet requires a modelUrl");this.modelId=e.resolved.modelId,this.modelUrl=e.resolved.modelUrl}getBackend(){return this.backend}async load(){this.tf=await oe(),this.backend=await ie(this.tf),this.model=await this.tf.loadGraphModel(this.modelUrl);let e=this.tf.zeros([1,A,A,3],"int32"),r=this.model.execute(e);(Array.isArray(r)?r[0]:r).dataSync(),e.dispose(),Array.isArray(r)?r.forEach(i=>i.dispose()):r.dispose()}poseCanvasCtx(){if(this.offscreen||(this.offscreen=document.createElement("canvas"),this.offscreen.width=A,this.offscreen.height=A,this.offCtx=this.offscreen.getContext("2d",{willReadFrequently:!0})),!this.offCtx)throw new Error("2d context unavailable");return this.offCtx}prepareInput(e,r){let{vw:s,vh:i}=St(e),n=s||1,u=i||1,p=0,l=0,x=n,v=u;if(r&&this.lastVideoKps&&this.stillPass>0){let I=this.lastVideoKps.filter(o=>o.score>=.15);if(I.length>=4){let o=1/0,d=1/0,f=-1/0,a=-1/0;for(let P of I)o=Math.min(o,P.xPx),d=Math.min(d,P.yPx),f=Math.max(f,P.xPx),a=Math.max(a,P.yPx);let m=Math.max(8,f-o),h=Math.max(8,a-d),y=(o+f)/2,c=(d+a)/2;x=Math.max(m/kt,n*.35),v=Math.max(h/kt,u*.35);let S=n/u;x/v<S?x=v*S:v=x/S,p=y-x/2,l=c-v/2}}let _=Math.min(A/x,A/v),w=x*_,g=v*_,b=(A-w)/2,E=(A-g)/2;this.inferLb={offsetX:b,offsetY:E,drawW:w,drawH:g,vw:x,vh:v,originX:p,originY:l},this.letterbox={offsetX:0,offsetY:0,drawW:n,drawH:u,vw:n,vh:u};let T=this.poseCanvasCtx();T.fillStyle="#000",T.fillRect(0,0,A,A);let C=Math.max(0,p),F=Math.max(0,l),k=Math.min(n,p+x),L=Math.min(u,l+v);if(k>C&&L>F){let I=b+(C-p)*(w/x),o=E+(F-l)*(g/v),d=(k-C)*(w/x),f=(L-F)*(g/v);T.drawImage(e,C,F,k-C,L-F,I,o,d,f)}return this.stillPass+=1,this.offscreen}smooth(e){if(!this.smoothed)return this.smoothed=e.map(r=>({...r})),this.smoothed;for(let r=0;r<e.length;r++)e[r].score<.1||(this.smoothed[r].xPx=_e*e[r].xPx+(1-_e)*this.smoothed[r].xPx,this.smoothed[r].yPx=_e*e[r].yPx+(1-_e)*this.smoothed[r].yPx,this.smoothed[r].score=e[r].score);return this.smoothed}async estimate(e,r){if(!this.tf||!this.model)throw new Error("MoveNet adapter not loaded");let s=St(e);if(!(s.vw>0&&s.vh>0))return null;let i=performance.now(),n=typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement,u=r.temporalSmooth!==!1&&n,p=this.prepareInput(e,u),l=this.tf.tidy(()=>this.tf.expandDims(this.tf.browser.fromPixels(p),0)),x=this.model.execute(l),_=(Array.isArray(x)?x[0]:x).dataSync();l.dispose(),Array.isArray(x)?x.forEach(o=>o.dispose()):x.dispose();let w=performance.now()-i,g=this.inferLb,b=this.letterbox,E=r.displayWidth||1,T=r.displayHeight||1,C=[],F=0;for(let o=0;o<17;o++){let d=Math.min(1,Math.max(0,_[o*3])),f=Math.min(1,Math.max(0,_[o*3+1])),a=_[o*3+2],m=or(f,d,g);C.push({name:N[o],xPx:m.x,yPx:m.y,score:a}),F+=a}let k=r.temporalSmooth===!1?C:this.smooth(C);this.lastVideoKps=k.map(o=>({xPx:o.xPx,yPx:o.yPx,score:o.score}));let L=k.map((o,d)=>({name:N[d],xPx:o.xPx,yPx:o.yPx,score:o.score})),I=L.map(o=>{let d=bt(o.xPx,o.yPx,E,T,b),f=E>0?d.x/E:0,a=T>0?d.y/T:0;return r.facingMode==="user"&&(f=1-f),{name:o.name,x:Math.min(1,Math.max(0,f)),y:Math.min(1,Math.max(0,a)),score:o.score}});return{raw:_,keypoints:I,score:F/17,inferenceMs:w,videoKeypoints:L,letterbox:b}}resetTemporal(){this.smoothed=null,this.lastVideoKps=null,this.stillPass=0}dispose(){try{this.model?.dispose()}catch{}this.model=null,this.smoothed=null,this.lastVideoKps=null,this.stillPass=0,this.offscreen=null,this.offCtx=null}};function et(t,e,r,s){return t.map(i=>{let n=bt(i.xPx,i.yPx,r,s,e);return{name:i.name,dx:n.x,dy:n.y,score:i.score}})}function tt(t){let{resolved:e}=t;if(e.kind==="unsupported"||!e.modelUrl&&e.kind!=="blazepose")throw new Error(e.unsupportedReason??`Model "${e.modelId}" is unavailable.`);return e.kind==="blazepose"?new le(t):new ce(t)}var Me="https://app.posetracker.com/scripts/tmp_model_to_remove.json";function z(t={}){let e=typeof t.modelUrl=="string"&&t.modelUrl.trim().length>0?t.modelUrl.trim():null;if(e)return{modelId:typeof t.model=="string"&&t.model.trim().length>0?t.model.trim():"custom",kind:"custom-graph",modelUrl:e};let r=(t.model??"movenet").trim().toLowerCase();return r==="movenet"||r==="movenet-singlepose-lightning"||r==="lightning"?{modelId:"movenet-singlepose-lightning",kind:"movenet-graph",modelUrl:Me}:r==="blazepose"?{modelId:"blazepose",kind:"blazepose",modelUrl:null}:{modelId:r,kind:"unsupported",modelUrl:null,unsupportedReason:`Unknown model "${t.model}". Use "movenet" (default), "blazepose", or pass modelUrl.`}}var de="AI Loading",rt="https://docs.posetracker.com/logo/light.png";var ir=`
1
+ "use strict";var PoseTracker=(()=>{var Ft=Object.create;var me=Object.defineProperty;var Ct=Object.getOwnPropertyDescriptor;var It=Object.getOwnPropertyNames;var Lt=Object.getPrototypeOf,At=Object.prototype.hasOwnProperty;var dt=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var Rt=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Dt=(t,e)=>{for(var r in e)me(t,r,{get:e[r],enumerable:!0})},ht=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of It(e))!At.call(t,i)&&i!==r&&me(t,i,{get:()=>e[i],enumerable:!(s=Ct(e,i))||s.enumerable});return t};var ut=(t,e,r)=>(r=t!=null?Ft(Lt(t)):{},ht(e||!t||!t.__esModule?me(r,"default",{value:t,enumerable:!0}):r,t)),Ot=t=>ht(me({},"__esModule",{value:!0}),t);var De=Rt((vr,fe)=>{"use strict";(function(){"use strict";var t="input is invalid type",e=typeof window=="object",r=e?window:{};r.JS_SHA256_NO_WINDOW&&(e=!1);var s=!e&&typeof self=="object",i=!r.JS_SHA256_NO_NODE_JS&&typeof process=="object"&&process.versions&&process.versions.node&&process.type!="renderer";i?r=global:s&&(r=self);var n=!r.JS_SHA256_NO_COMMON_JS&&typeof fe=="object"&&fe.exports,d=typeof define=="function"&&define.amd,p=!r.JS_SHA256_NO_ARRAY_BUFFER&&typeof ArrayBuffer<"u",l="0123456789abcdef".split(""),x=[-2147483648,8388608,32768,128],v=[24,16,8,0],k=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],E=["hex","array","digest","arrayBuffer"],g=[];(r.JS_SHA256_NO_NODE_JS||!Array.isArray)&&(Array.isArray=function(o){return Object.prototype.toString.call(o)==="[object Array]"}),p&&(r.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW||!ArrayBuffer.isView)&&(ArrayBuffer.isView=function(o){return typeof o=="object"&&o.buffer&&o.buffer.constructor===ArrayBuffer});var P=function(o,h){return function(f){return new S(h,!0).update(f)[o]()}},b=function(o){var h=P("hex",o);i&&(h=T(h,o)),h.create=function(){return new S(o)},h.update=function(m){return h.create().update(m)};for(var f=0;f<E.length;++f){var a=E[f];h[a]=P(a,o)}return h},T=function(o,h){var f=dt("crypto"),a=dt("buffer").Buffer,m=h?"sha224":"sha256",u;a.from&&!r.JS_SHA256_NO_BUFFER_FROM?u=a.from:u=function(c){return new a(c)};var y=function(c){if(typeof c=="string")return f.createHash(m).update(c,"utf8").digest("hex");if(c==null)throw new Error(t);return c.constructor===ArrayBuffer&&(c=new Uint8Array(c)),Array.isArray(c)||ArrayBuffer.isView(c)||c.constructor===a?f.createHash(m).update(u(c)).digest("hex"):o(c)};return y},L=function(o,h){return function(f,a){return new I(f,h,!0).update(a)[o]()}},F=function(o){var h=L("hex",o);h.create=function(m){return new I(m,o)},h.update=function(m,u){return h.create(m).update(u)};for(var f=0;f<E.length;++f){var a=E[f];h[a]=L(a,o)}return h};function S(o,h){h?(g[0]=g[16]=g[1]=g[2]=g[3]=g[4]=g[5]=g[6]=g[7]=g[8]=g[9]=g[10]=g[11]=g[12]=g[13]=g[14]=g[15]=0,this.blocks=g):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o?(this.h0=3238371032,this.h1=914150663,this.h2=812702999,this.h3=4144912697,this.h4=4290775857,this.h5=1750603025,this.h6=1694076839,this.h7=3204075428):(this.h0=1779033703,this.h1=3144134277,this.h2=1013904242,this.h3=2773480762,this.h4=1359893119,this.h5=2600822924,this.h6=528734635,this.h7=1541459225),this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0,this.is224=o}S.prototype.update=function(o){if(!this.finalized){var h,f=typeof o;if(f!=="string"){if(f==="object"){if(o===null)throw new Error(t);if(p&&o.constructor===ArrayBuffer)o=new Uint8Array(o);else if(!Array.isArray(o)&&(!p||!ArrayBuffer.isView(o)))throw new Error(t)}else throw new Error(t);h=!0}for(var a,m=0,u,y=o.length,c=this.blocks;m<y;){if(this.hashed&&(this.hashed=!1,c[0]=this.block,this.block=c[16]=c[1]=c[2]=c[3]=c[4]=c[5]=c[6]=c[7]=c[8]=c[9]=c[10]=c[11]=c[12]=c[13]=c[14]=c[15]=0),h)for(u=this.start;m<y&&u<64;++m)c[u>>>2]|=o[m]<<v[u++&3];else for(u=this.start;m<y&&u<64;++m)a=o.charCodeAt(m),a<128?c[u>>>2]|=a<<v[u++&3]:a<2048?(c[u>>>2]|=(192|a>>>6)<<v[u++&3],c[u>>>2]|=(128|a&63)<<v[u++&3]):a<55296||a>=57344?(c[u>>>2]|=(224|a>>>12)<<v[u++&3],c[u>>>2]|=(128|a>>>6&63)<<v[u++&3],c[u>>>2]|=(128|a&63)<<v[u++&3]):(a=65536+((a&1023)<<10|o.charCodeAt(++m)&1023),c[u>>>2]|=(240|a>>>18)<<v[u++&3],c[u>>>2]|=(128|a>>>12&63)<<v[u++&3],c[u>>>2]|=(128|a>>>6&63)<<v[u++&3],c[u>>>2]|=(128|a&63)<<v[u++&3]);this.lastByteIndex=u,this.bytes+=u-this.start,u>=64?(this.block=c[16],this.start=u-64,this.hash(),this.hashed=!0):this.start=u}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this}},S.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var o=this.blocks,h=this.lastByteIndex;o[16]=this.block,o[h>>>2]|=x[h&3],this.block=o[16],h>=56&&(this.hashed||this.hash(),o[0]=this.block,o[16]=o[1]=o[2]=o[3]=o[4]=o[5]=o[6]=o[7]=o[8]=o[9]=o[10]=o[11]=o[12]=o[13]=o[14]=o[15]=0),o[14]=this.hBytes<<3|this.bytes>>>29,o[15]=this.bytes<<3,this.hash()}},S.prototype.hash=function(){var o=this.h0,h=this.h1,f=this.h2,a=this.h3,m=this.h4,u=this.h5,y=this.h6,c=this.h7,w=this.blocks,M,R,D,H,_,B,V,X,Ae,Re,pe;for(M=16;M<64;++M)_=w[M-15],R=(_>>>7|_<<25)^(_>>>18|_<<14)^_>>>3,_=w[M-2],D=(_>>>17|_<<15)^(_>>>19|_<<13)^_>>>10,w[M]=w[M-16]+R+w[M-7]+D<<0;for(pe=h&f,M=0;M<64;M+=4)this.first?(this.is224?(X=300032,_=w[0]-1413257819,c=_-150054599<<0,a=_+24177077<<0):(X=704751109,_=w[0]-210244248,c=_-1521486534<<0,a=_+143694565<<0),this.first=!1):(R=(o>>>2|o<<30)^(o>>>13|o<<19)^(o>>>22|o<<10),D=(m>>>6|m<<26)^(m>>>11|m<<21)^(m>>>25|m<<7),X=o&h,H=X^o&f^pe,V=m&u^~m&y,_=c+D+V+k[M]+w[M],B=R+H,c=a+_<<0,a=_+B<<0),R=(a>>>2|a<<30)^(a>>>13|a<<19)^(a>>>22|a<<10),D=(c>>>6|c<<26)^(c>>>11|c<<21)^(c>>>25|c<<7),Ae=a&o,H=Ae^a&h^X,V=c&m^~c&u,_=y+D+V+k[M+1]+w[M+1],B=R+H,y=f+_<<0,f=_+B<<0,R=(f>>>2|f<<30)^(f>>>13|f<<19)^(f>>>22|f<<10),D=(y>>>6|y<<26)^(y>>>11|y<<21)^(y>>>25|y<<7),Re=f&a,H=Re^f&o^Ae,V=y&c^~y&m,_=u+D+V+k[M+2]+w[M+2],B=R+H,u=h+_<<0,h=_+B<<0,R=(h>>>2|h<<30)^(h>>>13|h<<19)^(h>>>22|h<<10),D=(u>>>6|u<<26)^(u>>>11|u<<21)^(u>>>25|u<<7),pe=h&f,H=pe^h&a^Re,V=u&y^~u&c,_=m+D+V+k[M+3]+w[M+3],B=R+H,m=o+_<<0,o=_+B<<0,this.chromeBugWorkAround=!0;this.h0=this.h0+o<<0,this.h1=this.h1+h<<0,this.h2=this.h2+f<<0,this.h3=this.h3+a<<0,this.h4=this.h4+m<<0,this.h5=this.h5+u<<0,this.h6=this.h6+y<<0,this.h7=this.h7+c<<0},S.prototype.hex=function(){this.finalize();var o=this.h0,h=this.h1,f=this.h2,a=this.h3,m=this.h4,u=this.h5,y=this.h6,c=this.h7,w=l[o>>>28&15]+l[o>>>24&15]+l[o>>>20&15]+l[o>>>16&15]+l[o>>>12&15]+l[o>>>8&15]+l[o>>>4&15]+l[o&15]+l[h>>>28&15]+l[h>>>24&15]+l[h>>>20&15]+l[h>>>16&15]+l[h>>>12&15]+l[h>>>8&15]+l[h>>>4&15]+l[h&15]+l[f>>>28&15]+l[f>>>24&15]+l[f>>>20&15]+l[f>>>16&15]+l[f>>>12&15]+l[f>>>8&15]+l[f>>>4&15]+l[f&15]+l[a>>>28&15]+l[a>>>24&15]+l[a>>>20&15]+l[a>>>16&15]+l[a>>>12&15]+l[a>>>8&15]+l[a>>>4&15]+l[a&15]+l[m>>>28&15]+l[m>>>24&15]+l[m>>>20&15]+l[m>>>16&15]+l[m>>>12&15]+l[m>>>8&15]+l[m>>>4&15]+l[m&15]+l[u>>>28&15]+l[u>>>24&15]+l[u>>>20&15]+l[u>>>16&15]+l[u>>>12&15]+l[u>>>8&15]+l[u>>>4&15]+l[u&15]+l[y>>>28&15]+l[y>>>24&15]+l[y>>>20&15]+l[y>>>16&15]+l[y>>>12&15]+l[y>>>8&15]+l[y>>>4&15]+l[y&15];return this.is224||(w+=l[c>>>28&15]+l[c>>>24&15]+l[c>>>20&15]+l[c>>>16&15]+l[c>>>12&15]+l[c>>>8&15]+l[c>>>4&15]+l[c&15]),w},S.prototype.toString=S.prototype.hex,S.prototype.digest=function(){this.finalize();var o=this.h0,h=this.h1,f=this.h2,a=this.h3,m=this.h4,u=this.h5,y=this.h6,c=this.h7,w=[o>>>24&255,o>>>16&255,o>>>8&255,o&255,h>>>24&255,h>>>16&255,h>>>8&255,h&255,f>>>24&255,f>>>16&255,f>>>8&255,f&255,a>>>24&255,a>>>16&255,a>>>8&255,a&255,m>>>24&255,m>>>16&255,m>>>8&255,m&255,u>>>24&255,u>>>16&255,u>>>8&255,u&255,y>>>24&255,y>>>16&255,y>>>8&255,y&255];return this.is224||w.push(c>>>24&255,c>>>16&255,c>>>8&255,c&255),w},S.prototype.array=S.prototype.digest,S.prototype.arrayBuffer=function(){this.finalize();var o=new ArrayBuffer(this.is224?28:32),h=new DataView(o);return h.setUint32(0,this.h0),h.setUint32(4,this.h1),h.setUint32(8,this.h2),h.setUint32(12,this.h3),h.setUint32(16,this.h4),h.setUint32(20,this.h5),h.setUint32(24,this.h6),this.is224||h.setUint32(28,this.h7),o};function I(o,h,f){var a,m=typeof o;if(m==="string"){var u=[],y=o.length,c=0,w;for(a=0;a<y;++a)w=o.charCodeAt(a),w<128?u[c++]=w:w<2048?(u[c++]=192|w>>>6,u[c++]=128|w&63):w<55296||w>=57344?(u[c++]=224|w>>>12,u[c++]=128|w>>>6&63,u[c++]=128|w&63):(w=65536+((w&1023)<<10|o.charCodeAt(++a)&1023),u[c++]=240|w>>>18,u[c++]=128|w>>>12&63,u[c++]=128|w>>>6&63,u[c++]=128|w&63);o=u}else if(m==="object"){if(o===null)throw new Error(t);if(p&&o.constructor===ArrayBuffer)o=new Uint8Array(o);else if(!Array.isArray(o)&&(!p||!ArrayBuffer.isView(o)))throw new Error(t)}else throw new Error(t);o.length>64&&(o=new S(h,!0).update(o).array());var M=[],R=[];for(a=0;a<64;++a){var D=o[a]||0;M[a]=92^D,R[a]=54^D}S.call(this,h,f),this.update(R),this.oKeyPad=M,this.inner=!0,this.sharedMemory=f}I.prototype=new S,I.prototype.finalize=function(){if(S.prototype.finalize.call(this),this.inner){this.inner=!1;var o=this.array();S.call(this,this.is224,this.sharedMemory),this.update(this.oKeyPad),this.update(o),S.prototype.finalize.call(this)}};var C=b();C.sha256=C,C.sha224=b(!0),C.sha256.hmac=F(),C.sha224.hmac=F(!0),n?fe.exports=C:(r.sha256=C.sha256,r.sha224=C.sha224,d&&define(function(){return C}))})()});var mr={};Dt(mr,{COCO_KEYPOINT_NAMES:()=>N,DEFAULT_MOVENET_LIGHTNING_URL:()=>Pe,ONLINE_TFJS_VERSION:()=>be,PoseCamera:()=>ct,SDK_NAME:()=>J,SDK_VERSION:()=>K,configure:()=>G,createPoseTracker:()=>Le,defaultTfjsCdnUrls:()=>Je,resolvePoseModel:()=>z,shouldShowWatermark:()=>ue});var K="0.3.6",J="posetracker-web";var q="https://movelytics-strapi-c78a339b7070.herokuapp.com",U=class extends Error{constructor(r,s){super(s);this.code=r;this.name="ConfigureError"}};async function G(t,e={}){let r=(e.baseUrl??q).replace(/\/$/,""),s={...t?{apiToken:t}:{},sdkName:J,sdkVersion:K,targetPlatform:"web",poseModelProfile:e.poseModelProfile??"AdaptiveChoice",locale:e.locale,localVersions:e.localVersions,engineChannel:e.engine==="v3"?"v3":"v4"},i;try{i=await fetch(`${r}/api/sdk/configure`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}catch(n){throw new U("network",`Handshake request failed: ${String(n)}`)}if(i.status===401||i.status===403)throw new U("invalid_token","Invalid, revoked or unauthorized API token.");if(i.status===429)throw new U("quota_exceeded","API call quota exceeded for the current plan.");if(!i.ok)throw new U("internal",`Handshake failed with HTTP ${i.status}.`);return await i.json()}var Be=ut(De(),1);var Ue=ut(De(),1),Oe="ptc1:";function Ne(t){return(0,Ue.sha256)(`posetracker-sdk-cache:${t}`)}function Ut(t){let e=unescape(encodeURIComponent(t)),r=new Uint8Array(e.length);for(let s=0;s<e.length;s++)r[s]=e.charCodeAt(s);return r}function Nt(t){let e="";for(let r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return decodeURIComponent(escape(e))}function Ht(t){let e="";for(let r=0;r<t.length;r++)e+=t[r].toString(16).padStart(2,"0");return e}function Bt(t){if(t.length%2!==0||/[^0-9a-f]/.test(t))return null;let e=new Uint8Array(t.length/2);for(let r=0;r<e.length;r++)e[r]=parseInt(t.slice(r*2,r*2+2),16);return e}function pt(t,e){let r=new Uint8Array(t.length),s=0,i=0;for(;i<t.length;){let n=Ue.sha256.array(`${e}:${s++}`);for(let d=0;d<n.length&&i<t.length;d++,i++)r[i]=t[i]^n[d]}return r}function ge(t,e){return Oe+Ht(pt(Ut(t),e))}function ye(t,e){if(!t.startsWith(Oe))return null;let r=Bt(t.slice(Oe.length));if(!r)return null;try{return Nt(pt(r,e))}catch{return null}}var He="posetracker.web.";function Z(){return typeof localStorage>"u"?null:{async getItem(t){try{return localStorage.getItem(He+t)}catch{return null}},async setItem(t,e){try{localStorage.setItem(He+t,e)}catch{}},async removeItem(t){try{localStorage.removeItem(He+t)}catch{}}}}function ft(){let t=Z();return t?{read:e=>t.getItem(`file.${e}`),write:(e,r)=>t.setItem(`file.${e}`,r),remove:e=>t.removeItem(`file.${e}`)}:null}function gt(){let t=new Map;return{async read(e){return t.has(e)?t.get(e):null},async write(e,r){t.set(e,r)},async remove(e){t.delete(e)}}}function xe(){return ft()??gt()}var mt="posetracker.engine.guard.",Q=class{constructor(e={}){this.lastError=null;this.files=e.fileStore!==void 0?e.fileStore:xe(),this.kv=e.keyValueStore!==void 0?e.keyValueStore:Z(),this.fetchFn=e.fetchFn??fetch,this.onDiagnostic=e.onDiagnostic,this.onDiagnostic?.(`[posetracker-web] EngineLoader fileStore=${this.files?"yes":"none"} kv=${this.kv?"localStorage":"none"}`)}async clearGuard(e){if(!e||!this.kv)return;let r=`${mt}${e.version}.${e.sha256}`;await this.kv.removeItem(r).catch(()=>{})}async load(e,r,s){if(this.lastError=null,!e)return this.lastError="manifest has no engine descriptor",null;let i=`${mt}${e.version}.${e.sha256}`;s?.forceRetry&&await this.kv?.removeItem(i).catch(()=>{});let n=await this.kv?.getItem(i);if(n==="probing"||n==="failed")return await this.kv?.setItem(i,"failed"),this.lastError=`engine crash-guard blocked version=${e.version} (state=${n}) \u2014 call configure() again to force retry`,this.onDiagnostic?.(`[posetracker-web] ${this.lastError}`),null;let d=`engine-${e.version}.sealed`,p=await this.files?.read(d);if(p!=null){let l=ye(p,r);if(l!==null&&(0,Be.sha256)(l)===e.sha256){let x=await this.evaluate(l,i);if(x)return this.onDiagnostic?.(`[posetracker-web] engine loaded from cache version=${e.version}`),{engine:x,source:"remote-cache"}}else this.onDiagnostic?.(`[posetracker-web] engine cache purged (unseal/integrity mismatch) key=${d}`),await this.files?.remove(d)}try{this.onDiagnostic?.(`[posetracker-web] downloading engine version=${e.version}\u2026`);let l=await this.fetchFn(e.signedUrl);if(!l.ok)throw new Error(`HTTP ${l.status}`);let x=await l.text(),v=(0,Be.sha256)(x);if(v!==e.sha256)throw new Error(`integrity check failed (got ${v.slice(0,12)}\u2026 expected ${e.sha256.slice(0,12)}\u2026, bytes=${x.length})`);let k=await this.evaluate(x,i);if(k){try{await this.files?.write(d,ge(x,r))}catch(E){this.onDiagnostic?.("[posetracker-web] engine cache write failed (non-fatal): "+(E instanceof Error?E.message:String(E)))}return this.onDiagnostic?.(`[posetracker-web] engine loaded from download version=${e.version} bytes=${x.length}`),{engine:k,source:"remote-download"}}}catch(l){this.lastError=l instanceof Error?l.message:String(l),this.onDiagnostic?.(`[posetracker-web] engine download/load failed: ${this.lastError}`)}return this.lastError||(this.lastError="engine evaluate returned null"),null}async evaluate(e,r){await this.kv?.setItem(r,"probing");try{let s={exports:{}};new Function("module","exports",e)(s,s.exports);let n=s.exports.createEngine;if(typeof n!="function")throw new Error(`engine bundle does not export createEngine() (got ${typeof n}, keys=${Object.keys(s.exports).join(",")})`);let d=n();return await this.kv?.setItem(r,"passed"),d}catch(s){return this.lastError="engine evaluate failed: "+(s instanceof Error?s.message:String(s)),this.onDiagnostic?.(`[posetracker-web] ${this.lastError}`),await this.kv?.setItem(r,"failed"),null}}};var Ve="posetracker.track.queue",Vt=100,O=class extends Error{constructor(r,s){super(s);this.code=r;this.name="TrackError"}},ee=class{constructor(e={}){this.baseUrl=(e.baseUrl??q).replace(/\/$/,""),this.kv=e.keyValueStore!==void 0?e.keyValueStore:Z(),this.fetchFn=e.fetchFn??fetch}async trackMetered(e){return this.post({event:e.event??"camera_start",apiToken:e.apiToken,platform:"web",sdkVersion:K,params:e.params})}async trackAnonymous(e={}){let r={event:e.event??"camera_start",platform:"web",sdkVersion:K,params:e.params,queuedAtMs:Date.now()};try{await this.post({...r}),this.flushQueue()}catch{await this.enqueue(r)}}async flushQueue(){let e=await this.readQueue();if(e.length===0)return;let r=[];for(let s of e)try{await this.post({...s,params:{...s.params??{},queuedAtMs:s.queuedAtMs}})}catch{r.push(s)}await this.writeQueue(r)}async post(e){let r;try{r=await this.fetchFn(`${this.baseUrl}/api/sdk/track`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch(s){throw new O("network",`Usage tracking request failed: ${String(s)}`)}if(r.status===401||r.status===403)throw new O("invalid_token","Invalid, revoked or unauthorized API token.");if(r.status===429)throw new O("quota_exceeded","API call quota exceeded for the current plan.");if(!r.ok)throw new O("internal",`Usage tracking failed with HTTP ${r.status}.`);return await r.json()}async readQueue(){try{let e=await this.kv?.getItem(Ve);if(!e)return[];let r=JSON.parse(e);return Array.isArray(r)?r:[]}catch{return[]}}async writeQueue(e){try{e.length===0?await this.kv?.removeItem(Ve):await this.kv?.setItem(Ve,JSON.stringify(e.slice(-Vt)))}catch{}}async enqueue(e){let r=await this.readQueue();r.push(e),await this.writeQueue(r)}};var $=class extends Error{constructor(r,s){super(s);this.code=r;this.name="SkeletonFetchError"}};async function Ke(t,e={}){let r=t.trim();if(!r||r==="true"||r==="false")throw new $("invalid","skeleton uuid is required");let s=(e.baseUrl??q).replace(/\/$/,""),i;try{i=await fetch(`${s}/api/sdk/skeleton?uuid=${encodeURIComponent(r)}`)}catch(d){throw new $("network",`Skeleton fetch failed: ${String(d)}`)}if(i.status===404)throw new $("not_found",`Skeleton '${r}' not found`);if(!i.ok)throw new $("network",`Skeleton fetch HTTP ${i.status}`);let n=await i.json();if(!n?.keypoints||!n?.keypoint_lines||!n?.circles||!n?.lines)throw new $("invalid","Skeleton payload incomplete");return{keypoints:n.keypoints,keypoint_lines:n.keypoint_lines,keypoint_angles:n.keypoint_angles??[],circles:n.circles,lines:n.lines,angles:n.angles}}async function $e(t={}){if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw new Error("getUserMedia is not available in this environment");let e=t.facingMode??"user",r={audio:!1,video:{facingMode:e,width:t.idealWidth?{ideal:t.idealWidth}:void 0,height:t.idealHeight?{ideal:t.idealHeight}:void 0,frameRate:t.idealFrameRate?{ideal:t.idealFrameRate}:void 0}};try{return await navigator.mediaDevices.getUserMedia(r)}catch{return navigator.mediaDevices.getUserMedia({audio:!1,video:{facingMode:e}})}}function We(t){if(t)for(let e of t.getTracks())try{e.stop()}catch{}}function te(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement}function yt(t){return typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement}function ze(t){return typeof ImageBitmap<"u"&&t instanceof ImageBitmap}function Y(t){return ze(t)?{width:t.width||0,height:t.height||0}:te(t)?{width:t.videoWidth||0,height:t.videoHeight||0}:{width:t.naturalWidth||t.width||0,height:t.naturalHeight||t.height||0}}async function ve(t){if(typeof t=="string")return{url:t,revoke:()=>{}};let e=URL.createObjectURL(t);return{url:e,revoke:()=>{try{URL.revokeObjectURL(e)}catch{}}}}function je(t,e=15e3){return new Promise((r,s)=>{if(t.readyState>=2&&t.videoWidth>0){r();return}let i=()=>{p(),r()},n=()=>{p(),s(new Error("Failed to load video source"))},d=setTimeout(()=>{p(),s(new Error("Timed out waiting for video metadata"))},e),p=()=>{clearTimeout(d),t.removeEventListener("loadeddata",i),t.removeEventListener("error",n)};t.addEventListener("loadeddata",i),t.addEventListener("error",n)})}function we(t,e=15e3){return new Promise((r,s)=>{if(t.complete&&t.naturalWidth>0){r();return}if(t.complete){s(new Error("Image failed to decode (naturalWidth=0)"));return}let i=()=>{p(),r()},n=()=>{p(),s(new Error("Failed to load image source"))},d=setTimeout(()=>{p(),s(new Error("Timed out waiting for image"))},e),p=()=>{clearTimeout(d),t.removeEventListener("load",i),t.removeEventListener("error",n)};t.addEventListener("load",i),t.addEventListener("error",n)})}async function xt(t){let e=document.createElement("canvas");e.width=t.width,e.height=t.height;let r=e.getContext("2d");if(!r)throw new Error("2d context unavailable");r.drawImage(t,0,0);let s=await new Promise((i,n)=>{e.toBlob(d=>d?i(d):n(new Error("toBlob failed")),"image/png")});return ve(s)}function Ee(t,e){if(t&&typeof t=="object"&&"type"in t)return t;let r=t==="video"||t==="image"||t==="camera"?t:"camera";if(r==="camera")return{type:"camera",facingMode:e?.facingMode};let s=e?.sourceFile??e?.sourceUrl;if(!s)throw new Error(`PoseSource type "${r}" requires sourceUrl or sourceFile (or pass a full PoseSource object)`);return r==="video"?{type:"video",src:s}:{type:"image",src:s}}var qe={face_squat:"squat",face_pushup:"push_up",face_plank:"plank",face_lunge:"lunge",face_jumping_jack:"jumping_jack",face_low_impact_jack:"low_impact_jack",face_balance_leg:"balance_leg",face_balance_leg_left:"balance_leg_left",face_balance_leg_right:"balance_leg_right",jump:"air_time_jump"};function re(t){return qe[t]??t}function se(t,e){let r=e.find(n=>n.id===t);if(r)return r;let s=re(t),i=Object.entries(qe).filter(([,n])=>n===s).map(([n])=>n);for(let n of i){let d=e.find(p=>p.id===n);if(d)return d}return e.find(n=>re(n.id)===s)}var vt=["shoulder_roll","shoulder_deep_breath","chair_forward_fold","chair_side_stretch","back_flexibility_test"];function ke(t){let e=String(t||"").trim().toLowerCase();return e==="squat"||e==="face_squat"}function W(t){return t==="v3"?"v3":"v4"}function Ge(t={}){if(t.userExercise)return"v4";let e=String(t.engine||"").trim().toLowerCase();return ke(t.exercise)&&e!=="v4"?"v3":W(t.engine)}function Ye(t){return vt.includes(t)}var Kt={left_knee:"from_hip_to_ankle",right_knee:"from_hip_to_ankle",left_hip:"from_knee_to_shoulder",right_hip:"from_knee_to_shoulder",left_shoulder:"from_hip_to_elbow",right_shoulder:"from_hip_to_elbow",left_elbow:"from_shoulder_to_wrist",right_elbow:"from_shoulder_to_wrist"};function $t(t){let e={left_side:{},right_side:{}};for(let r of t){if(r.side!=="left"&&r.side!=="right")continue;let s=r.side==="left"?e.left_side:e.right_side,n=`${r.id.replace(/^(left_|right_)/,"")}_angle`,d=Kt[r.id]??"value";s[n]||(s[n]={}),s[n][d]=r.degrees,s[n][`score_${d}`]=r.score}return e}function Xe(t){switch(t.type){case"initialization":return{type:"initialization",message:t.step==="configuring"?"checking you plan and access":t.message??Wt(t.step),ready:t.ready,step:t.step,...t.mode!=null?{mode:t.mode}:{},...t.acceleration!=null?{acceleration:t.acceleration}:{}};case"error":return{type:"error",error:t.code,message:t.message};case"warning":return{type:"warning",error:t.code,message:t.message};case"keypoints":return{type:"keypoints",data:t.keypoints.map(e=>({name:e.name,x:e.x,y:e.y,...e.z!=null?{z:e.z}:{},score:e.score})),score:t.score,timestampMs:t.timestampMs};case"stats":return{type:"stats",fps:t.fps,medianInferenceMs:t.medianInferenceMs,backend:t.backend,keypointsAbove03:t.keypointsAbove03,meanScore:t.meanScore,videoSize:t.videoSize,timestampMs:t.timestampMs};case"angles":return{type:"angles",data:$t(t.angles),timestampMs:t.timestampMs};case"counter":return{type:"counter",current_count:t.count,...t.formScore!=null?{form_score:{score:t.formScore.score,avg_score:t.formScore.average,grade:t.formScore.grade}}:{},...t.referenceScore!=null?{reference_score:{overallScore:t.referenceScore}}:{},...t.analysis!=null?{analysis:t.analysis}:{},timestampMs:t.timestampMs};case"posture":return{type:"posture",message:t.hint,ready:t.ready,direction:t.direction??"",requirements:{missingKeypoints:t.missingKeypoints},timestampMs:t.timestampMs};case"progression":return{type:"progression",value:t.value,timestampMs:t.timestampMs};case"recommendations":return{type:"recommendations",data:t.recommendations,timestampMs:t.timestampMs};case"form_score":return{type:"form_score",score:t.score,avg_score:t.average,grade:t.grade,timestampMs:t.timestampMs};case"exercise_summary":return{type:"exercise_summary",exercise:t.exercise,counter:t.counter,avg_rep_score:t.averageFormScore,avg_similarity:t.averageSimilarity,grade:t.grade,history:t.history,durationMs:t.durationMs,...t.disclaimer!=null?{disclaimer:t.disclaimer}:{},...t.method!=null?{method:t.method}:{},timestampMs:t.timestampMs};case"jump_calibration":return{type:"jump_calibration",calibrated:t.calibrated,cmPerPixel:t.cmPerPixel,baselineY:t.baselineY,visibleHips:t.visibleHips,message:t.message,timestampMs:t.timestampMs};case"jump_started":return{type:"jump_started",message:t.message,timestampMs:t.timestampMs};case"jump_height":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"jump_discarded":return{type:"jump_discarded",reason:t.reason,userMessage:t.userMessage,timestampMs:t.timestampMs};case"jump_result":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"jump_summary":{let{type:e,timestampMs:r,...s}=t;return{type:e,...s,timestampMs:r}}case"performance_warning":return{type:"error",error:t.code,message:t.message,meanFps:t.meanFps,thresholdFps:t.thresholdFps,activeProfile:t.activeProfile,medianInferenceMs:t.medianInferenceMs,videoSize:t.videoSize,timestampMs:t.timestampMs};case"quality_changed":return{type:"warning",error:"quality_downgraded",message:t.detail,previousProfile:t.previousProfile,activeProfile:t.activeProfile,reason:t.reason,timestampMs:t.timestampMs};case"runtime_download_progress":return{type:"initialization",message:`downloading pose runtime ${t.completedParts+1}/${t.totalParts}`,ready:!1,step:"downloading",part:t.part,completedParts:t.completedParts,totalParts:t.totalParts,timestampMs:t.timestampMs};default:return{type:"unknown",event:t}}}function Wt(t){switch(t){case"configuring":return"checking you plan and access";case"downloading":return"downloading engine";case"warming":return"loading pose model";case"ready":return"running";default:return t}}var N=["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"];var be="4.22.0",zt="https://cdn.jsdelivr.net/npm";function Je(t=zt,e=be){let r=t.replace(/\/$/,"");return{scriptUrls:[`${r}/@tensorflow/tfjs-core@${e}/dist/tf-core.min.js`,`${r}/@tensorflow/tfjs-converter@${e}/dist/tf-converter.min.js`,`${r}/@tensorflow/tfjs-backend-webgl@${e}/dist/tf-backend-webgl.min.js`,`${r}/@tensorflow/tfjs-backend-wasm@${e}/dist/tf-backend-wasm.min.js`],wasmPath:`${r}/@tensorflow/tfjs-backend-wasm@${e}/dist/`}}var Se=null;function jt(t){try{t.env().set("WEBGL_CPU_FORWARD",!1)}catch{}try{t.env().set("WEBGL_PACK",!1)}catch{}try{t.env().set("WEBGL_FORCE_F16_TEXTURES",!1)}catch{}try{t.env().set("WEBGL_RENDER_FLOAT32_ENABLED",!1)}catch{}try{t.env().set("WEBGL_FLUSH_THRESHOLD",1.75)}catch{}}function qt(){return new Function("s","return import(s)")("@tensorflow/tfjs")}async function oe(){if(Se)return Se;if(typeof window<"u"&&window.tf)return Se=window.tf,window.tf;try{let t=await qt();return Se=t,t}catch{}throw new Error("TensorFlow.js not found. Install peer `@tensorflow/tfjs` or load CDN scripts (see README) so `window.tf` is available.")}async function ie(t){jt(t);try{let e=await t.setBackend("webgl");if(await t.ready(),e!==!1&&t.getBackend()==="webgl")return"webgl"}catch{}return await t.setBackend("cpu"),await t.ready(),"cpu"}var wt="2.1.3",Gt="https://cdn.jsdelivr.net/npm",ne=null,ae=null;function Et(t=Gt,e=wt){return`${t.replace(/\/$/,"")}/@tensorflow-models/pose-detection@${e}/dist/pose-detection.min.js`}function Yt(){return new Function("s","return import(s)")("@tensorflow-models/pose-detection")}function Qe(){if(typeof window>"u")return null;let t=window.poseDetection;return t&&typeof t.createDetector=="function"&&t.SupportedModels?.BlazePose?t:null}function Xt(t){if(typeof document>"u")return Promise.reject(new Error("Cannot inject pose-detection outside a browser document"));let e=Qe();return e?Promise.resolve(e):ae||(ae=new Promise((r,s)=>{let n=document.querySelector('script[data-pt-pose-detection="1"]'),d=()=>{let l=Qe();if(l){r(l);return}s(new Error("pose-detection CDN script loaded but `window.poseDetection` is missing. Check script order (TF.js first) and network."))};if(n){if(n.dataset.ptLoaded==="1"){d();return}n.addEventListener("load",d,{once:!0}),n.addEventListener("error",()=>s(new Error(`Failed to load pose-detection from ${n.src}`)),{once:!0});return}let p=document.createElement("script");p.src=t,p.async=!0,p.dataset.ptPoseDetection="1",p.onload=()=>{p.dataset.ptLoaded="1",d()},p.onerror=()=>{ae=null,s(new Error(`Failed to load pose-detection from ${t}`))},document.head.appendChild(p)}),ae)}async function Ze(t){if(ne)return ne;let e=Qe();if(e)return ne=e,e;try{let s=await Yt();return ne=s,s}catch{}if(t?.allowCdn!==!1&&typeof window<"u"&&typeof document<"u"){let s=Et(t?.cdnBase,t?.version),i=await Xt(s);return ne=i,i}throw new Error('BlazePose requires optional peer `@tensorflow-models/pose-detection`. Install it, load the CDN script so `window.poseDetection` exists, or use model="movenet" / pass a TF.js modelUrl.')}function Jt(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement?{vw:t.videoWidth||0,vh:t.videoHeight||0}:typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement?{vw:t.naturalWidth||t.width||0,vh:t.naturalHeight||t.height||0}:typeof HTMLCanvasElement<"u"&&t instanceof HTMLCanvasElement?{vw:t.width||0,vh:t.height||0}:{vw:t.width||0,vh:t.height||0}}var Qt={nose:"nose",left_eye:"left_eye",right_eye:"right_eye",left_ear:"left_ear",right_ear:"right_ear",left_shoulder:"left_shoulder",right_shoulder:"right_shoulder",left_elbow:"left_elbow",right_elbow:"right_elbow",left_wrist:"left_wrist",right_wrist:"right_wrist",left_hip:"left_hip",right_hip:"right_hip",left_knee:"left_knee",right_knee:"right_knee",left_ankle:"left_ankle",right_ankle:"right_ankle"},Zt=new Set(["left_eye_inner","left_eye_outer","right_eye_inner","right_eye_outer","mouth_left","mouth_right"]),er=1280,tr=640;function rr(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)?tr:er}function sr(t,e,r,s,i,n,d){if(d<=1.5&&r>2)return{x:t*i,y:e*n};let p=r/i,l=s/n;return{x:p>0?t/p:t,y:l>0?e/l:e}}var le=class{constructor(e){this.modelId="blazepose";this.detector=null;this.backend=null;this.modelType="lite"}getBackend(){return this.backend}async load(){let e=await oe();this.backend=await ie(e);let r=await Ze();this.detector=await r.createDetector(r.SupportedModels.BlazePose,{runtime:"tfjs",modelType:this.modelType,enableSmoothing:!1})}async estimate(e,r){if(!this.detector)throw new Error("BlazePose adapter not loaded");let s=Jt(e);if(!(s.vw>0&&s.vh>0))return null;r.temporalSmooth===!1&&this.resetTemporal();let i=e,n=null,d=s.vw,p=s.vh;if(typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement||typeof HTMLVideoElement<"u"&&e instanceof HTMLVideoElement||typeof HTMLCanvasElement<"u"&&e instanceof HTMLCanvasElement){let a=Math.min(1,rr()/Math.max(s.vw,s.vh));d=Math.max(1,Math.round(s.vw*a)),p=Math.max(1,Math.round(s.vh*a)),n=document.createElement("canvas"),n.width=d,n.height=p;let m=n.getContext("2d");m&&(m.drawImage(e,0,0,d,p),i=n)}let l=performance.now(),x=await this.detector.estimatePoses(i,{flipHorizontal:!1,maxPoses:1}),v=performance.now()-l,k=x[0];if(!k?.keypoints?.length)return{keypoints:N.map(a=>({name:a,x:0,y:0,score:0})),score:0,inferenceMs:v,videoKeypoints:N.map(a=>({name:a,xPx:0,yPx:0,score:0})),letterbox:{offsetX:0,offsetY:0,drawW:s.vw,drawH:s.vh,vw:s.vw,vh:s.vh}};let E=s.vw||1,g=s.vh||1,P=r.displayWidth||1,b=r.displayHeight||1,T=0;for(let a of k.keypoints)T=Math.max(T,Math.abs(a.x||0),Math.abs(a.y||0));let L=new Map,F=[];for(let a of k.keypoints){let m=String(a.name||"").toLowerCase(),u=typeof a.score=="number"?a.score:0,y=sr(a.x,a.y,d,p,E,g,T),c=Qt[m];c?L.set(c,{x:y.x,y:y.y,score:u}):Zt.has(m)&&F.push({name:m,x:y.x,y:y.y,score:u})}let S=N.map(a=>{let m=L.get(a);return{name:a,xPx:m?.x??0,yPx:m?.y??0,score:m?.score??0}}),I=Math.max(P/E,b/g),C=(P-E*I)/2,o=(b-g*I)/2,h=0,f=S.map(a=>{let m=a.xPx*I+C,u=a.yPx*I+o,y=P>0?m/P:0,c=b>0?u/b:0;return r.facingMode==="user"&&(y=1-y),h+=a.score,{name:a.name,x:Math.min(1,Math.max(0,y)),y:Math.min(1,Math.max(0,c)),score:a.score}});for(let a of F){let m=a.x*I+C,u=a.y*I+o,y=P>0?m/P:0,c=b>0?u/b:0;r.facingMode==="user"&&(y=1-y),f.push({name:a.name,x:Math.min(1,Math.max(0,y)),y:Math.min(1,Math.max(0,c)),score:a.score})}return{keypoints:f,score:h/17,inferenceMs:v,videoKeypoints:S,letterbox:{offsetX:0,offsetY:0,drawW:E,drawH:g,vw:E,vh:g}}}resetTemporal(){try{this.detector?.reset?.()}catch{}}dispose(){try{this.detector?.dispose?.()}catch{}this.detector=null}};function kt(t){return typeof HTMLVideoElement<"u"&&t instanceof HTMLVideoElement?{vw:t.videoWidth||0,vh:t.videoHeight||0}:typeof HTMLImageElement<"u"&&t instanceof HTMLImageElement?{vw:t.naturalWidth||t.width||0,vh:t.naturalHeight||t.height||0}:typeof HTMLCanvasElement<"u"&&t instanceof HTMLCanvasElement?{vw:t.width||0,vh:t.height||0}:{vw:t.width||0,vh:t.height||0}}var A=192,_e=.45,St=.52;function or(t,e,r){let s=t*A,i=e*A;return{x:(s-r.offsetX)*(r.vw/r.drawW)+(r.originX??0),y:(i-r.offsetY)*(r.vh/r.drawH)+(r.originY??0)}}function bt(t,e,r,s,i){let n=Math.max(r/i.vw,s/i.vh),d=(r-i.vw*n)/2,p=(s-i.vh*n)/2;return{x:t*n+d,y:e*n+p}}var ce=class{constructor(e){this.tf=null;this.model=null;this.backend=null;this.offscreen=null;this.offCtx=null;this.smoothed=null;this.lastVideoKps=null;this.stillPass=0;this.inferLb={offsetX:0,offsetY:0,drawW:A,drawH:A,vw:1,vh:1,originX:0,originY:0};this.letterbox={offsetX:0,offsetY:0,drawW:A,drawH:A,vw:1,vh:1};if(!e.resolved.modelUrl)throw new Error(e.resolved.unsupportedReason??"MoveNet requires a modelUrl");this.modelId=e.resolved.modelId,this.modelUrl=e.resolved.modelUrl}getBackend(){return this.backend}async load(){this.tf=await oe(),this.backend=await ie(this.tf),this.model=await this.tf.loadGraphModel(this.modelUrl);let e=this.tf.zeros([1,A,A,3],"int32"),r=this.model.execute(e);(Array.isArray(r)?r[0]:r).dataSync(),e.dispose(),Array.isArray(r)?r.forEach(i=>i.dispose()):r.dispose()}poseCanvasCtx(){if(this.offscreen||(this.offscreen=document.createElement("canvas"),this.offscreen.width=A,this.offscreen.height=A,this.offCtx=this.offscreen.getContext("2d",{willReadFrequently:!0})),!this.offCtx)throw new Error("2d context unavailable");return this.offCtx}prepareInput(e,r){let{vw:s,vh:i}=kt(e),n=s||1,d=i||1,p=0,l=0,x=n,v=d;if(r&&this.lastVideoKps&&this.stillPass>0){let C=this.lastVideoKps.filter(o=>o.score>=.15);if(C.length>=4){let o=1/0,h=1/0,f=-1/0,a=-1/0;for(let M of C)o=Math.min(o,M.xPx),h=Math.min(h,M.yPx),f=Math.max(f,M.xPx),a=Math.max(a,M.yPx);let m=Math.max(8,f-o),u=Math.max(8,a-h),y=(o+f)/2,c=(h+a)/2;x=Math.max(m/St,n*.35),v=Math.max(u/St,d*.35);let w=n/d;x/v<w?x=v*w:v=x/w,p=y-x/2,l=c-v/2}}let k=Math.min(A/x,A/v),E=x*k,g=v*k,P=(A-E)/2,b=(A-g)/2;this.inferLb={offsetX:P,offsetY:b,drawW:E,drawH:g,vw:x,vh:v,originX:p,originY:l},this.letterbox={offsetX:0,offsetY:0,drawW:n,drawH:d,vw:n,vh:d};let T=this.poseCanvasCtx();T.fillStyle="#000",T.fillRect(0,0,A,A);let L=Math.max(0,p),F=Math.max(0,l),S=Math.min(n,p+x),I=Math.min(d,l+v);if(S>L&&I>F){let C=P+(L-p)*(E/x),o=b+(F-l)*(g/v),h=(S-L)*(E/x),f=(I-F)*(g/v);T.drawImage(e,L,F,S-L,I-F,C,o,h,f)}return this.stillPass+=1,this.offscreen}smooth(e){if(!this.smoothed)return this.smoothed=e.map(r=>({...r})),this.smoothed;for(let r=0;r<e.length;r++)e[r].score<.1||(this.smoothed[r].xPx=_e*e[r].xPx+(1-_e)*this.smoothed[r].xPx,this.smoothed[r].yPx=_e*e[r].yPx+(1-_e)*this.smoothed[r].yPx,this.smoothed[r].score=e[r].score);return this.smoothed}async estimate(e,r){if(!this.tf||!this.model)throw new Error("MoveNet adapter not loaded");let s=kt(e);if(!(s.vw>0&&s.vh>0))return null;let i=performance.now(),n=typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement,d=r.temporalSmooth!==!1&&n,p=this.prepareInput(e,d),l=this.tf.tidy(()=>this.tf.expandDims(this.tf.browser.fromPixels(p),0)),x=this.model.execute(l),k=(Array.isArray(x)?x[0]:x).dataSync();l.dispose(),Array.isArray(x)?x.forEach(o=>o.dispose()):x.dispose();let E=performance.now()-i,g=this.inferLb,P=this.letterbox,b=r.displayWidth||1,T=r.displayHeight||1,L=[],F=0;for(let o=0;o<17;o++){let h=Math.min(1,Math.max(0,k[o*3])),f=Math.min(1,Math.max(0,k[o*3+1])),a=k[o*3+2],m=or(f,h,g);L.push({name:N[o],xPx:m.x,yPx:m.y,score:a}),F+=a}let S=r.temporalSmooth===!1?L:this.smooth(L);this.lastVideoKps=S.map(o=>({xPx:o.xPx,yPx:o.yPx,score:o.score}));let I=S.map((o,h)=>({name:N[h],xPx:o.xPx,yPx:o.yPx,score:o.score})),C=I.map(o=>{let h=bt(o.xPx,o.yPx,b,T,P),f=b>0?h.x/b:0,a=T>0?h.y/T:0;return r.facingMode==="user"&&(f=1-f),{name:o.name,x:Math.min(1,Math.max(0,f)),y:Math.min(1,Math.max(0,a)),score:o.score}});return{raw:k,keypoints:C,score:F/17,inferenceMs:E,videoKeypoints:I,letterbox:P}}resetTemporal(){this.smoothed=null,this.lastVideoKps=null,this.stillPass=0}dispose(){try{this.model?.dispose()}catch{}this.model=null,this.smoothed=null,this.lastVideoKps=null,this.stillPass=0,this.offscreen=null,this.offCtx=null}};function et(t,e,r,s){return t.map(i=>{let n=bt(i.xPx,i.yPx,r,s,e);return{name:i.name,dx:n.x,dy:n.y,score:i.score}})}function tt(t){let{resolved:e}=t;if(e.kind==="unsupported"||!e.modelUrl&&e.kind!=="blazepose")throw new Error(e.unsupportedReason??`Model "${e.modelId}" is unavailable.`);return e.kind==="blazepose"?new le(t):new ce(t)}var Pe="https://app.posetracker.com/scripts/tmp_model_to_remove.json";function z(t={}){let e=typeof t.modelUrl=="string"&&t.modelUrl.trim().length>0?t.modelUrl.trim():null;if(e)return{modelId:typeof t.model=="string"&&t.model.trim().length>0?t.model.trim():"custom",kind:"custom-graph",modelUrl:e};let r=(t.model??"movenet").trim().toLowerCase();return r==="movenet"||r==="movenet-singlepose-lightning"||r==="lightning"?{modelId:"movenet-singlepose-lightning",kind:"movenet-graph",modelUrl:Pe}:r==="blazepose"?{modelId:"blazepose",kind:"blazepose",modelUrl:null}:{modelId:r,kind:"unsupported",modelUrl:null,unsupportedReason:`Unknown model "${t.model}". Use "movenet" (default), "blazepose", or pass modelUrl.`}}var de="AI Loading",rt="https://docs.posetracker.com/logo/light.png";var ir=`
2
2
  .pt-root {
3
3
  position: relative; width: 100%; height: 100%; min-height: 240px;
4
4
  background: #000; overflow: hidden; isolation: isolate;
@@ -57,12 +57,20 @@
57
57
  }
58
58
  .pt-hud.debug { display: block; }
59
59
  .pt-placement {
60
- position: absolute; z-index: 2; pointer-events: none; display: none;
61
- border: 2px dashed rgba(255, 195, 0, 0.85);
62
- box-sizing: border-box;
63
- border-radius: 4px;
60
+ position: absolute; inset: 0; z-index: 2; pointer-events: none; display: none;
64
61
  }
65
62
  .pt-placement.show { display: block; }
63
+ .pt-placement::before {
64
+ content: "";
65
+ position: absolute;
66
+ left: var(--pt-pad, 10%);
67
+ top: var(--pt-pad, 10%);
68
+ right: var(--pt-pad, 10%);
69
+ bottom: var(--pt-pad, 10%);
70
+ box-sizing: border-box;
71
+ border: 2px solid #4DD21D;
72
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
73
+ }
66
74
  `,_t=!1;function nr(){if(_t||typeof document>"u")return;let t=document.createElement("style");t.setAttribute("data-posetracker","shell"),t.textContent=ir,document.head.appendChild(t),_t=!0}function st(t,e={}){nr();let r=e.loadingText&&e.loadingText.trim()||de,s=e.logoUrl??rt,i=document.createElement("div");i.className="pt-root",i.innerHTML=`
67
75
  <video playsinline muted autoplay></video>
68
76
  <img class="pt-still" alt="" draggable="false" />
@@ -79,7 +87,7 @@
79
87
  <img class="pt-wm-logo" alt="PoseTracker" src="${s}" />
80
88
  </div>
81
89
  <div class="pt-hud"></div>
82
- `,t.appendChild(i);let n=i.querySelector("video"),u=i.querySelector("img.pt-still"),p=i.querySelector("canvas.pt-overlay"),l=i.querySelector(".pt-boot"),x=i.querySelector(".pt-boot-msg"),v=i.querySelector(".pt-wm"),_=i.querySelector(".pt-hud"),w=i.querySelector(".pt-placement");return{root:i,video:n,image:u,canvas:p,boot:l,bootMsg:x,watermark:v,hud:_,placement:w,setBootMessage(g,b=!1){x.textContent=g,x.classList.toggle("is-error",b)},setCameraVisible(g){g?this.setMediaMode(u.src?"image":"video"):this.setMediaMode("none")},setMediaMode(g){let b=g==="video",E=g==="image",T=b||E;n.style.opacity=b?"1":"0",u.style.opacity=E?"1":"0",p.style.opacity=T?"1":"0",l.classList.toggle("hide",T),T||(x.textContent=r,x.classList.remove("is-error"))},setWatermarkVisible(g){v.classList.toggle("show",g)},setHud(g,b){_.classList.toggle("debug",b),_.textContent=g},setPlacementVisible(g,b=10){if(w.classList.toggle("show",g),g){let E=Math.max(0,Math.min(40,b));w.style.left=`${E}%`,w.style.top=`${E}%`,w.style.right=`${E}%`,w.style.bottom=`${E}%`}},applyMirror(g){let b=g==="user"?"scaleX(-1)":"none";n.style.transform=b,u.style.transform="none",p.style.transform=b},destroy(){i.remove()}}}var ot={keypoints:["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"],keypoint_lines:["right_shoulder||left_shoulder","right_hip||left_hip","right_hip||right_shoulder","right_hip||right_knee","right_ankle||right_knee","right_shoulder||right_elbow","right_wrist||right_elbow","left_hip||left_shoulder","left_hip||left_knee","left_ankle||left_knee","left_shoulder||left_elbow","left_wrist||left_elbow"],keypoint_angles:[],angles:{fontColor:"#000000",strokeColor:"#FFC300"},circles:{circleFillColor:"#010A73",circleStrokeColor:"#FFC300",strokeWidth:"4",radius:"8"},lines:{lineStrokeColor:"#010A73",strokeWidth:"4"}};var it=.3;function nt(t,e,r,s,i=!0,n){if(t.canvas.width!==r&&(t.canvas.width=r),t.canvas.height!==s&&(t.canvas.height=s),t.clearRect(0,0,r,s),!i)return;let u=n??ot,p={};for(let E of e)p[E.name]=E;let l=u.lines.lineStrokeColor,x=Number(u.lines.strokeWidth);for(let E of u.keypoint_lines){let[T,C]=String(E).split("||"),F=p[T],k=p[C];!F||!k||F.score<=it||k.score<=it||(t.beginPath(),t.moveTo(F.dx,F.dy),t.lineTo(k.dx,k.dy),t.lineWidth=x,t.strokeStyle=l,t.stroke())}let v=u.circles.circleFillColor,_=u.circles.circleStrokeColor,w=Number(u.circles.strokeWidth),g=Number(u.circles.radius),b=new Set(u.keypoints);for(let E of e)!b.has(E.name)||E.score<=it||(t.beginPath(),t.arc(E.dx,E.dy,g,0,Math.PI*2),t.fillStyle=v,t.fill(),t.lineWidth=w,t.strokeStyle=_,t.stroke())}var Mt={angles:!1,recommendations:!1,progression:!1,keypoints:!1,minGrade:null},ar=new Set(["angles","recommendations","progression","keypoints","minGrade"]),lr={blazepose:'BlazePose as a features flag (use options.model = "blazepose" instead)',poseEngine:"pose engine selection (MediaPipe/PoseLandmarker)",mediapipeModel:"MediaPipe model selection",poseBackend:"pose backend selection",runInWorker:"worker thread selection"},he="You cannot use developer features. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)",at="Invalid params. Please refer to the documentation and set token=YOUR API_KEY et exercise=A correct exercise. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)";function Pe(t){let e=lr[t]??`'${t}'`;return`The '${t}' option (${e}) is not available as a features flag. Remove the option, or use options.model / modelUrl for model selection. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)`}function lt(t){if(!t)return{features:{...Mt},unsupportedKeys:[]};let e=Object.keys(t).filter(r=>!ar.has(r));return{features:{angles:t.angles===!0,recommendations:t.recommendations===!0,progression:t.progression===!0,keypoints:t.keypoints===!0,minGrade:t.minGrade??null},unsupportedKeys:e}}function Te(t,e){let r=[];return t.angles&&r.push("angles"),t.recommendations&&r.push("recommendations"),t.progression&&r.push("progression"),t.keypoints&&e.withExercise&&r.push("keypoints"),r}function Pt(t){return typeof t=="string"&&t.length>0&&t!=="free"}function ue(t){return!Pt(t)}var Fe="session.sealed",Ce="engine.version",cr=30;function dr(t){if(typeof t=="string"){let e=document.querySelector(t);if(!e)throw new Error(`PoseTracker mount target not found: ${t}`);return e}return t}function hr(t){if(!t.length)return null;let e=[...t].sort((r,s)=>r-s);return e[Math.floor(e.length/2)]??null}function j(t){return t.facingMode?t.facingMode:t.position==="back"?"environment":"user"}var Ie=class{constructor(e,r={}){this.status="idle";this.mode="keypoints-only";this.lastError=null;this.manifest=null;this.engine=null;this.engineSource=null;this.session=null;this.currentExerciseId=null;this.listeners=new Set;this.typedListeners=new Map;this.messageListeners=new Set;this.stateListeners=new Set;this.preloadPromise=null;this.coldStartMode="basic";this.configurePromise=null;this.handshakePromise=null;this.meteredSessionState="idle";this.lastCameraStartInfo=null;this.sessionErrorStreak=0;this.featureGateReported={unsupported:!1,freeBlock:!1,missingToken:!1};this.keypointsSuppressionLogged=!1;this.shell=null;this.stream=null;this.poseSource={type:"camera"};this.objectUrlRevoke=null;this.estimateInput=null;this.imageShotPending=!1;this.adapter=null;this.running=!1;this.busy=!1;this.loopToken=0;this.cameraRevealed=!1;this.disposed=!1;this.skeletonDef=null;this.acceleration={state:"unknown",runtime:"unknown",backend:null,glRenderer:null,medianInferenceMs:null};this.inferMs=[];this.fpsWindow=0;this.fpsTimer=0;let s=e??(typeof r.apiToken=="string"?r.apiToken:null)??null;this.opts={...r,onDiagnostic:r.onDiagnostic},this.apiToken=s;let i=lt(r.features);this.features=i.features,this.unsupportedFeatureKeys=i.unsupportedKeys,this.resolved=z({model:r.model,modelUrl:r.modelUrl}),this.skeletonDef=r.skeletonDef??null,this.coldStartMode=r.coldStart==="full"?"full":"basic";try{this.poseSource=Ee(r.source,{sourceUrl:r.sourceUrl,sourceFile:r.sourceFile,facingMode:j(r)})}catch{this.poseSource={type:"camera",facingMode:j(r)}}this.files=r.fileStore!==void 0?r.fileStore:ve(),this.engineLoader=r.engineLoader??new J({fileStore:this.files,onDiagnostic:this.opts.onDiagnostic}),this.tracker=r.usageTracker??new Z({baseUrl:r.baseUrl}),this.opts.onDiagnostic?.(`[posetracker-web] client created hasApiToken=${!!this.apiToken} features=${JSON.stringify(this.features)}`)}getStatus(){return this.status}getMode(){return this.mode}getError(){return this.lastError}getManifest(){return this.manifest}getPlanType(){return this.manifest?.plan?.plan??null}getFeatures(){return{...this.features}}getEngineSource(){return this.engineSource}getAcceleration(){return this.acceleration.state}getAccelerationDiagnostics(){return{...this.acceleration}}getResolvedModel(){return this.resolved}getColdStartMode(){return this.coldStartMode}getSource(){return this.poseSource}addEventListener(e){return this.listeners.add(e),()=>this.listeners.delete(e)}on(e,r){if(e==="*")return this.listeners.add(r),()=>this.listeners.delete(r);let s=this.typedListeners.get(e);return s||(s=new Set,this.typedListeners.set(e,s)),s.add(r),()=>s.delete(r)}addMessageListener(e){return this.messageListeners.add(e),()=>this.messageListeners.delete(e)}onStateChange(e){return this.stateListeners.add(e),()=>this.stateListeners.delete(e)}mount(e){if(this.disposed)throw new Error("PoseTracker disposed");this.shell&&this.shell.destroy();let r=dr(e);r.innerHTML="",this.shell=st(r,{loadingText:this.opts.loadingText??de}),this.shell.setCameraVisible(!1),this.cameraRevealed=!1,this.resolveSkeletonFromOptions()}preload(e){let r=e?.coldStart==="full"?"full":"basic";if(this.coldStartMode=r,this.status==="ready"&&this.preloadPromise)return this.preloadPromise.then(()=>this.ensureColdStartMode(r));if(!this.preloadPromise)this.preloadPromise=this.doPreload().then(()=>this.ensureColdStartMode(r)).catch(s=>{throw this.preloadPromise=null,s});else if(r==="full")return this.preloadPromise.then(()=>this.ensureColdStartMode("full"));return this.preloadPromise}warmup(e){return this.preload(e)}async start(){if(this.disposed)throw new Error("PoseTracker disposed");if(!this.shell)throw new Error("Call mount() before start()");let e=this.poseSource.type==="camera"||this.poseSource.type==="video"||this.poseSource.type==="image";await this.preload({coldStart:this.opts.coldStart==="basic"&&this.poseSource.type==="camera"?"basic":e?"full":"basic"}),(this.coldStartMode==="basic"||!this.estimateInput)&&await this.ensureSource(),this.poseSource.type==="image"&&(this.imageShotPending=!0),this.beginLoop()}async setSource(e,r){if(this.disposed)throw new Error("PoseTracker disposed");let s=this.running;this.stopMediaOnly(),this.poseSource=Ee(e,{sourceUrl:r?.sourceUrl??this.opts.sourceUrl,sourceFile:r?.sourceFile??this.opts.sourceFile,facingMode:r?.facingMode??j(this.opts)}),this.opts.source=this.poseSource,this.poseSource.type==="camera"&&this.poseSource.facingMode&&(this.opts.facingMode=this.poseSource.facingMode),this.adapter?.resetTemporal&&this.adapter.resetTemporal(),this.shell&&(await this.ensureAdapter(),await this.ensureSource(),(s||this.poseSource.type==="image")&&(this.poseSource.type==="image"&&(this.imageShotPending=!0),this.beginLoop()))}async analyze(){if(this.disposed)throw new Error("PoseTracker disposed");if(!this.shell)throw new Error("Call mount() before analyze()");return await this.ensureAdapter(),this.estimateInput||await this.ensureSource(),this.poseSource.type==="image"?(this.imageShotPending=!0,this.running||this.beginLoop(),null):this.runOneFrame()}stop(){this.running=!1,this.loopToken+=1,this.busy=!1,this.imageShotPending=!1,this.stopMediaOnly(),this.shell&&(this.shell.setMediaMode("none"),this.shell.setPlacementVisible(!1),this.cameraRevealed=!1,this.applyWatermark()),this.status!=="error"&&this.setStatus("idle")}stopMediaOnly(){if(We(this.stream),this.stream=null,this.estimateInput=null,this.objectUrlRevoke&&(this.objectUrlRevoke(),this.objectUrlRevoke=null),this.shell){try{this.shell.video.pause()}catch{}this.shell.video.removeAttribute("src"),this.shell.video.srcObject=null,this.shell.video.load(),this.shell.image.removeAttribute("src")}}async dispose(){this.stopExercise(),this.stop(),this.adapter?.dispose(),this.adapter=null,this.shell?.destroy(),this.shell=null,this.listeners.clear(),this.typedListeners.clear(),this.messageListeners.clear(),this.stateListeners.clear(),this.preloadPromise=null,this.configurePromise=null,this.handshakePromise=null,this.meteredSessionState="idle",this.lastCameraStartInfo=null,this.featureGateReported={unsupported:!1,freeBlock:!1,missingToken:!1},this.keypointsSuppressionLogged=!1,this.engine=null,this.engineSource=null,this.manifest=null,this.mode="keypoints-only",this.setStatus("idle"),this.disposed=!0}configure(e){return e!==void 0&&e!==this.apiToken&&(this.apiToken=e,this.opts.apiToken=e,this.configurePromise=null,this.handshakePromise=null,this.featureGateReported.freeBlock=!1,this.featureGateReported.missingToken=!1),this.mode!=="full-engine"&&(this.configurePromise=null),this.configurePromise||(this.configurePromise=this.tryConfigure({silentStatus:this.status==="ready",forceEngineRetry:!0}).then(r=>(r||(this.configurePromise=null),this.retryMeteredSessionIfRefused(),r)).catch(()=>(this.configurePromise=null,!1))),this.configurePromise}getAvailableExercises(){if(this.mode!=="full-engine")return[];if(W(this.opts.engine)==="v4"){let e=this.engine?.listExercises?.()??[];if(e.length)return e.map(r=>this.v4ExerciseConfig(r))}return this.manifest?.exercises??[]}getEngineChannel(){return W(this.opts.engine)}v4ExerciseConfig(e){let r=e.type==="static"?"static":"dynamic";return{id:e.id,name:e.displayName||e.id,type:r,movement:{name:e.id,type:r,scale_acceptance:{},movement_steps:[],movement_initial_posture:null}}}getAvailableCustomExercises(){return this.mode!=="full-engine"||!this.engine?.listCustomExercises?[]:this.engine.listCustomExercises()}startExercise(e,r={}){if(this.status!=="ready")throw new Error(`Cannot start exercise while status is '${this.status}' \u2014 call preload()/start() first.`);if(this.mode!=="full-engine"||!this.engine)throw new Error("Exercises require full-engine mode (validated API key). The SDK is running keypoints-only \u2014 call configure(apiToken) first.");if(this.meteredSessionState==="refused")throw new Error("API-key features are not available offline: PoseTracker could not track this session. Reconnect and retry.");if(this.unsupportedFeatureKeys.length>0){let p=Pe(this.unsupportedFeatureKeys[0]);throw this.reportError({type:"error",code:"feature_not_supported",message:p}),new Error(p)}if(this.getPlanType()==="free"&&Te(this.features,{withExercise:!0}).length>0)throw this.reportError({type:"error",code:"free_plan_feature_blocked",message:he}),new Error(he);if(Ye(e)&&W(this.opts.engine)!=="v4"){let p=`Exercise '${e}' requires engine: 'v4'`;throw this.reportError({type:"error",code:"invalid_exercise",message:p}),new Error(p)}let s=re(e)||e;if(Ge({engine:this.opts.engine,exercise:s})==="v4"){let p=this.engine.listExercises?.()??[],l=p.find(w=>w.id===e)??p.find(w=>w.id===s);if(l){this.beginEngineSession(this.v4ExerciseConfig(l),r);return}let x=this.getAvailableCustomExercises(),v=x.find(w=>w.id===e)??se(e,x);if(v){this.startCustomExercise(v,r);return}let _=`Exercise '${e}' is not available in V4 engine`;throw this.reportError({type:"error",code:"invalid_exercise",message:_}),new Error(_)}if(Se(s)&&W(this.opts.engine)==="v4"){this.beginEngineSession(this.v4ExerciseConfig({id:s||"squat",displayName:"Squat",type:"dynamic"}),r);return}let n=this.getAvailableExercises(),u=se(e,n);if(!u){let p=this.getAvailableCustomExercises(),l=p.find(v=>v.id===e)??se(e,p);if(l){this.startCustomExercise(l,r);return}let x=`Exercise '${e}' is not available in V3 engine`;throw this.reportError({type:"error",code:"invalid_exercise",message:x}),new Error(x)}this.beginEngineSession(u,r)}beginEngineSession(e,r){this.engine&&(this.stopExercise(),this.currentExerciseId=e.id,this.keypointsSuppressionLogged=!1,this.session=this.engine.createSession({exercise:e,locale:this.opts.locale??"en",difficulty:r.difficulty,minGrade:this.features.minGrade??void 0,v4CatalogSquat:this.opts.engine==="v4",features:{angles:this.features.angles,recommendations:this.features.recommendations,progression:this.features.progression}},s=>this.emitFromSession(s)))}startCustomExercise(e,r){if(!this.engine?.createCustomSession)throw new Error(`The cached engine bundle is too old for custom exercise '${e.id}' \u2014 reconnect so the SDK can update it.`);if(e.id==="jump_analysis"&&(!r.userHeightCm||r.userHeightCm<=0)){let s="User height (userHeightCm) must be provided for jump_analysis exercise";throw this.reportError({type:"error",code:"jump_analysis_missing_height",message:s}),new Error(s)}this.stopExercise(),this.currentExerciseId=e.id,this.keypointsSuppressionLogged=!1,this.session=this.engine.createCustomSession({exerciseId:e.id,locale:this.opts.locale??"en",userHeightCm:r.userHeightCm,devicePitchDeg:r.devicePitchDeg},s=>this.emitFromSession(s))}stopExercise(){try{this.session?.end()}catch(e){this.opts.onDiagnostic?.("[posetracker-web] engine session end() threw: "+(e instanceof Error?e.message:String(e)))}this.session=null,this.currentExerciseId=null,this.sessionErrorStreak=0,this.shell?.setPlacementVisible(!1)}getCurrentExerciseId(){return this.currentExerciseId}fetchSkeleton(e){return Ke(e,{baseUrl:this.opts.baseUrl})}async setModel(e,r){let s=this.running;s&&(this.running=!1,this.loopToken+=1,this.busy=!1),this.adapter?.dispose(),this.adapter=null,e!==void 0&&(this.opts.model=e),r!==void 0&&(this.opts.modelUrl=r),this.resolved=z({model:this.opts.model,modelUrl:this.opts.modelUrl}),await this.ensureAdapter(),s&&this.beginLoop()}updateOptions(e){this.opts={...this.opts,...e},e.skeletonDef!==void 0&&(this.skeletonDef=e.skeletonDef),e.skeletonUuid&&this.resolveSkeletonFromOptions(),this.applyWatermark(),this.shell&&(e.facingMode||e.position)&&this.shell.applyMirror(j(this.opts))}ingestPose(e){if(!this.session||this.features.keypoints?this.emit({type:"keypoints",keypoints:e.keypoints,score:e.score,timestampMs:e.timestampMs}):this.keypointsSuppressionLogged||(this.keypointsSuppressionLogged=!0,this.opts.onDiagnostic?.("[posetracker-web] keypoints events paused during exercise (features.keypoints=false)")),this.session)try{this.session.processPose(e),this.sessionErrorStreak=0}catch(r){this.sessionErrorStreak+=1;let s=r instanceof Error?r.message:String(r);if(this.sessionErrorStreak===1&&this.opts.onDiagnostic?.(`[posetracker-web] processPose threw: ${s}`),this.sessionErrorStreak>=cr){let i=this.currentExerciseId;this.session=null,this.currentExerciseId=null,this.sessionErrorStreak=0,this.reportError({type:"error",code:"internal",message:`Exercise session '${i??"?"}' failed repeatedly (${s}) \u2014 session stopped.`})}}}async doPreload(){this.setStatus("configuring"),await this.resolveManifestOnce(),this.setStatus("downloading"),await this.tryConfigure({silentStatus:!0}),this.tracker.flushQueue();try{this.setStatus("warming"),await this.ensureAdapter(),this.acceleration={...this.acceleration,state:"gpu",runtime:"tfjs-webgl",backend:"webgl"}}catch(e){throw this.reportError({type:"error",code:"model_load_failed",message:e instanceof Error?e.message:String(e)}),this.setStatus("error"),e}this.setStatus("ready"),this.emit({type:"initialization",step:"ready",message:"running",ready:!0,mode:this.mode,acceleration:this.getAcceleration()})}async ensureColdStartMode(e){e==="full"&&this.shell&&await this.ensureSource()}async ensureAdapter(){if(!this.adapter){if(this.resolved=z({model:this.opts.model,modelUrl:this.opts.modelUrl}),this.resolved.kind==="unsupported")throw new Error(this.resolved.unsupportedReason??"Unsupported model");this.emit({type:"initialization",step:"loading_pose_model",message:"loading pose model",ready:!1}),this.adapter=tt({resolved:this.resolved}),await this.adapter.load()}}async ensureSource(){if(!this.shell)throw new Error("Call mount() before opening a pose source");let e=this.poseSource;if(e.type==="camera"){if(this.stream&&this.estimateInput)return;this.emit({type:"initialization",step:"accessing_webcam",message:"accessing webcam (source=camera)",ready:!1});try{this.stream=await $e({facingMode:e.facingMode??j(this.opts),idealWidth:this.opts.idealWidth,idealHeight:this.opts.idealHeight})}catch(r){let s=r instanceof Error?r.message:String(r),i=/NotAllowed|Permission|denied/i.test(s);throw this.reportError({type:"error",code:i?"camera_denied":"camera_unavailable",message:s}),this.shell.setBootMessage(i?"Camera permission denied \u2014 allow access in the browser":"Camera unavailable",!0),r}this.shell.video.srcObject=this.stream,this.shell.applyMirror(e.facingMode??j(this.opts)),this.shell.setMediaMode("video");try{await this.shell.video.play()}catch{}this.estimateInput=this.shell.video,this.scheduleReveal("camera"),this.handleCameraStart({backend:this.acceleration.backend??"webgl",profileId:null});return}if(e.type==="video"){this.emit({type:"initialization",step:"loading_media",message:"loading video (source=video)",ready:!1});try{await this.attachVideoSource(e.src,{autoplay:e.autoplay!==!1,loop:e.loop!==!1,muted:e.muted!==!1})}catch(r){let s=r instanceof Error?r.message:String(r);throw this.reportError({type:"error",code:"media_load_failed",message:s}),this.shell.setBootMessage("Could not load video source",!0),r}this.shell.applyMirror("none"),this.shell.setMediaMode("video"),this.scheduleReveal("video");return}this.emit({type:"initialization",step:"loading_media",message:"loading image (source=image)",ready:!1});try{await this.attachImageSource(e.src)}catch(r){let s=r instanceof Error?r.message:String(r);throw this.reportError({type:"error",code:"media_load_failed",message:s}),this.shell.setBootMessage("Could not load image source",!0),r}this.shell.applyMirror("none"),this.shell.setMediaMode("image"),this.scheduleReveal("image")}scheduleReveal(e){let r=Date.now(),s=()=>{if(this.revealCamera(),!this.cameraRevealed){if(Date.now()-r>8e3){this.shell?.setBootMessage(`${e} loaded but preview is empty \u2014 check the file or CORS`,!0);return}setTimeout(s,120)}};setTimeout(s,60)}async attachVideoSource(e,r){if(!this.shell)throw new Error("no shell");let s=this.shell.video;if(ee(e)){if(e.parentElement&&e!==s){if(this.estimateInput=e,r.autoplay)try{await e.play()}catch{}await je(e);return}e.srcObject?s.srcObject=e.srcObject:(e.currentSrc||e.src)&&(s.src=e.currentSrc||e.src)}else{let i=await xe(e);this.objectUrlRevoke=i.revoke,s.src=i.url}if(s.loop=r.loop,s.muted=r.muted,s.playsInline=!0,await je(s),r.autoplay)try{await s.play()}catch{}this.estimateInput=s}async attachImageSource(e){if(!this.shell)throw new Error("no shell");if(ze(e)){let s=await vt(e);this.objectUrlRevoke=s.revoke,this.shell.image.src=s.url,await we(this.shell.image),this.estimateInput=e;return}if(yt(e)){if(e.complete&&e.naturalWidth>0){this.shell.image.src=e.currentSrc||e.src,this.estimateInput=e;return}await we(e),this.shell.image.src=e.currentSrc||e.src,this.estimateInput=e;return}let r=await xe(e);this.objectUrlRevoke=r.revoke,this.shell.image.src=r.url,await we(this.shell.image),this.estimateInput=this.shell.image}beginLoop(){this.running=!0,this.fpsTimer=performance.now(),this.loop(++this.loopToken)}frameReady(e){let r=te(e);return!(!(r.width>0&&r.height>0)||ee(e)&&(e.readyState<2||this.poseSource.type==="video"&&(e.paused||e.ended)))}async runOneFrame(){if(!this.adapter||!this.shell||!this.estimateInput||!this.frameReady(this.estimateInput))return null;let e=this.estimateInput,r=this.shell.canvas.clientWidth||this.shell.root.clientWidth||1,s=this.shell.canvas.clientHeight||this.shell.root.clientHeight||1,i=te(e),n=this.poseSource.type==="image"&&i.width>0&&i.height>0,u=n?i.width:r,p=n?i.height:s,l=this.poseSource.type==="camera"?j(this.opts):"environment",x=this.poseSource.type==="image"&&this.opts.stillMode==="tracked",v=await this.adapter.estimate(e,{facingMode:l,displayWidth:u,displayHeight:p,temporalSmooth:this.poseSource.type==="camera"||this.poseSource.type==="video"||x});if(!v)return null;let _=this.shell.canvas.getContext("2d");if(_&&this.opts.drawSkeleton!==!1){let b=et(v.videoKeypoints,v.letterbox,r,s);nt(_,b,r,s,!0,this.skeletonDef)}else _&&this.opts.drawSkeleton===!1&&_.clearRect(0,0,r,s);let w={keypoints:v.keypoints,score:v.score,timestampMs:Date.now()};this.ingestPose(w),this.inferMs.push(v.inferenceMs),this.inferMs.length>30&&this.inferMs.shift(),this.fpsWindow+=1;let g=performance.now();if(g-this.fpsTimer>=1e3){let b=this.fpsWindow;this.fpsWindow=0,this.fpsTimer=g;let E=hr(this.inferMs);this.acceleration.medianInferenceMs=E;let T=v.keypoints.filter(k=>k.score>=.3).length,C="getBackend"in this.adapter&&typeof this.adapter.getBackend=="function"?this.adapter.getBackend():null;this.acceleration.backend=C;let F=te(e);this.opts.debugHud&&this.shell.setHud(`${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${C??"?"} \xB7 ${b} fps \xB7 ${E!=null?Math.round(E)+" ms":"?"} \xB7 kp\u22650.3=${T}/17`,!0),this.emit({type:"stats",fps:b,medianInferenceMs:E,backend:C,keypointsAbove03:T,meanScore:v.score,videoSize:`${F.width}x${F.height}`,timestampMs:Date.now()})}return this.revealCamera(),w}async loop(e){if(e!==this.loopToken||!this.running||!this.shell||this.disposed)return;if(!this.adapter){requestAnimationFrame(()=>{this.loop(e)});return}let r=this.estimateInput;if(!!r&&!this.busy&&this.frameReady(r)&&(this.poseSource.type!=="image"||this.imageShotPending)){this.busy=!0;try{let n=await this.runOneFrame();if(this.poseSource.type==="image"&&n){this.imageShotPending=!1,this.running=!1,this.busy=!1;return}}catch(n){let u=n instanceof Error?n.message:String(n);if(this.emit({type:"error",code:"internal",message:`inference: ${u}`}),this.poseSource.type==="image"){this.imageShotPending=!1,this.running=!1,this.busy=!1;return}}this.busy=!1}if(e!==this.loopToken||!this.running)return;let i=r&&ee(r)?r:this.shell.video;if(this.poseSource.type!=="image"&&typeof i.requestVideoFrameCallback=="function"&&(this.poseSource.type==="video"||this.poseSource.type==="camera")){i.requestVideoFrameCallback(()=>{this.loop(e)});return}requestAnimationFrame(()=>{this.loop(e)})}async resolveSkeletonFromOptions(){if(this.opts.skeletonDef){this.skeletonDef=this.opts.skeletonDef;return}let e=this.opts.skeletonUuid?.trim();if(!e||e==="true"||e==="false"){this.skeletonDef=null;return}try{this.skeletonDef=await this.fetchSkeleton(e)}catch{this.skeletonDef=null}}resolveManifestOnce(){return this.handshakePromise||(this.handshakePromise=this.doResolveManifestOnce().then(e=>(e||(this.handshakePromise=null),e))),this.handshakePromise}async doResolveManifestOnce(){let e={poseRuntime:null,engine:await this.files?.read(Ce).catch(()=>null)??null};if(this.apiToken){let r=await this.resolveManifest(this.apiToken,e);return r&&(this.manifest=r,r.revoked===!0&&await this.handleRevocation("Access revoked by the backend.")),r}try{let r=await G(null,{...this.opts,localVersions:e});return this.manifest=r,r}catch{return null}}async handleRevocation(e){let r=await this.files?.read(Ce).catch(()=>null);r&&(await this.files?.remove(`engine-${r}.sealed`).catch(()=>{}),await this.files?.remove(Ce).catch(()=>{})),await this.files?.remove(Fe).catch(()=>{}),this.engine=null,this.engineSource=null,this.stopExercise(),this.setMode("keypoints-only"),this.reportError({type:"error",code:"invalid_token",message:e}),this.applyWatermark()}async handleCameraStart(e){this.lastCameraStartInfo=e;let r={backend:e.backend,profileId:e.profileId,poseModelProfile:this.opts.poseModelProfile??"AdaptiveChoice",mode:this.mode,exercise:this.currentExerciseId,model:this.opts.model??"movenet",modelUrl:this.opts.modelUrl??null};if(!this.apiToken){this.tracker.trackAnonymous({event:"camera_start",params:r});return}this.meteredSessionState="pending";try{await this.tracker.trackMetered({event:"camera_start",apiToken:this.apiToken,params:r}),this.meteredSessionState="validated",this.opts.onDiagnostic?.("[posetracker-web] camera_start tracked (metered)")}catch(s){this.meteredSessionState="refused",s instanceof O&&s.code==="network"?this.reportError({type:"error",code:"offline_metered",message:"API-key features are not available offline: PoseTracker cannot count their usage. Keypoints-only keeps running."}):s instanceof O&&s.code==="invalid_token"?await this.handleRevocation("API key invalid or revoked \u2014 engine cache purged."):s instanceof O&&s.code==="quota_exceeded"?this.reportError({type:"error",code:"quota_exceeded",message:s.message}):this.reportError({type:"error",code:"internal",message:`Usage tracking failed: ${s instanceof Error?s.message:String(s)}`})}}retryMeteredSessionIfRefused(){this.meteredSessionState==="refused"&&this.apiToken&&this.lastCameraStartInfo&&this.handleCameraStart(this.lastCameraStartInfo)}async tryConfigure({silentStatus:e,forceEngineRetry:r=!1}){if(this.mode==="full-engine")return!0;if(!this.apiToken)return this.validateRequestedFeatures(),this.setMode("keypoints-only"),this.applyWatermark(),!1;let s=await this.resolveManifestOnce();if(!s||s.revoked===!0)return this.setMode("keypoints-only"),this.applyWatermark(),!1;this.manifest=s,this.validateRequestedFeatures(),e||this.setStatus("downloading");let i=Ne(this.apiToken);r&&s.engine&&await this.engineLoader.clearGuard(s.engine);let n=await this.engineLoader.load(s.engine??null,i,{forceRetry:r});if(!n){let u=this.engineLoader.lastError;return this.reportError({type:"error",code:"engine_load_failed",message:u?`Engine bundle unavailable \u2014 ${u}`:"Engine bundle unavailable \u2014 running keypoints-only."}),this.setMode("keypoints-only"),this.applyWatermark(),!1}return this.engine=n.engine,this.engineSource=n.source,s.engine?.version&&await this.files?.write(Ce,s.engine.version).catch(()=>{}),this.setMode("full-engine"),this.applyWatermark(),!0}async resolveManifest(e,r){let s=Ne(e);try{let i=await G(e,{...this.opts,localVersions:r});return await this.files?.write(Fe,ge(JSON.stringify(i),s)).catch(()=>{}),i}catch(i){if(i instanceof U&&i.code==="invalid_token")return await this.handleRevocation(i.message),null;if(i instanceof U&&i.code==="quota_exceeded")return this.reportError({type:"error",code:"quota_exceeded",message:i.message}),null;let n=await this.files?.read(Fe);if(n){let u=ye(n,s);if(u)try{return JSON.parse(u)}catch{await this.files?.remove(Fe).catch(()=>{})}}return this.reportError({type:"error",code:"network",message:"Handshake unreachable and no cached session \u2014 running keypoints-only."}),null}}validateRequestedFeatures(){this.unsupportedFeatureKeys.length>0&&!this.featureGateReported.unsupported&&(this.featureGateReported.unsupported=!0,this.reportError({type:"error",code:"feature_not_supported",message:Pe(this.unsupportedFeatureKeys[0])}));let e=this.features;if(e.angles||e.recommendations||e.progression||e.keypoints){if(!this.apiToken){this.featureGateReported.missingToken||(this.featureGateReported.missingToken=!0,this.reportError({type:"error",code:"invalid_token",message:at}));return}this.getPlanType()==="free"&&Te(e,{withExercise:!1}).length>0&&!this.featureGateReported.freeBlock&&(this.featureGateReported.freeBlock=!0,this.reportError({type:"error",code:"free_plan_feature_blocked",message:he}))}}emitFromSession(e){if(!(e.type==="angles"&&!this.features.angles)&&!(e.type==="recommendations"&&!this.features.recommendations)&&!(e.type==="progression"&&!this.features.progression)){if(e.type==="posture"){let r=this.opts.drawPlacementBox!==!1;this.shell?.setPlacementVisible(r&&!e.ready,this.opts.placementPaddingPercent??10)}else e.type==="exercise_summary"&&this.shell?.setPlacementVisible(!1);this.emit(e)}}watermarkOn(){return typeof this.opts.showWatermark=="boolean"?this.opts.showWatermark:this.coldStartMode==="basic"&&!this.stream?!1:ue(this.getPlanType())}applyWatermark(){this.shell?.setWatermarkVisible(this.watermarkOn()&&this.cameraRevealed)}revealCamera(){if(!this.shell||this.cameraRevealed)return;let e=this.estimateInput;if(!e)return;let r=te(e);r.width>0&&r.height>0&&requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.cameraRevealed||!this.shell||(this.cameraRevealed=!0,this.poseSource.type==="image"?this.shell.setMediaMode("image"):this.shell.setMediaMode("video"),this.applyWatermark())})})}setStatus(e){this.status=e,(e==="configuring"||e==="downloading"||e==="warming")&&this.emit({type:"initialization",step:e,message:e==="configuring"?"checking you plan and access":e==="downloading"?"downloading engine":"loading pose model",ready:!1}),this.notifyState()}setMode(e){this.mode!==e&&(this.mode=e,this.notifyState())}reportError(e){this.lastError=e,this.emit(e),this.notifyState()}notifyState(){this.stateListeners.forEach(e=>{try{e()}catch{}})}emit(e){if(this.listeners.forEach(n=>{try{n(e)}catch(u){this.opts.onDiagnostic?.(`[posetracker-web] listener threw on '${e.type}': `+(u instanceof Error?u.message:String(u)))}}),this.typedListeners.get(e.type)?.forEach(n=>{try{n(e)}catch{}}),this.typedListeners.get("*")?.forEach(n=>{try{n(e)}catch{}}),this.messageListeners.size===0)return;let i=Xe(e);this.messageListeners.forEach(n=>{try{n(i)}catch{}})}};function Le(t={}){return new Ie(t.apiToken,t)}function Tt(t,e={}){let r=Le(e);return r.mount(t),r}var ct={mount:Tt};return Ot(ur);})();
90
+ `,t.appendChild(i);let n=i.querySelector("video"),d=i.querySelector("img.pt-still"),p=i.querySelector("canvas.pt-overlay"),l=i.querySelector(".pt-boot"),x=i.querySelector(".pt-boot-msg"),v=i.querySelector(".pt-wm"),k=i.querySelector(".pt-hud"),E=i.querySelector(".pt-placement");return{root:i,video:n,image:d,canvas:p,boot:l,bootMsg:x,watermark:v,hud:k,placement:E,setBootMessage(g,P=!1){x.textContent=g,x.classList.toggle("is-error",P)},setCameraVisible(g){g?this.setMediaMode(d.src?"image":"video"):this.setMediaMode("none")},setMediaMode(g){let P=g==="video",b=g==="image",T=P||b;n.style.opacity=P?"1":"0",d.style.opacity=b?"1":"0",p.style.opacity=T?"1":"0",l.classList.toggle("hide",T),T||(x.textContent=r,x.classList.remove("is-error"))},setWatermarkVisible(g){v.classList.toggle("show",g)},setHud(g,P){k.classList.toggle("debug",P),k.textContent=g},setPlacementVisible(g,P=10){if(E.classList.toggle("show",g),g){let b=Math.max(0,Math.min(40,P));E.style.setProperty("--pt-pad",`${b}%`)}},applyMirror(g){let P=g==="user"?"scaleX(-1)":"none";n.style.transform=P,d.style.transform="none",p.style.transform=P},destroy(){i.remove()}}}var ot={keypoints:["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"],keypoint_lines:["right_shoulder||left_shoulder","right_hip||left_hip","right_hip||right_shoulder","right_hip||right_knee","right_ankle||right_knee","right_shoulder||right_elbow","right_wrist||right_elbow","left_hip||left_shoulder","left_hip||left_knee","left_ankle||left_knee","left_shoulder||left_elbow","left_wrist||left_elbow"],keypoint_angles:[],angles:{fontColor:"#000000",strokeColor:"#FFC300"},circles:{circleFillColor:"#010A73",circleStrokeColor:"#FFC300",strokeWidth:"4",radius:"8"},lines:{lineStrokeColor:"#010A73",strokeWidth:"4"}};var it=.3;function nt(t,e,r,s,i=!0,n){if(t.canvas.width!==r&&(t.canvas.width=r),t.canvas.height!==s&&(t.canvas.height=s),t.clearRect(0,0,r,s),!i)return;let d=n??ot,p={};for(let b of e)p[b.name]=b;let l=d.lines.lineStrokeColor,x=Number(d.lines.strokeWidth);for(let b of d.keypoint_lines){let[T,L]=String(b).split("||"),F=p[T],S=p[L];!F||!S||F.score<=it||S.score<=it||(t.beginPath(),t.moveTo(F.dx,F.dy),t.lineTo(S.dx,S.dy),t.lineWidth=x,t.strokeStyle=l,t.stroke())}let v=d.circles.circleFillColor,k=d.circles.circleStrokeColor,E=Number(d.circles.strokeWidth),g=Number(d.circles.radius),P=new Set(d.keypoints);for(let b of e)!P.has(b.name)||b.score<=it||(t.beginPath(),t.arc(b.dx,b.dy,g,0,Math.PI*2),t.fillStyle=v,t.fill(),t.lineWidth=E,t.strokeStyle=k,t.stroke())}var Pt={angles:!1,recommendations:!1,progression:!1,keypoints:!1,minGrade:null},ar=new Set(["angles","recommendations","progression","keypoints","minGrade"]),lr={blazepose:'BlazePose as a features flag (use options.model = "blazepose" instead)',poseEngine:"pose engine selection (MediaPipe/PoseLandmarker)",mediapipeModel:"MediaPipe model selection",poseBackend:"pose backend selection",runInWorker:"worker thread selection"},he="You cannot use developer features. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)",at="Invalid params. Please refer to the documentation and set token=YOUR API_KEY et exercise=A correct exercise. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)";function Me(t){let e=lr[t]??`'${t}'`;return`The '${t}' option (${e}) is not available as a features flag. Remove the option, or use options.model / modelUrl for model selection. (visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)`}function lt(t){if(!t)return{features:{...Pt},unsupportedKeys:[]};let e=Object.keys(t).filter(r=>!ar.has(r));return{features:{angles:t.angles===!0,recommendations:t.recommendations===!0,progression:t.progression===!0,keypoints:t.keypoints===!0,minGrade:t.minGrade??null},unsupportedKeys:e}}function Te(t,e){let r=[];return t.angles&&r.push("angles"),t.recommendations&&r.push("recommendations"),t.progression&&r.push("progression"),t.keypoints&&e.withExercise&&r.push("keypoints"),r}function Mt(t){return typeof t=="string"&&t.length>0&&t!=="free"}function ue(t){return!Mt(t)}var Fe="session.sealed",Ce="engine.version",cr=30;function dr(t){if(typeof t=="string"){let e=document.querySelector(t);if(!e)throw new Error(`PoseTracker mount target not found: ${t}`);return e}return t}function hr(t){if(!t.length)return null;let e=[...t].sort((r,s)=>r-s);return e[Math.floor(e.length/2)]??null}async function ur(t){let e=t.base64?`data:${t.mime??"image/jpeg"};base64,${t.base64}`:t.uri;if(!e)throw new Error("processFrame: pass image, base64, or uri.");let r=await fetch(e);if(!r.ok)throw new Error(`processFrame: could not load frame (HTTP ${r.status}).`);return createImageBitmap(await r.blob())}function pr(t){return typeof HTMLCanvasElement<"u"&&t instanceof HTMLCanvasElement?{width:t.width,height:t.height}:Y(t)}function j(t){return t.facingMode?t.facingMode:t.position==="back"?"environment":"user"}var Ie=class{constructor(e,r={}){this.status="idle";this.mode="keypoints-only";this.lastError=null;this.manifest=null;this.engine=null;this.engineSource=null;this.session=null;this.currentExerciseId=null;this.listeners=new Set;this.typedListeners=new Map;this.messageListeners=new Set;this.stateListeners=new Set;this.preloadPromise=null;this.coldStartMode="basic";this.configurePromise=null;this.handshakePromise=null;this.meteredSessionState="idle";this.lastCameraStartInfo=null;this.sessionErrorStreak=0;this.featureGateReported={unsupported:!1,freeBlock:!1,missingToken:!1};this.keypointsSuppressionLogged=!1;this.shell=null;this.stream=null;this.poseSource={type:"camera"};this.objectUrlRevoke=null;this.estimateInput=null;this.imageShotPending=!1;this.adapter=null;this.running=!1;this.busy=!1;this.loopToken=0;this.cameraRevealed=!1;this.disposed=!1;this.skeletonDef=null;this.acceleration={state:"unknown",runtime:"unknown",backend:null,glRenderer:null,medianInferenceMs:null};this.inferMs=[];this.fpsWindow=0;this.fpsTimer=0;this.externalReady=!1;this.lastExternalPose=null;this.externalCollector=null;let s=e??(typeof r.apiToken=="string"?r.apiToken:null)??null;this.opts={...r,onDiagnostic:r.onDiagnostic},this.apiToken=s;let i=lt(r.features);this.features=i.features,this.unsupportedFeatureKeys=i.unsupportedKeys,this.resolved=z({model:r.model,modelUrl:r.modelUrl}),this.skeletonDef=r.skeletonDef??null,this.coldStartMode=r.coldStart==="full"?"full":"basic";try{this.poseSource=Ee(r.source,{sourceUrl:r.sourceUrl,sourceFile:r.sourceFile,facingMode:j(r)})}catch{this.poseSource={type:"camera",facingMode:j(r)}}this.files=r.fileStore!==void 0?r.fileStore:xe(),this.engineLoader=r.engineLoader??new Q({fileStore:this.files,onDiagnostic:this.opts.onDiagnostic}),this.tracker=r.usageTracker??new ee({baseUrl:r.baseUrl}),this.opts.onDiagnostic?.(`[posetracker-web] client created hasApiToken=${!!this.apiToken} features=${JSON.stringify(this.features)}`)}getStatus(){return this.status}getMode(){return this.mode}getError(){return this.lastError}getManifest(){return this.manifest}getPlanType(){return this.manifest?.plan?.plan??null}getFeatures(){return{...this.features}}getEngineSource(){return this.engineSource}getAcceleration(){return this.acceleration.state}getAccelerationDiagnostics(){return{...this.acceleration}}getResolvedModel(){return this.resolved}getColdStartMode(){return this.coldStartMode}getSource(){return this.poseSource}addEventListener(e){return this.listeners.add(e),()=>this.listeners.delete(e)}on(e,r){if(e==="*")return this.listeners.add(r),()=>this.listeners.delete(r);let s=this.typedListeners.get(e);return s||(s=new Set,this.typedListeners.set(e,s)),s.add(r),()=>s.delete(r)}addMessageListener(e){return this.messageListeners.add(e),()=>this.messageListeners.delete(e)}onStateChange(e){return this.stateListeners.add(e),()=>this.stateListeners.delete(e)}mount(e){if(this.disposed)throw new Error("PoseTracker disposed");this.shell&&this.shell.destroy();let r=dr(e);r.innerHTML="",this.shell=st(r,{loadingText:this.opts.loadingText??de}),this.shell.setCameraVisible(!1),this.cameraRevealed=!1,this.resolveSkeletonFromOptions()}preload(e){let r=e?.coldStart==="full"?"full":"basic";if(this.coldStartMode=r,this.status==="ready"&&this.preloadPromise)return this.preloadPromise.then(()=>this.ensureColdStartMode(r));if(!this.preloadPromise)this.preloadPromise=this.doPreload().then(()=>this.ensureColdStartMode(r)).catch(s=>{throw this.preloadPromise=null,s});else if(r==="full")return this.preloadPromise.then(()=>this.ensureColdStartMode("full"));return this.preloadPromise}warmup(e){return this.preload(e)}async start(){if(this.disposed)throw new Error("PoseTracker disposed");if(!this.shell)throw new Error("Call mount() before start()");let e=this.poseSource.type==="camera"||this.poseSource.type==="video"||this.poseSource.type==="image";await this.preload({coldStart:this.opts.coldStart==="basic"&&this.poseSource.type==="camera"?"basic":e?"full":"basic"}),(this.coldStartMode==="basic"||!this.estimateInput)&&await this.ensureSource(),this.poseSource.type==="image"&&(this.imageShotPending=!0),this.beginLoop()}async setSource(e,r){if(this.disposed)throw new Error("PoseTracker disposed");let s=this.running;this.stopMediaOnly(),this.poseSource=Ee(e,{sourceUrl:r?.sourceUrl??this.opts.sourceUrl,sourceFile:r?.sourceFile??this.opts.sourceFile,facingMode:r?.facingMode??j(this.opts)}),this.opts.source=this.poseSource,this.poseSource.type==="camera"&&this.poseSource.facingMode&&(this.opts.facingMode=this.poseSource.facingMode),this.adapter?.resetTemporal&&this.adapter.resetTemporal(),this.shell&&(await this.ensureAdapter(),await this.ensureSource(),(s||this.poseSource.type==="image")&&(this.poseSource.type==="image"&&(this.imageShotPending=!0),this.beginLoop()))}async analyze(){if(this.disposed)throw new Error("PoseTracker disposed");if(!this.shell)throw new Error("Call mount() before analyze()");return await this.ensureAdapter(),this.estimateInput||await this.ensureSource(),this.poseSource.type==="image"?(this.imageShotPending=!0,this.running||this.beginLoop(),null):this.runOneFrame()}async warmupExternal(){if(this.disposed)throw new Error("PoseTracker disposed");this.preloadPromise?await this.preloadPromise:await this.preload({coldStart:"basic"}),await this.ensureAdapter(),this.externalReady||(this.externalReady=!0,this.handleCameraStart({backend:this.acceleration.backend??"webgl",profileId:"external-frames"}))}async processFrame(e){if(this.disposed)throw new Error("PoseTracker disposed");if(!this.externalReady||!this.adapter)throw new Error("processFrame: call warmupExternal() and wait for it to resolve first.");if(this.running||this.stream)throw new Error("processFrame: the SDK camera or a media source is running on this client. External frames and start() cannot run together.");if(this.busy)return{dropped:!0,pose:this.lastExternalPose,events:[]};this.busy=!0;let r=[];this.externalCollector=r;let s=null;try{let i;e.image?i=e.image:(s=await ur(e),i=s);let n=pr(i);if(!(n.width>0&&n.height>0))throw new Error("processFrame: frame has no pixels.");let d=await this.adapter.estimate(i,{facingMode:e.mirrored===!1?"environment":"user",displayWidth:n.width,displayHeight:n.height,temporalSmooth:!0});if(!d)return{dropped:!1,pose:null,events:r};let p={keypoints:d.keypoints,score:d.score,timestampMs:typeof e.timestampMs=="number"?e.timestampMs:Date.now()};return this.lastExternalPose=p,this.ingestPose(p),this.recordInference(d,n),{dropped:!1,pose:p,events:r}}finally{s?.close(),this.externalCollector=null,this.busy=!1}}stop(){this.running=!1,this.loopToken+=1,this.busy=!1,this.imageShotPending=!1,this.stopMediaOnly(),this.shell&&(this.shell.setMediaMode("none"),this.shell.setPlacementVisible(!1),this.cameraRevealed=!1,this.applyWatermark()),this.status!=="error"&&this.setStatus("idle")}stopMediaOnly(){if(We(this.stream),this.stream=null,this.estimateInput=null,this.objectUrlRevoke&&(this.objectUrlRevoke(),this.objectUrlRevoke=null),this.shell){try{this.shell.video.pause()}catch{}this.shell.video.removeAttribute("src"),this.shell.video.srcObject=null,this.shell.video.load(),this.shell.image.removeAttribute("src")}}async dispose(){this.stopExercise(),this.stop(),this.adapter?.dispose(),this.adapter=null,this.shell?.destroy(),this.shell=null,this.listeners.clear(),this.typedListeners.clear(),this.messageListeners.clear(),this.stateListeners.clear(),this.preloadPromise=null,this.configurePromise=null,this.handshakePromise=null,this.meteredSessionState="idle",this.lastCameraStartInfo=null,this.featureGateReported={unsupported:!1,freeBlock:!1,missingToken:!1},this.keypointsSuppressionLogged=!1,this.externalReady=!1,this.lastExternalPose=null,this.externalCollector=null,this.engine=null,this.engineSource=null,this.manifest=null,this.mode="keypoints-only",this.setStatus("idle"),this.disposed=!0}configure(e){return e!==void 0&&e!==this.apiToken&&(this.apiToken=e,this.opts.apiToken=e,this.configurePromise=null,this.handshakePromise=null,this.featureGateReported.freeBlock=!1,this.featureGateReported.missingToken=!1),this.mode!=="full-engine"&&(this.configurePromise=null),this.configurePromise||(this.configurePromise=this.tryConfigure({silentStatus:this.status==="ready",forceEngineRetry:!0}).then(r=>(r||(this.configurePromise=null),this.retryMeteredSessionIfRefused(),r)).catch(()=>(this.configurePromise=null,!1))),this.configurePromise}getAvailableExercises(){if(this.mode!=="full-engine")return[];if(W(this.opts.engine)==="v4"){let e=this.engine?.listExercises?.()??[];if(e.length)return e.map(r=>this.v4ExerciseConfig(r))}return this.manifest?.exercises??[]}getEngineChannel(){return W(this.opts.engine)}v4ExerciseConfig(e){let r=e.type==="static"?"static":"dynamic";return{id:e.id,name:e.displayName||e.id,type:r,movement:{name:e.id,type:r,scale_acceptance:{},movement_steps:[],movement_initial_posture:null}}}getAvailableCustomExercises(){return this.mode!=="full-engine"||!this.engine?.listCustomExercises?[]:this.engine.listCustomExercises()}startExercise(e,r={}){if(this.status!=="ready")throw new Error(`Cannot start exercise while status is '${this.status}' \u2014 call preload()/start() first.`);if(this.mode!=="full-engine"||!this.engine)throw new Error("Exercises require full-engine mode (validated API key). The SDK is running keypoints-only \u2014 call configure(apiToken) first.");if(this.meteredSessionState==="refused")throw new Error("API-key features are not available offline: PoseTracker could not track this session. Reconnect and retry.");if(this.unsupportedFeatureKeys.length>0){let p=Me(this.unsupportedFeatureKeys[0]);throw this.reportError({type:"error",code:"feature_not_supported",message:p}),new Error(p)}if(this.getPlanType()==="free"&&Te(this.features,{withExercise:!0}).length>0)throw this.reportError({type:"error",code:"free_plan_feature_blocked",message:he}),new Error(he);if(Ye(e)&&W(this.opts.engine)!=="v4"){let p=`Exercise '${e}' requires engine: 'v4'`;throw this.reportError({type:"error",code:"invalid_exercise",message:p}),new Error(p)}let s=re(e)||e;if(Ge({engine:this.opts.engine,exercise:s})==="v4"){let p=this.engine.listExercises?.()??[],l=p.find(E=>E.id===e)??p.find(E=>E.id===s);if(l){this.beginEngineSession(this.v4ExerciseConfig(l),r);return}let x=this.getAvailableCustomExercises(),v=x.find(E=>E.id===e)??se(e,x);if(v){this.startCustomExercise(v,r);return}let k=`Exercise '${e}' is not available in V4 engine`;throw this.reportError({type:"error",code:"invalid_exercise",message:k}),new Error(k)}if(ke(s)&&W(this.opts.engine)==="v4"){this.beginEngineSession(this.v4ExerciseConfig({id:s||"squat",displayName:"Squat",type:"dynamic"}),r);return}let n=this.getAvailableExercises(),d=se(e,n);if(!d){let p=this.getAvailableCustomExercises(),l=p.find(v=>v.id===e)??se(e,p);if(l){this.startCustomExercise(l,r);return}let x=`Exercise '${e}' is not available in V3 engine`;throw this.reportError({type:"error",code:"invalid_exercise",message:x}),new Error(x)}this.beginEngineSession(d,r)}beginEngineSession(e,r){this.engine&&(this.stopExercise(),this.currentExerciseId=e.id,this.keypointsSuppressionLogged=!1,this.session=this.engine.createSession({exercise:e,locale:this.opts.locale??"en",difficulty:r.difficulty,minGrade:this.features.minGrade??void 0,v4CatalogSquat:this.opts.engine==="v4",features:{angles:this.features.angles,recommendations:this.features.recommendations,progression:this.features.progression}},s=>this.emitFromSession(s)))}startCustomExercise(e,r){if(!this.engine?.createCustomSession)throw new Error(`The cached engine bundle is too old for custom exercise '${e.id}' \u2014 reconnect so the SDK can update it.`);if(e.id==="jump_analysis"&&(!r.userHeightCm||r.userHeightCm<=0)){let s="User height (userHeightCm) must be provided for jump_analysis exercise";throw this.reportError({type:"error",code:"jump_analysis_missing_height",message:s}),new Error(s)}this.stopExercise(),this.currentExerciseId=e.id,this.keypointsSuppressionLogged=!1,this.session=this.engine.createCustomSession({exerciseId:e.id,locale:this.opts.locale??"en",userHeightCm:r.userHeightCm,devicePitchDeg:r.devicePitchDeg},s=>this.emitFromSession(s))}stopExercise(){try{this.session?.end()}catch(e){this.opts.onDiagnostic?.("[posetracker-web] engine session end() threw: "+(e instanceof Error?e.message:String(e)))}this.session=null,this.currentExerciseId=null,this.sessionErrorStreak=0,this.shell?.setPlacementVisible(!1)}getCurrentExerciseId(){return this.currentExerciseId}fetchSkeleton(e){return Ke(e,{baseUrl:this.opts.baseUrl})}async setModel(e,r){let s=this.running;s&&(this.running=!1,this.loopToken+=1,this.busy=!1),this.adapter?.dispose(),this.adapter=null,e!==void 0&&(this.opts.model=e),r!==void 0&&(this.opts.modelUrl=r),this.resolved=z({model:this.opts.model,modelUrl:this.opts.modelUrl}),await this.ensureAdapter(),s&&this.beginLoop()}updateOptions(e){this.opts={...this.opts,...e},e.skeletonDef!==void 0&&(this.skeletonDef=e.skeletonDef),e.skeletonUuid&&this.resolveSkeletonFromOptions(),this.applyWatermark(),this.shell&&(e.facingMode||e.position)&&this.shell.applyMirror(j(this.opts))}ingestPose(e){if(!this.session||this.features.keypoints?this.emit({type:"keypoints",keypoints:e.keypoints,score:e.score,timestampMs:e.timestampMs}):this.keypointsSuppressionLogged||(this.keypointsSuppressionLogged=!0,this.opts.onDiagnostic?.("[posetracker-web] keypoints events paused during exercise (features.keypoints=false)")),this.session)try{this.session.processPose(e),this.sessionErrorStreak=0}catch(r){this.sessionErrorStreak+=1;let s=r instanceof Error?r.message:String(r);if(this.sessionErrorStreak===1&&this.opts.onDiagnostic?.(`[posetracker-web] processPose threw: ${s}`),this.sessionErrorStreak>=cr){let i=this.currentExerciseId;this.session=null,this.currentExerciseId=null,this.sessionErrorStreak=0,this.reportError({type:"error",code:"internal",message:`Exercise session '${i??"?"}' failed repeatedly (${s}) \u2014 session stopped.`})}}}async doPreload(){this.setStatus("configuring"),await this.resolveManifestOnce(),this.setStatus("downloading"),await this.tryConfigure({silentStatus:!0}),this.tracker.flushQueue();try{this.setStatus("warming"),await this.ensureAdapter(),this.acceleration={...this.acceleration,state:"gpu",runtime:"tfjs-webgl",backend:"webgl"}}catch(e){throw this.reportError({type:"error",code:"model_load_failed",message:e instanceof Error?e.message:String(e)}),this.setStatus("error"),e}this.setStatus("ready"),this.emit({type:"initialization",step:"ready",message:"running",ready:!0,mode:this.mode,acceleration:this.getAcceleration()})}async ensureColdStartMode(e){e==="full"&&this.shell&&await this.ensureSource()}async ensureAdapter(){if(!this.adapter){if(this.resolved=z({model:this.opts.model,modelUrl:this.opts.modelUrl}),this.resolved.kind==="unsupported")throw new Error(this.resolved.unsupportedReason??"Unsupported model");this.emit({type:"initialization",step:"loading_pose_model",message:"loading pose model",ready:!1}),this.adapter=tt({resolved:this.resolved}),await this.adapter.load()}}async ensureSource(){if(!this.shell)throw new Error("Call mount() before opening a pose source");let e=this.poseSource;if(e.type==="camera"){if(this.stream&&this.estimateInput)return;this.emit({type:"initialization",step:"accessing_webcam",message:"accessing webcam (source=camera)",ready:!1});try{this.stream=await $e({facingMode:e.facingMode??j(this.opts),idealWidth:this.opts.idealWidth,idealHeight:this.opts.idealHeight})}catch(r){let s=r instanceof Error?r.message:String(r),i=/NotAllowed|Permission|denied/i.test(s);throw this.reportError({type:"error",code:i?"camera_denied":"camera_unavailable",message:s}),this.shell.setBootMessage(i?"Camera permission denied \u2014 allow access in the browser":"Camera unavailable",!0),r}this.shell.video.srcObject=this.stream,this.shell.applyMirror(e.facingMode??j(this.opts)),this.shell.setMediaMode("video");try{await this.shell.video.play()}catch{}this.estimateInput=this.shell.video,this.scheduleReveal("camera"),this.handleCameraStart({backend:this.acceleration.backend??"webgl",profileId:null});return}if(e.type==="video"){this.emit({type:"initialization",step:"loading_media",message:"loading video (source=video)",ready:!1});try{await this.attachVideoSource(e.src,{autoplay:e.autoplay!==!1,loop:e.loop!==!1,muted:e.muted!==!1})}catch(r){let s=r instanceof Error?r.message:String(r);throw this.reportError({type:"error",code:"media_load_failed",message:s}),this.shell.setBootMessage("Could not load video source",!0),r}this.shell.applyMirror("none"),this.shell.setMediaMode("video"),this.scheduleReveal("video");return}this.emit({type:"initialization",step:"loading_media",message:"loading image (source=image)",ready:!1});try{await this.attachImageSource(e.src)}catch(r){let s=r instanceof Error?r.message:String(r);throw this.reportError({type:"error",code:"media_load_failed",message:s}),this.shell.setBootMessage("Could not load image source",!0),r}this.shell.applyMirror("none"),this.shell.setMediaMode("image"),this.scheduleReveal("image")}scheduleReveal(e){let r=Date.now(),s=()=>{if(this.revealCamera(),!this.cameraRevealed){if(Date.now()-r>8e3){this.shell?.setBootMessage(`${e} loaded but preview is empty \u2014 check the file or CORS`,!0);return}setTimeout(s,120)}};setTimeout(s,60)}async attachVideoSource(e,r){if(!this.shell)throw new Error("no shell");let s=this.shell.video;if(te(e)){if(e.parentElement&&e!==s){if(this.estimateInput=e,r.autoplay)try{await e.play()}catch{}await je(e);return}e.srcObject?s.srcObject=e.srcObject:(e.currentSrc||e.src)&&(s.src=e.currentSrc||e.src)}else{let i=await ve(e);this.objectUrlRevoke=i.revoke,s.src=i.url}if(s.loop=r.loop,s.muted=r.muted,s.playsInline=!0,await je(s),r.autoplay)try{await s.play()}catch{}this.estimateInput=s}async attachImageSource(e){if(!this.shell)throw new Error("no shell");if(ze(e)){let s=await xt(e);this.objectUrlRevoke=s.revoke,this.shell.image.src=s.url,await we(this.shell.image),this.estimateInput=e;return}if(yt(e)){if(e.complete&&e.naturalWidth>0){this.shell.image.src=e.currentSrc||e.src,this.estimateInput=e;return}await we(e),this.shell.image.src=e.currentSrc||e.src,this.estimateInput=e;return}let r=await ve(e);this.objectUrlRevoke=r.revoke,this.shell.image.src=r.url,await we(this.shell.image),this.estimateInput=this.shell.image}beginLoop(){this.running=!0,this.fpsTimer=performance.now(),this.loop(++this.loopToken)}frameReady(e){let r=Y(e);return!(!(r.width>0&&r.height>0)||te(e)&&(e.readyState<2||this.poseSource.type==="video"&&(e.paused||e.ended)))}async runOneFrame(){if(!this.adapter||!this.shell||!this.estimateInput||!this.frameReady(this.estimateInput))return null;let e=this.estimateInput,r=this.shell.canvas.clientWidth||this.shell.root.clientWidth||1,s=this.shell.canvas.clientHeight||this.shell.root.clientHeight||1,i=Y(e),n=this.poseSource.type==="image"&&i.width>0&&i.height>0,d=n?i.width:r,p=n?i.height:s,l=this.poseSource.type==="camera"?j(this.opts):"environment",x=this.poseSource.type==="image"&&this.opts.stillMode==="tracked",v=await this.adapter.estimate(e,{facingMode:l,displayWidth:d,displayHeight:p,temporalSmooth:this.poseSource.type==="camera"||this.poseSource.type==="video"||x});if(!v)return null;let k=this.shell.canvas.getContext("2d");if(k&&this.opts.drawSkeleton!==!1){let g=et(v.videoKeypoints,v.letterbox,r,s);nt(k,g,r,s,!0,this.skeletonDef)}else k&&this.opts.drawSkeleton===!1&&k.clearRect(0,0,r,s);let E={keypoints:v.keypoints,score:v.score,timestampMs:Date.now()};return this.ingestPose(E),this.recordInference(v,Y(e)),this.revealCamera(),E}recordInference(e,r){this.inferMs.push(e.inferenceMs),this.inferMs.length>30&&this.inferMs.shift(),this.fpsWindow+=1;let s=performance.now();if(s-this.fpsTimer<1e3)return;let i=this.fpsWindow;this.fpsWindow=0,this.fpsTimer=s;let n=hr(this.inferMs);this.acceleration.medianInferenceMs=n;let d=e.keypoints.filter(x=>x.score>=.3).length,p=this.adapter,l=p&&"getBackend"in p&&typeof p.getBackend=="function"?p.getBackend():null;this.acceleration.backend=l,this.opts.debugHud&&this.shell&&this.shell.setHud(`${this.resolved.modelId} \xB7 ${this.poseSource.type} \xB7 ${l??"?"} \xB7 ${i} fps \xB7 ${n!=null?Math.round(n)+" ms":"?"} \xB7 kp\u22650.3=${d}/17`,!0),this.emit({type:"stats",fps:i,medianInferenceMs:n,backend:l,keypointsAbove03:d,meanScore:e.score,videoSize:`${r.width}x${r.height}`,timestampMs:Date.now()})}async loop(e){if(e!==this.loopToken||!this.running||!this.shell||this.disposed)return;if(!this.adapter){requestAnimationFrame(()=>{this.loop(e)});return}let r=this.estimateInput;if(!!r&&!this.busy&&this.frameReady(r)&&(this.poseSource.type!=="image"||this.imageShotPending)){this.busy=!0;try{let n=await this.runOneFrame();if(this.poseSource.type==="image"&&n){this.imageShotPending=!1,this.running=!1,this.busy=!1;return}}catch(n){let d=n instanceof Error?n.message:String(n);if(this.emit({type:"error",code:"internal",message:`inference: ${d}`}),this.poseSource.type==="image"){this.imageShotPending=!1,this.running=!1,this.busy=!1;return}}this.busy=!1}if(e!==this.loopToken||!this.running)return;let i=r&&te(r)?r:this.shell.video;if(this.poseSource.type!=="image"&&typeof i.requestVideoFrameCallback=="function"&&(this.poseSource.type==="video"||this.poseSource.type==="camera")){i.requestVideoFrameCallback(()=>{this.loop(e)});return}requestAnimationFrame(()=>{this.loop(e)})}async resolveSkeletonFromOptions(){if(this.opts.skeletonDef){this.skeletonDef=this.opts.skeletonDef;return}let e=this.opts.skeletonUuid?.trim();if(!e||e==="true"||e==="false"){this.skeletonDef=null;return}try{this.skeletonDef=await this.fetchSkeleton(e)}catch{this.skeletonDef=null}}resolveManifestOnce(){return this.handshakePromise||(this.handshakePromise=this.doResolveManifestOnce().then(e=>(e||(this.handshakePromise=null),e))),this.handshakePromise}async doResolveManifestOnce(){let e={poseRuntime:null,engine:await this.files?.read(Ce).catch(()=>null)??null};if(this.apiToken){let r=await this.resolveManifest(this.apiToken,e);return r&&(this.manifest=r,r.revoked===!0&&await this.handleRevocation("Access revoked by the backend.")),r}try{let r=await G(null,{...this.opts,localVersions:e});return this.manifest=r,r}catch{return null}}async handleRevocation(e){let r=await this.files?.read(Ce).catch(()=>null);r&&(await this.files?.remove(`engine-${r}.sealed`).catch(()=>{}),await this.files?.remove(Ce).catch(()=>{})),await this.files?.remove(Fe).catch(()=>{}),this.engine=null,this.engineSource=null,this.stopExercise(),this.setMode("keypoints-only"),this.reportError({type:"error",code:"invalid_token",message:e}),this.applyWatermark()}async handleCameraStart(e){this.lastCameraStartInfo=e;let r={backend:e.backend,profileId:e.profileId,poseModelProfile:this.opts.poseModelProfile??"AdaptiveChoice",mode:this.mode,exercise:this.currentExerciseId,model:this.opts.model??"movenet",modelUrl:this.opts.modelUrl??null};if(!this.apiToken){this.tracker.trackAnonymous({event:"camera_start",params:r});return}this.meteredSessionState="pending";try{await this.tracker.trackMetered({event:"camera_start",apiToken:this.apiToken,params:r}),this.meteredSessionState="validated",this.opts.onDiagnostic?.("[posetracker-web] camera_start tracked (metered)")}catch(s){this.meteredSessionState="refused",s instanceof O&&s.code==="network"?this.reportError({type:"error",code:"offline_metered",message:"API-key features are not available offline: PoseTracker cannot count their usage. Keypoints-only keeps running."}):s instanceof O&&s.code==="invalid_token"?await this.handleRevocation("API key invalid or revoked \u2014 engine cache purged."):s instanceof O&&s.code==="quota_exceeded"?this.reportError({type:"error",code:"quota_exceeded",message:s.message}):this.reportError({type:"error",code:"internal",message:`Usage tracking failed: ${s instanceof Error?s.message:String(s)}`})}}retryMeteredSessionIfRefused(){this.meteredSessionState==="refused"&&this.apiToken&&this.lastCameraStartInfo&&this.handleCameraStart(this.lastCameraStartInfo)}async tryConfigure({silentStatus:e,forceEngineRetry:r=!1}){if(this.mode==="full-engine")return!0;if(!this.apiToken)return this.validateRequestedFeatures(),this.setMode("keypoints-only"),this.applyWatermark(),!1;let s=await this.resolveManifestOnce();if(!s||s.revoked===!0)return this.setMode("keypoints-only"),this.applyWatermark(),!1;this.manifest=s,this.validateRequestedFeatures(),e||this.setStatus("downloading");let i=Ne(this.apiToken);r&&s.engine&&await this.engineLoader.clearGuard(s.engine);let n=await this.engineLoader.load(s.engine??null,i,{forceRetry:r});if(!n){let d=this.engineLoader.lastError;return this.reportError({type:"error",code:"engine_load_failed",message:d?`Engine bundle unavailable \u2014 ${d}`:"Engine bundle unavailable \u2014 running keypoints-only."}),this.setMode("keypoints-only"),this.applyWatermark(),!1}return this.engine=n.engine,this.engineSource=n.source,s.engine?.version&&await this.files?.write(Ce,s.engine.version).catch(()=>{}),this.setMode("full-engine"),this.applyWatermark(),!0}async resolveManifest(e,r){let s=Ne(e);try{let i=await G(e,{...this.opts,localVersions:r});return await this.files?.write(Fe,ge(JSON.stringify(i),s)).catch(()=>{}),i}catch(i){if(i instanceof U&&i.code==="invalid_token")return await this.handleRevocation(i.message),null;if(i instanceof U&&i.code==="quota_exceeded")return this.reportError({type:"error",code:"quota_exceeded",message:i.message}),null;let n=await this.files?.read(Fe);if(n){let d=ye(n,s);if(d)try{return JSON.parse(d)}catch{await this.files?.remove(Fe).catch(()=>{})}}return this.reportError({type:"error",code:"network",message:"Handshake unreachable and no cached session \u2014 running keypoints-only."}),null}}validateRequestedFeatures(){this.unsupportedFeatureKeys.length>0&&!this.featureGateReported.unsupported&&(this.featureGateReported.unsupported=!0,this.reportError({type:"error",code:"feature_not_supported",message:Me(this.unsupportedFeatureKeys[0])}));let e=this.features;if(e.angles||e.recommendations||e.progression||e.keypoints){if(!this.apiToken){this.featureGateReported.missingToken||(this.featureGateReported.missingToken=!0,this.reportError({type:"error",code:"invalid_token",message:at}));return}this.getPlanType()==="free"&&Te(e,{withExercise:!1}).length>0&&!this.featureGateReported.freeBlock&&(this.featureGateReported.freeBlock=!0,this.reportError({type:"error",code:"free_plan_feature_blocked",message:he}))}}emitFromSession(e){if(!(e.type==="angles"&&!this.features.angles)&&!(e.type==="recommendations"&&!this.features.recommendations)&&!(e.type==="progression"&&!this.features.progression)){if(e.type==="posture"){let r=this.opts.drawPlacementBox!==!1;this.shell?.setPlacementVisible(r&&!e.ready,this.opts.placementPaddingPercent??10)}else e.type==="exercise_summary"&&this.shell?.setPlacementVisible(!1);this.emit(e)}}watermarkOn(){return typeof this.opts.showWatermark=="boolean"?this.opts.showWatermark:this.coldStartMode==="basic"&&!this.stream?!1:ue(this.getPlanType())}applyWatermark(){this.shell?.setWatermarkVisible(this.watermarkOn()&&this.cameraRevealed)}revealCamera(){if(!this.shell||this.cameraRevealed)return;let e=this.estimateInput;if(!e)return;let r=Y(e);r.width>0&&r.height>0&&requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.cameraRevealed||!this.shell||(this.cameraRevealed=!0,this.poseSource.type==="image"?this.shell.setMediaMode("image"):this.shell.setMediaMode("video"),this.applyWatermark())})})}setStatus(e){this.status=e,(e==="configuring"||e==="downloading"||e==="warming")&&this.emit({type:"initialization",step:e,message:e==="configuring"?"checking you plan and access":e==="downloading"?"downloading engine":"loading pose model",ready:!1}),this.notifyState()}setMode(e){this.mode!==e&&(this.mode=e,this.notifyState())}reportError(e){this.lastError=e,this.emit(e),this.notifyState()}notifyState(){this.stateListeners.forEach(e=>{try{e()}catch{}})}emit(e){if(this.externalCollector?.push(e),this.listeners.forEach(n=>{try{n(e)}catch(d){this.opts.onDiagnostic?.(`[posetracker-web] listener threw on '${e.type}': `+(d instanceof Error?d.message:String(d)))}}),this.typedListeners.get(e.type)?.forEach(n=>{try{n(e)}catch{}}),this.typedListeners.get("*")?.forEach(n=>{try{n(e)}catch{}}),this.messageListeners.size===0)return;let i=Xe(e);this.messageListeners.forEach(n=>{try{n(i)}catch{}})}};function Le(t={}){return new Ie(t.apiToken,t)}function Tt(t,e={}){let r=Le(e);return r.mount(t),r}var ct={mount:Tt};return Ot(mr);})();
83
91
  /*! Bundled license information:
84
92
 
85
93
  js-sha256/src/sha256.js:
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@pose-tracker/pose-estimation-web",
3
- "version": "0.3.4",
4
- "private": false,
3
+ "version": "0.3.6",
5
4
  "description": "PoseTracker web pose estimation SDK (vanilla) — MoveNet via TensorFlow.js + remote model URL. Exercise engine via API token. No bundled weights.",
6
5
  "type": "module",
7
6
  "main": "./dist/index.cjs",
@@ -31,6 +30,7 @@
31
30
  "scripts": {
32
31
  "build": "tsup",
33
32
  "typecheck": "tsc -p tsconfig.json --noEmit",
33
+ "test": "node --test \"test/*.test.mjs\"",
34
34
  "prepack": "npm run build",
35
35
  "pack:dry": "npm pack --dry-run"
36
36
  },
@@ -52,7 +52,7 @@
52
52
  "url": "https://www.posetracker.com"
53
53
  },
54
54
  "license": "SEE LICENSE IN LICENSE",
55
- "homepage": "https://www.posetracker.com",
55
+ "homepage": "https://github.com/Movelytics/pose-estimation-web#readme",
56
56
  "repository": {
57
57
  "type": "git",
58
58
  "url": "git+https://github.com/Movelytics/pose-estimation-web.git"
@@ -76,5 +76,8 @@
76
76
  "@tensorflow/tfjs": "^4.22.0",
77
77
  "tsup": "^8.4.0",
78
78
  "typescript": "^5.4.0"
79
+ },
80
+ "bugs": {
81
+ "url": "https://github.com/Movelytics/pose-estimation-web/issues"
79
82
  }
80
83
  }