@jaak.ai/stamps 2.1.0-dev.8 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
  3. package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
  4. package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
  5. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
  6. package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
  7. package/dist/cjs/jaak-stamps.cjs.entry.js +144 -409
  8. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  9. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  10. package/dist/cjs/loader.cjs.js +4 -3
  11. package/dist/cjs/loader.cjs.js.map +1 -1
  12. package/dist/collection/collection-manifest.json +1 -1
  13. package/dist/collection/components/my-component/my-component.css +1 -1
  14. package/dist/collection/components/my-component/my-component.js +53 -295
  15. package/dist/collection/components/my-component/my-component.js.map +1 -1
  16. package/dist/collection/services/CameraService.js +84 -2
  17. package/dist/collection/services/CameraService.js.map +1 -1
  18. package/dist/collection/services/DetectionService.js +7 -146
  19. package/dist/collection/services/DetectionService.js.map +1 -1
  20. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
  21. package/dist/components/index.js +165 -113
  22. package/dist/components/index.js.map +1 -1
  23. package/dist/components/jaak-stamps.js +145 -412
  24. package/dist/components/jaak-stamps.js.map +1 -1
  25. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  26. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  27. package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
  28. package/dist/esm/index-Drbzcuq-.js.map +1 -0
  29. package/dist/esm/jaak-stamps-webcomponent.js +5 -4
  30. package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
  31. package/dist/esm/jaak-stamps.entry.js +144 -409
  32. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  33. package/dist/esm/loader.js +4 -3
  34. package/dist/esm/loader.js.map +1 -1
  35. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  36. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
  37. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  38. package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
  39. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
  40. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
  41. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
  42. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
  43. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
  44. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
  45. package/dist/types/components/my-component/my-component.d.ts +1 -27
  46. package/dist/types/components.d.ts +1 -3
  47. package/dist/types/services/CameraService.d.ts +1 -0
  48. package/dist/types/services/DetectionService.d.ts +1 -9
  49. package/dist/types/services/interfaces/IDetectionService.d.ts +0 -6
  50. package/dist/types/stencil-public-runtime.d.ts +12 -5
  51. package/package.json +3 -1
  52. package/dist/cjs/index-BfhtOB0D.js.map +0 -1
  53. package/dist/esm/index-BP1Q4KOg.js.map +0 -1
  54. package/dist/jaak-stamps-webcomponent/p-8e25497e.entry.js +0 -2
  55. package/dist/jaak-stamps-webcomponent/p-8e25497e.entry.js.map +0 -1
  56. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
  57. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-BfhtOB0D.js');
3
+ var index = require('./index-Ga0t6BMe.js');
4
4
 
5
5
  class EventBusService {
6
6
  events = new Map();
@@ -206,7 +206,21 @@ class CameraService {
206
206
  tempStream.getTracks().forEach(track => track.stop());
207
207
  }
208
208
  const devices = await navigator.mediaDevices.enumerateDevices();
209
- this.availableCameras = devices.filter(device => device.kind === 'videoinput');
209
+ const allCameras = devices.filter(device => device.kind === 'videoinput');
210
+ // Filter out virtual cameras
211
+ this.availableCameras = allCameras.filter(camera => {
212
+ const isVirtual = this.isVirtualCamera(camera);
213
+ if (isVirtual) {
214
+ console.log(`Virtual camera detected and filtered: ${camera.label}`);
215
+ }
216
+ return !isVirtual;
217
+ });
218
+ // Check if there are physical cameras available
219
+ if (this.availableCameras.length === 0) {
220
+ console.log('No physical cameras available');
221
+ this.eventBus.emit('error', new Error('Cámaras virtuales no están permitidas. Use una cámara física.'));
222
+ return [];
223
+ }
210
224
  // Cache the results for optimization
211
225
  CameraService.deviceEnumerationCache = {
212
226
  devices: this.availableCameras,
@@ -256,6 +270,21 @@ class CameraService {
256
270
  });
257
271
  this.lastStreamConstraints = finalConstraints;
258
272
  const stream = await this.currentStreamPromise;
273
+ // Validate that active stream is not from a virtual camera
274
+ const videoTrack = stream.getVideoTracks()[0];
275
+ if (videoTrack) {
276
+ const settings = videoTrack.getSettings();
277
+ const deviceId = settings.deviceId;
278
+ if (deviceId) {
279
+ const devices = await navigator.mediaDevices.enumerateDevices();
280
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
281
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
282
+ console.log(`Virtual camera detected in active stream: ${currentDevice.label}`);
283
+ stream.getTracks().forEach(track => track.stop());
284
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
285
+ }
286
+ }
287
+ }
259
288
  return stream;
260
289
  }
261
290
  catch (error) {
@@ -273,7 +302,23 @@ class CameraService {
273
302
  audio: false
274
303
  });
275
304
  this.lastStreamConstraints = basicConstraints;
276
- return await this.currentStreamPromise;
305
+ const stream = await this.currentStreamPromise;
306
+ // Validate fallback stream as well
307
+ const videoTrack = stream.getVideoTracks()[0];
308
+ if (videoTrack) {
309
+ const settings = videoTrack.getSettings();
310
+ const deviceId = settings.deviceId;
311
+ if (deviceId) {
312
+ const devices = await navigator.mediaDevices.enumerateDevices();
313
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
314
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
315
+ console.log(`Virtual camera detected in fallback stream: ${currentDevice.label}`);
316
+ stream.getTracks().forEach(track => track.stop());
317
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
318
+ }
319
+ }
320
+ }
321
+ return stream;
277
322
  }
278
323
  }
279
324
  async switchCamera(cameraId) {
@@ -282,6 +327,12 @@ class CameraService {
282
327
  await this.enumerateDevices();
283
328
  return;
284
329
  }
330
+ // Validate that the target camera is not virtual
331
+ if (this.isVirtualCamera(selectedCamera)) {
332
+ console.log(`Attempted to switch to virtual camera: ${selectedCamera.label}`);
333
+ this.eventBus.emit('error', new Error('No se puede cambiar a cámara virtual'));
334
+ return;
335
+ }
285
336
  await this.setSelectedCamera(cameraId);
286
337
  }
287
338
  isRearCamera(stream) {
@@ -553,6 +604,37 @@ class CameraService {
553
604
  invalidateDeviceCache() {
554
605
  CameraService.deviceEnumerationCache = null;
555
606
  }
607
+ isVirtualCamera(device) {
608
+ const label = device.label.toLowerCase();
609
+ const virtualCameraIndicators = [
610
+ 'obs',
611
+ 'virtual',
612
+ 'snap camera',
613
+ 'manycam',
614
+ 'xsplit',
615
+ 'camtwist',
616
+ 'ecamm',
617
+ 'nvidia broadcast',
618
+ 'droidcam',
619
+ 'iriun',
620
+ 'elgato',
621
+ 'streamlabs',
622
+ 'wirecast',
623
+ 'zoom virtual',
624
+ 'teams virtual',
625
+ 'mmhmm',
626
+ 'camo',
627
+ 'reincubate camo',
628
+ 'webcamoid',
629
+ 'splitcam',
630
+ 'cheese',
631
+ 'guvcview',
632
+ 'yawcam',
633
+ 'webcam 7',
634
+ 'altserver'
635
+ ];
636
+ return virtualCameraIndicators.some(indicator => label.includes(indicator));
637
+ }
556
638
  }
557
639
 
558
640
  class LowMemoryDeviceStrategy {
@@ -650,10 +732,6 @@ class DetectionService {
650
732
  // Static canvas pool for reuse across instances
651
733
  static canvasPool = new Map();
652
734
  static MAX_POOL_SIZE = 5;
653
- // Resource management for optimization
654
- modelLoadController;
655
- classificationLoadController;
656
- modelBlobUrls = new Set();
657
735
  constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
658
736
  this.debug = debug;
659
737
  this.useDocumentClassification = useDocumentClassification;
@@ -671,26 +749,11 @@ class DetectionService {
671
749
  return;
672
750
  }
673
751
  try {
674
- // Create AbortController for this load operation
675
- this.modelLoadController = new AbortController();
676
752
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
677
753
  try {
678
- // Load model with abort signal support
679
- const response = await fetch(this.MODEL_PATH, {
680
- signal: this.modelLoadController.signal
681
- });
682
- if (!response.ok) {
683
- throw new Error(`Failed to fetch model: ${response.status}`);
684
- }
685
- const modelBuffer = await response.arrayBuffer();
686
- this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
754
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
687
755
  }
688
756
  catch (error) {
689
- // Handle abort signal
690
- if (error.name === 'AbortError') {
691
- console.log('Model loading was cancelled');
692
- return;
693
- }
694
757
  // Múltiples tipos de errores de memoria que pueden ocurrir
695
758
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
696
759
  error.message.includes('Out of memory') ||
@@ -712,15 +775,7 @@ class DetectionService {
712
775
  sessionLogVerbosityLevel: 0,
713
776
  };
714
777
  try {
715
- // Retry with fallback options and abort signal
716
- const fallbackResponse = await fetch(this.MODEL_PATH, {
717
- signal: this.modelLoadController.signal
718
- });
719
- if (!fallbackResponse.ok) {
720
- throw new Error(`Failed to fetch model with fallback: ${fallbackResponse.status}`);
721
- }
722
- const fallbackBuffer = await fallbackResponse.arrayBuffer();
723
- this.session = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
778
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
724
779
  }
725
780
  catch (fallbackError) {
726
781
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -741,13 +796,9 @@ class DetectionService {
741
796
  return;
742
797
  }
743
798
  try {
744
- // Create AbortController for classification model loading
745
- this.classificationLoadController = new AbortController();
746
799
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
747
800
  try {
748
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
749
- signal: this.classificationLoadController.signal
750
- });
801
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
751
802
  if (classResponse.ok) {
752
803
  this.mobileNetClassMap = await classResponse.json();
753
804
  }
@@ -765,22 +816,9 @@ class DetectionService {
765
816
  // Load model
766
817
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
767
818
  try {
768
- // Load classification model with abort signal support
769
- const response = await fetch(this.MOBILENET_MODEL_PATH, {
770
- signal: this.classificationLoadController.signal
771
- });
772
- if (!response.ok) {
773
- throw new Error(`Failed to fetch classification model: ${response.status}`);
774
- }
775
- const modelBuffer = await response.arrayBuffer();
776
- this.mobileNetSession = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
819
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
777
820
  }
778
821
  catch (error) {
779
- // Handle abort signal
780
- if (error.name === 'AbortError') {
781
- console.log('Classification model loading was cancelled');
782
- return;
783
- }
784
822
  // Múltiples tipos de errores de memoria que pueden ocurrir
785
823
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
786
824
  error.message.includes('Out of memory') ||
@@ -802,15 +840,7 @@ class DetectionService {
802
840
  sessionLogVerbosityLevel: 0,
803
841
  };
804
842
  try {
805
- // Retry with fallback options and abort signal
806
- const fallbackResponse = await fetch(this.MOBILENET_MODEL_PATH, {
807
- signal: this.classificationLoadController.signal
808
- });
809
- if (!fallbackResponse.ok) {
810
- throw new Error(`Failed to fetch classification model with fallback: ${fallbackResponse.status}`);
811
- }
812
- const fallbackBuffer = await fallbackResponse.arrayBuffer();
813
- this.mobileNetSession = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
843
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
814
844
  }
815
845
  catch (fallbackError) {
816
846
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -1000,9 +1030,6 @@ class DetectionService {
1000
1030
  return alignment.top && alignment.right && alignment.bottom && alignment.left;
1001
1031
  }
1002
1032
  cleanup() {
1003
- // Cancel any ongoing requests
1004
- this.abortPendingRequests();
1005
- // Cleanup ONNX resources
1006
1033
  this.cleanupCanvasPool();
1007
1034
  if (this.session) {
1008
1035
  this.session.release?.();
@@ -1014,8 +1041,6 @@ class DetectionService {
1014
1041
  }
1015
1042
  this.mobileNetClassMap = undefined;
1016
1043
  this.modelLoaded = false;
1017
- // Cleanup blob URLs
1018
- this.revokeModelBlobUrls();
1019
1044
  }
1020
1045
  initializeCanvasPool() {
1021
1046
  this.preprocessCanvas = document.createElement('canvas');
@@ -1113,93 +1138,11 @@ class DetectionService {
1113
1138
  const pool = DetectionService.canvasPool.get(key);
1114
1139
  // Only return to pool if not at max capacity
1115
1140
  if (pool.length < DetectionService.MAX_POOL_SIZE) {
1116
- const ctx = canvas.getContext('2d');
1117
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1118
1141
  pool.push(canvas);
1119
1142
  }
1120
- // If pool is full, let the canvas be garbage collected
1121
1143
  }
1122
- // Resource management methods for cache and browser optimization
1123
- async clearModelCache() {
1124
- try {
1125
- // Clear cache specific to models
1126
- const cacheNames = await caches.keys();
1127
- const modelCaches = cacheNames.filter(name => name.includes('onnx') ||
1128
- name.includes('jaak-static') ||
1129
- name.includes('squeezenet') ||
1130
- name.includes('ddmyp'));
1131
- await Promise.all(modelCaches.map(cacheName => caches.delete(cacheName)));
1132
- if (this.debug) {
1133
- console.log(`🧹 Cleared ${modelCaches.length} model caches`);
1134
- }
1135
- }
1136
- catch (error) {
1137
- console.warn('Error clearing model cache:', error);
1138
- }
1139
- }
1140
- abortPendingRequests() {
1141
- // Cancel any fetch in progress
1142
- if (this.modelLoadController) {
1143
- this.modelLoadController.abort();
1144
- this.modelLoadController = undefined;
1145
- }
1146
- if (this.classificationLoadController) {
1147
- this.classificationLoadController.abort();
1148
- this.classificationLoadController = undefined;
1149
- }
1150
- }
1151
- revokeModelBlobUrls() {
1152
- // Revoke any blob URLs created for models
1153
- this.modelBlobUrls.forEach(url => URL.revokeObjectURL(url));
1154
- this.modelBlobUrls.clear();
1155
- }
1156
- // Progressive resource release for optimization
1157
- async releaseMobileNetResources() {
1158
- if (this.mobileNetSession) {
1159
- this.mobileNetSession.release?.();
1160
- this.mobileNetSession = undefined;
1161
- }
1162
- this.mobileNetClassMap = undefined;
1163
- if (this.classificationLoadController) {
1164
- this.classificationLoadController.abort();
1165
- this.classificationLoadController = undefined;
1166
- }
1167
- if (this.debug) {
1168
- console.log('🧹 Released MobileNet classification resources');
1169
- }
1170
- }
1171
- // Optimize canvas pool by reducing to specific size
1172
- optimizeCanvasPool(maxSize) {
1173
- DetectionService.canvasPool.forEach((pool, key) => {
1174
- while (pool.length > maxSize) {
1175
- const canvas = pool.pop();
1176
- // Explicitly clear and nullify for garbage collection
1177
- if (canvas) {
1178
- const ctx = canvas.getContext('2d');
1179
- if (ctx) {
1180
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1181
- }
1182
- }
1183
- }
1184
- if (pool.length === 0) {
1185
- DetectionService.canvasPool.delete(key);
1186
- }
1187
- });
1188
- if (this.debug) {
1189
- console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
1190
- }
1191
- }
1192
- // Clear all static canvas pools
1193
- static clearAllCanvasPools() {
1194
- DetectionService.canvasPool.forEach((pool) => {
1195
- pool.forEach(canvas => {
1196
- const ctx = canvas.getContext('2d');
1197
- if (ctx) {
1198
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1199
- }
1200
- });
1201
- pool.length = 0;
1202
- });
1144
+ // Static method to clear all canvas pools (useful for memory management)
1145
+ static clearCanvasPools() {
1203
1146
  DetectionService.canvasPool.clear();
1204
1147
  }
1205
1148
  // Method to get pool statistics for debugging
@@ -1258,7 +1201,7 @@ class ServiceContainer {
1258
1201
  }
1259
1202
  }
1260
1203
 
1261
- const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
1204
+ const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
1262
1205
 
1263
1206
  const JaakStamps = class {
1264
1207
  constructor(hostRef) {
@@ -1336,6 +1279,7 @@ const JaakStamps = class {
1336
1279
  alignmentStartTime;
1337
1280
  alignmentTimer;
1338
1281
  backDocumentTimer;
1282
+ orientationTimer;
1339
1283
  // Performance monitoring
1340
1284
  performanceMetrics = {
1341
1285
  fps: 0,
@@ -1439,7 +1383,7 @@ const JaakStamps = class {
1439
1383
  if (!window.ort) {
1440
1384
  this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
1441
1385
  const script = document.createElement('script');
1442
- script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
1386
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
1443
1387
  document.head.appendChild(script);
1444
1388
  await new Promise((resolve) => {
1445
1389
  script.onload = () => {
@@ -1520,6 +1464,18 @@ const JaakStamps = class {
1520
1464
  });
1521
1465
  resizeObserver.observe(this.detectionContainer.parentElement);
1522
1466
  }
1467
+ // Handle mobile device orientation changes
1468
+ window.addEventListener('orientationchange', () => {
1469
+ // Clear any existing orientation timer
1470
+ if (this.orientationTimer) {
1471
+ clearTimeout(this.orientationTimer);
1472
+ }
1473
+ // Add delay to ensure video dimensions are updated after rotation
1474
+ this.orientationTimer = window.setTimeout(() => {
1475
+ this.handleResize();
1476
+ this.orientationTimer = undefined;
1477
+ }, 300);
1478
+ });
1523
1479
  }
1524
1480
  handleResize() {
1525
1481
  if (this.detectionContainer) {
@@ -1676,13 +1632,7 @@ const JaakStamps = class {
1676
1632
  }
1677
1633
  try {
1678
1634
  this.exitSession();
1679
- // Aggressive cleanup when stopping capture
1680
- const cleanupResult = await this.aggressiveResourceCleanup();
1681
- return {
1682
- success: true,
1683
- resourcesFreed: cleanupResult.freed,
1684
- cleanupErrors: cleanupResult.errors
1685
- };
1635
+ return { success: true };
1686
1636
  }
1687
1637
  catch (error) {
1688
1638
  return { success: false, error: error.message };
@@ -1907,60 +1857,11 @@ const JaakStamps = class {
1907
1857
  // Always allow getting capture delay, even during initialization
1908
1858
  return this.captureDelay;
1909
1859
  }
1910
- async getResourceReport() {
1911
- // Public method to get resource usage report
1912
- return this.getResourceReportInternal();
1913
- }
1914
- async forceResourceCleanup() {
1915
- // Public method to force aggressive resource cleanup
1916
- const readyCheck = this.isComponentReady();
1917
- if (!readyCheck.ready) {
1918
- return { success: false, error: readyCheck.message };
1919
- }
1920
- try {
1921
- const cleanupResult = await this.aggressiveResourceCleanup();
1922
- return {
1923
- success: true,
1924
- resourcesFreed: cleanupResult.freed,
1925
- errors: cleanupResult.errors,
1926
- timestamp: new Date().toISOString()
1927
- };
1928
- }
1929
- catch (error) {
1930
- return { success: false, error: error.message };
1931
- }
1932
- }
1933
1860
  // Memory and device capability detection
1934
1861
  checkDeviceCapabilities() {
1935
1862
  const deviceMemory = navigator.deviceMemory;
1936
- const hardwareConcurrency = navigator.hardwareConcurrency || 1;
1937
- const isLowEndDevice = hardwareConcurrency <= 2;
1938
- const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1939
- const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
1940
- // Si tenemos info de memoria del dispositivo
1941
- if (deviceMemory && deviceMemory < 4) {
1942
- return {
1943
- canUseML: false,
1944
- reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
1945
- memoryMB: deviceMemory * 1024
1946
- };
1947
- }
1948
- // Dispositivos móviles de gama baja
1949
- if (isMobile && isLowEndDevice) {
1950
- return {
1951
- canUseML: false,
1952
- reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
1953
- memoryMB: null
1954
- };
1955
- }
1956
- // Safari tiene limitaciones conocidas con WASM
1957
- if (isSafari) {
1958
- return {
1959
- canUseML: false,
1960
- reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
1961
- memoryMB: null
1962
- };
1963
- }
1863
+ // Siempre intentar usar ML, independientemente del dispositivo
1864
+ // Las restricciones anteriores no eran confiables
1964
1865
  return {
1965
1866
  canUseML: true,
1966
1867
  reason: 'Dispositivo compatible con detección automática.',
@@ -2086,6 +1987,12 @@ const JaakStamps = class {
2086
1987
  this.videoRef.onloadedmetadata = async () => {
2087
1988
  await this.videoRef.play();
2088
1989
  this.stateManager.updateCaptureState({ isVideoActive: true });
1990
+ // Recalculate mask dimensions when new camera stream loads
1991
+ if (this.detectionContainer) {
1992
+ const container = this.detectionContainer.parentElement;
1993
+ const rect = container.getBoundingClientRect();
1994
+ this.updateMaskDimensions(rect);
1995
+ }
2089
1996
  resolve();
2090
1997
  };
2091
1998
  });
@@ -2257,6 +2164,10 @@ const JaakStamps = class {
2257
2164
  clearTimeout(this.alignmentTimer);
2258
2165
  this.alignmentTimer = undefined;
2259
2166
  }
2167
+ if (this.orientationTimer) {
2168
+ clearTimeout(this.orientationTimer);
2169
+ this.orientationTimer = undefined;
2170
+ }
2260
2171
  // Liberar recursos de ONNX al limpiar el componente
2261
2172
  this.releaseOnnxResources();
2262
2173
  if (this.performanceUpdateInterval) {
@@ -2265,182 +2176,11 @@ const JaakStamps = class {
2265
2176
  }
2266
2177
  // Clear canvas pool
2267
2178
  this.canvasPool = [];
2268
- // Clean DOM references and memory leaks
2269
- this.cleanupDOMReferences();
2270
2179
  this.detectionBoxes = [];
2271
2180
  this.alignmentStartTime = undefined;
2272
2181
  this.hasDocumentDetected = false;
2273
2182
  this.serviceContainer?.cleanup();
2274
2183
  }
2275
- // AGGRESSIVE RESOURCE CLEANUP METHODS
2276
- // Clean DOM references and memory leaks
2277
- cleanupDOMReferences() {
2278
- // Remove event listeners that could maintain references
2279
- if (this.videoRef) {
2280
- // Remove common event listeners (if any were added)
2281
- this.videoRef.srcObject = null;
2282
- this.videoRef = undefined;
2283
- }
2284
- // Clean detection container
2285
- if (this.detectionContainer) {
2286
- this.detectionContainer = undefined;
2287
- }
2288
- // Clean references to images in the DOM and revoke blob URLs
2289
- const images = this.el.shadowRoot?.querySelectorAll('img');
2290
- images?.forEach(img => {
2291
- if (img.src.startsWith('blob:')) {
2292
- URL.revokeObjectURL(img.src);
2293
- }
2294
- img.src = '';
2295
- });
2296
- }
2297
- // Clear browser cache and storage
2298
- async clearBrowserCache() {
2299
- try {
2300
- // 1. Clear Service Worker cache if exists
2301
- if ('serviceWorker' in navigator) {
2302
- const registrations = await navigator.serviceWorker.getRegistrations();
2303
- await Promise.all(registrations.map(registration => registration.unregister()));
2304
- }
2305
- // 2. Clear Cache API specific to the component
2306
- const cacheNames = await caches.keys();
2307
- const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2308
- await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2309
- // 3. Force garbage collection if available (only in dev)
2310
- if (this.debug && window.gc) {
2311
- window.gc();
2312
- }
2313
- }
2314
- catch (error) {
2315
- console.warn('Error clearing browser cache:', error);
2316
- }
2317
- }
2318
- // Clear local storage specific to the component
2319
- async clearLocalStorage() {
2320
- try {
2321
- // Clear localStorage specific to the component
2322
- const keysToRemove = [];
2323
- for (let i = 0; i < localStorage.length; i++) {
2324
- const key = localStorage.key(i);
2325
- if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2326
- keysToRemove.push(key);
2327
- }
2328
- }
2329
- keysToRemove.forEach(key => localStorage.removeItem(key));
2330
- // Clear sessionStorage
2331
- const sessionKeysToRemove = [];
2332
- for (let i = 0; i < sessionStorage.length; i++) {
2333
- const key = sessionStorage.key(i);
2334
- if (key && (key.includes('jaak') || key.includes('onnx'))) {
2335
- sessionKeysToRemove.push(key);
2336
- }
2337
- }
2338
- sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2339
- }
2340
- catch (error) {
2341
- console.warn('Error clearing local storage:', error);
2342
- }
2343
- }
2344
- // Aggressive resource cleanup combining all strategies
2345
- async aggressiveResourceCleanup() {
2346
- const freed = [];
2347
- const errors = [];
2348
- try {
2349
- // 1. Release ONNX resources
2350
- this.releaseOnnxResources();
2351
- freed.push('ONNX models');
2352
- // 2. Clear model cache in detection service
2353
- if (this.detectionService) {
2354
- await this.detectionService.clearModelCache();
2355
- freed.push('Model cache');
2356
- }
2357
- // 3. Clear browser cache
2358
- await this.clearBrowserCache();
2359
- freed.push('Browser cache');
2360
- // 4. Clear local storage
2361
- await this.clearLocalStorage();
2362
- freed.push('Local storage');
2363
- // 5. Clean DOM references
2364
- this.cleanupDOMReferences();
2365
- freed.push('DOM references');
2366
- // 6. Optimize canvas pools
2367
- if (this.detectionService) {
2368
- this.detectionService.optimizeCanvasPool(0);
2369
- freed.push('Canvas pools');
2370
- }
2371
- // 7. Clear component canvas pool
2372
- this.canvasPool = [];
2373
- freed.push('Component canvas pool');
2374
- // 8. Force garbage collection (if available)
2375
- if (this.debug && window.gc) {
2376
- window.gc();
2377
- freed.push('Garbage collection');
2378
- }
2379
- }
2380
- catch (error) {
2381
- errors.push(error.message);
2382
- }
2383
- return { freed, errors };
2384
- }
2385
- // Progressive resource release after front capture
2386
- async onFrontCaptureComplete() {
2387
- try {
2388
- // Release classification model if not needed for back
2389
- if (this.detectionService && !this.useDocumentClassification) {
2390
- await this.detectionService.releaseMobileNetResources();
2391
- }
2392
- // Optimize canvas pool to minimum needed for back capture
2393
- if (this.detectionService) {
2394
- this.detectionService.optimizeCanvasPool(1);
2395
- }
2396
- // Reduce component canvas pool
2397
- while (this.canvasPool.length > 1) {
2398
- const canvas = this.canvasPool.pop();
2399
- if (canvas) {
2400
- const ctx = canvas.getContext('2d');
2401
- if (ctx) {
2402
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2403
- }
2404
- }
2405
- }
2406
- if (this.debug) {
2407
- console.log('🧹 Progressive cleanup after front capture completed');
2408
- }
2409
- }
2410
- catch (error) {
2411
- console.warn('Error in progressive cleanup:', error);
2412
- }
2413
- }
2414
- // Resource reporting for debugging
2415
- getResourceReportInternal() {
2416
- const report = {
2417
- timestamp: new Date().toISOString(),
2418
- component: {
2419
- hasVideoStream: !!this.videoStream,
2420
- hasAnimationFrame: !!this.animationId,
2421
- canvasPoolSize: this.canvasPool.length,
2422
- detectionBoxesCount: this.detectionBoxes.length,
2423
- performanceMonitorActive: !!this.performanceUpdateInterval
2424
- }
2425
- };
2426
- // Add detection service stats if available
2427
- if (this.detectionService) {
2428
- report.detectionService = {
2429
- isModelLoaded: this.detectionService.isModelLoaded(),
2430
- canvasPoolStats: this.detectionService.getPoolStats()
2431
- };
2432
- }
2433
- // Add memory info if available
2434
- if ('memory' in performance) {
2435
- const memInfo = performance.memory;
2436
- report.memory = {
2437
- usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2438
- totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2439
- jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2440
- };
2441
- }
2442
- return report;
2443
- }
2444
2184
  render() {
2445
2185
  const captureState = this.stateManager?.getCaptureState() || {
2446
2186
  isVideoActive: false,
@@ -2450,7 +2190,7 @@ const JaakStamps = class {
2450
2190
  isCapturing: false
2451
2191
  };
2452
2192
  const cameraInfo = this.cameraInfoWithAutofocus;
2453
- return (index.h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, index.h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, index.h("video", { key: 'bb310544c17ae734d8b42ed2617748c2f69743ff', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
2193
+ return (index.h("div", { key: 'f9cbca2fd6b8548a29ace7fcb972b53f402f61af', class: "detector-container" }, index.h("div", { key: '4f3cac734eb6bfd44a8de644eadca17e7893e11a', class: "video-container" }, index.h("video", { key: '5e15a38c989a72d5274281272d41ba064447ca6f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '38c8ad8b691db60e9b5a2a8b641fe6febbeacfca', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
2454
2194
  position: 'absolute',
2455
2195
  left: `${box.x}px`,
2456
2196
  top: `${box.y}px`,
@@ -2459,9 +2199,9 @@ const JaakStamps = class {
2459
2199
  border: '2px solid #32406C',
2460
2200
  pointerEvents: 'none',
2461
2201
  boxSizing: 'border-box'
2462
- } })))), this.isMaskReady && (index.h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, index.h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, index.h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), index.h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), index.h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), index.h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, index.h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2202
+ } })))), this.isMaskReady && (index.h("div", { key: 'c56fde56b7a86a4e8a4cf558745291ea5cde12fd', class: "overlay-mask" }, index.h("div", { key: '04ed3f01f271dc84eed594cb4b60c2453645bd41', class: "card-outline" }, index.h("div", { key: '648bd423a5e886d4768115237de5896c7223b30b', class: "side side-top" }), index.h("div", { key: 'b8878059493ca61961b9b3c82ecc18b50b79ac85', class: "side side-right" }), index.h("div", { key: '09e5ad64ff33dbd5c112bf52b71e14409ab6aa65', class: "side side-bottom" }), index.h("div", { key: 'b8ba3d83c9bc1b4a0da859d7ed4b9a15eaddcc2a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '3279701a240f77ac96e68a34ad95c099340c9a6a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '2adf96d0a57e482372fd9586229dac2301b64fd4', class: "back-capture-section" }, index.h("div", { key: '305a147c9cfee854c60ac946a48a1d21b2792725', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (index.h("button", { key: '43270a0647f39307229c540dd5130fb58b1ff4fb', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: 'eb4666603774564eef95a120ac13faeb9e10c8b4', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: 'cac667d9548ae2314ad5a677c5ed90f25184f728', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '0af7aa4106ce189ae2d79bec78ede01abe25b1e7', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2463
2203
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
2464
- : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, index.h("button", { key: 'e4327d260b988b67e0de3654c72f24455cd3bbbd', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, index.h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, index.h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, index.h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, index.h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, index.h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), index.h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, index.h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), index.h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), index.h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, index.h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, index.h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, index.h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, index.h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, index.h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), index.h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, index.h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), index.h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, index.h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, index.h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), index.h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, index.h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), index.h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, index.h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, index.h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), index.h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, index.h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), index.h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, index.h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2204
+ : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'de6e5c4fad4d4d996b894457c9af52bc71e533fb', class: "camera-controls" }, index.h("button", { key: 'cfddcf2420d5f5923a6b02ca446bbe1b8e9fc5e4', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '5fb73f958b9a5a6f9242032c4530b0bdab84f6c5', class: "camera-selector-dropdown" }, index.h("div", { key: '8ee2d26b1001e571e7b6c326e927926fa08287fd', class: "camera-selector-header" }, index.h("span", { key: '78350b0fb1bd933f7c8c59d8dab0b7122ffbe363' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'bb750e0ddd5a1ee5ff96bd57805a7f90b8821e9f', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'd0f6343cf1b7367f3da33ec8cf64891109d923b8', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: 'c9642fbfb0acea766d3f0542f4cc9f17f4af4d7b', class: "device-info" }, index.h("small", { key: 'b6e9507ff7addf011f339f6c22a88a96b40db68a' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '04ae9262409284782850b90945eb7038673e2097', class: "manual-capture-section" }, index.h("button", { key: '27c315e503ccba468b89ebd58d6b70f40ddd06ba', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '8f2dcd2df9a218b37eb0b8204a8724362cfeaa53', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '6fd6fe1d0213b01c8119a67fb8108f73227feee1', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: 'fba3816b48b99ca64510ecbefc0d41431569c9ae', class: "flip-animation" }, index.h("div", { key: 'beea1c8245b5d81f1008efc6d1b079a61c821e7f', class: "id-card-icon" }), index.h("div", { key: 'f0fbc34f619a653e2c0ade764ca99e7aa582d157', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'e4a0e6d31112e3a50024d1d0430b4ad997049fe7', class: "success-animation" }, index.h("div", { key: '5dd3f781f55630bc904c81207548ae35bc79b452', class: "check-icon" }), index.h("div", { key: 'a10ebc557582325091efb0abd12d62b27a43b98c', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '975865955dee1803b5215ecf049701c98b324855', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '84f6f67202e26ce80f7fc5d5f321e23b5bc55c57', class: "status-spinner" })), index.h("div", { key: 'a1ae56ed156e8feb279a1bf160f749c5d0b5ce45', class: "status-content" }, index.h("div", { key: 'd62932554cea257ce19763df26e0ba702e50eeca', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'c5506fa12dd0d37d26dc26c9f2381c843330451f', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '2097eda63185f336f7319014fae2ef6db1e41761', class: "performance-monitor" }, index.h("div", { key: 'd611c494c6b1a5930ef64aebdcba41e5d94c9ee6', class: "performance-expanded" }, index.h("div", { key: '800d46831e7b9bec260d9c5e67ea1a3eb9c2933e', class: "metrics-row" }, index.h("div", { key: 'dd0db1faa9050d98e71da4b5cd26a4a484c37c64', class: "metric-compact" }, index.h("span", { key: 'f0e6ea61a35ddb2ae5c2a6aff8c255a02c5c1219', class: "metric-label" }, "FPS"), index.h("span", { key: 'cbf4df4d142f6f01e45f566a8ede8fbbf29ad673', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '83861ad31fa46c8199a5583e4b3f8eefd44bacd6', class: "metric-compact" }, index.h("span", { key: '41b8de4cda47959dfc9df393e95fdadde534dd21', class: "metric-label" }, "MEM"), index.h("span", { key: 'b268f13cc78066e240ed158f1443113a94827aff', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '9f9a9d0690906b186492af23b900c5fa0f5cff89', class: "metrics-row" }, index.h("div", { key: '84fa6e453dc36e4a74e8b573447b17230766229e', class: "metric-compact" }, index.h("span", { key: 'f17d52899090c89f532d74c552dd8c5338fc2d83', class: "metric-label" }, "INF"), index.h("span", { key: '2beca0d7ce55ed574f6b01efc086c5846983e024', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '981e300cf076e81bfe946974f6cffd63619eacde', class: "metric-compact" }, index.h("span", { key: '3dfb0c996a69eb87b0f711322675cad69c471146', class: "metric-label" }, "FRAME"), index.h("span", { key: '3ae2ac6e90cda77f23e559f3197c3b761f9bdc40', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '2c51216838d87728d9d7764e5ba8a50fa02eb493', class: "metrics-row" }, index.h("div", { key: 'eda810ffb80ec38ab84583f59b26706f90a50fc9', class: "metric-compact" }, index.h("span", { key: '978ba9c0c67d6da6b29b3535af33a314bab9276a', class: "metric-label" }, "DET"), index.h("span", { key: '3e5e169fd4a3906283d8f918b5bbc7c5df6ccd0f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'b4b915c745e35c09470a4dc5edfc112d32cfb8b6', class: "metric-compact" }, index.h("span", { key: '3e2a7b88a4446813c955efb0dfa9e2958e8e1448', class: "metric-label" }, "RATE"), index.h("span", { key: 'f4f073f7ee622167a241c6a1c535ef580c13d9ab', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '76c556e92f6d7980539f1a30efe8751f665164fb', class: "watermark" }, index.h("img", { key: 'af23e115d6b4e92a72e55d6d98fde0e4b1d09e9b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2465
2205
  }
2466
2206
  // Utility methods
2467
2207
  updateDetectionBoxes(boxes) {
@@ -2658,8 +2398,8 @@ const JaakStamps = class {
2658
2398
  if (captureState.step === 'front') {
2659
2399
  this.stateManager.setCapturedImages({
2660
2400
  front: {
2661
- fullFrame: captureCanvas.toDataURL('image/png'),
2662
- cropped: croppedCanvas.toDataURL('image/png')
2401
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2402
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2663
2403
  }
2664
2404
  });
2665
2405
  // Check if document classification is enabled (independent of detector)
@@ -2685,8 +2425,6 @@ const JaakStamps = class {
2685
2425
  showFlipAnimation: true
2686
2426
  });
2687
2427
  this.triggerRerender();
2688
- // Progressive resource cleanup after front capture
2689
- await this.onFrontCaptureComplete();
2690
2428
  setTimeout(() => {
2691
2429
  this.stateManager.updateCaptureState({
2692
2430
  showFlipAnimation: false,
@@ -2701,8 +2439,8 @@ const JaakStamps = class {
2701
2439
  else if (captureState.step === 'back') {
2702
2440
  this.stateManager.setCapturedImages({
2703
2441
  back: {
2704
- fullFrame: captureCanvas.toDataURL('image/png'),
2705
- cropped: croppedCanvas.toDataURL('image/png')
2442
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2443
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2706
2444
  }
2707
2445
  });
2708
2446
  this.completeProcess(false);
@@ -2740,8 +2478,8 @@ const JaakStamps = class {
2740
2478
  if (captureState.step === 'front') {
2741
2479
  this.stateManager.setCapturedImages({
2742
2480
  front: {
2743
- fullFrame: captureCanvas.toDataURL('image/png'),
2744
- cropped: croppedCanvas.toDataURL('image/png')
2481
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2482
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2745
2483
  }
2746
2484
  });
2747
2485
  // Check if document classification is enabled
@@ -2762,8 +2500,6 @@ const JaakStamps = class {
2762
2500
  isDetectionPaused: true,
2763
2501
  showFlipAnimation: true
2764
2502
  });
2765
- // Progressive resource cleanup after front capture
2766
- await this.onFrontCaptureComplete();
2767
2503
  setTimeout(() => {
2768
2504
  this.stateManager.updateCaptureState({
2769
2505
  showFlipAnimation: false,
@@ -2775,8 +2511,8 @@ const JaakStamps = class {
2775
2511
  else if (captureState.step === 'back') {
2776
2512
  this.stateManager.setCapturedImages({
2777
2513
  back: {
2778
- fullFrame: captureCanvas.toDataURL('image/png'),
2779
- cropped: croppedCanvas.toDataURL('image/png')
2514
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2515
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2780
2516
  }
2781
2517
  });
2782
2518
  this.completeProcess(false);
@@ -2902,10 +2638,13 @@ const JaakStamps = class {
2902
2638
  this.useDocumentDetector = false; // Desactivar detector automático
2903
2639
  // Actualizar el estado con mensaje de error
2904
2640
  this.updateStatus('Modo manual activado', errorMessage, 'error');
2905
- // Ocultar mensaje después de 5 segundos
2906
- setTimeout(() => {
2907
- this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
2908
- }, 5000);
2641
+ // Continuar con la configuración de cámara para mostrar el video
2642
+ this.continueWithCameraSetup().catch(() => {
2643
+ // Si falla la configuración de cámara, mantener el mensaje de error pero cambiar el estado
2644
+ setTimeout(() => {
2645
+ this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
2646
+ }, 5000);
2647
+ });
2909
2648
  }
2910
2649
  stopPerformanceMonitoring() {
2911
2650
  // Nota: No necesitamos limpiar más variables porque ya no se usarán
@@ -2941,16 +2680,6 @@ const JaakStamps = class {
2941
2680
  setTimeout(() => {
2942
2681
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2943
2682
  }, 3000);
2944
- // Aggressive cleanup after process completion with delay
2945
- setTimeout(async () => {
2946
- const cleanupResult = await this.aggressiveResourceCleanup();
2947
- if (this.debug) {
2948
- console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2949
- if (cleanupResult.errors.length > 0) {
2950
- console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2951
- }
2952
- }
2953
- }, 1000); // Wait 1 second after completion
2954
2683
  }
2955
2684
  stopDetection() {
2956
2685
  if (this.animationId) {
@@ -3002,6 +2731,12 @@ const JaakStamps = class {
3002
2731
  try {
3003
2732
  // Close the selector immediately when user selects a camera
3004
2733
  this.showCameraSelector = false;
2734
+ // Check if user selected the same camera that's already active
2735
+ const currentCameraId = this.cameraService.getSelectedCameraId();
2736
+ if (currentCameraId === cameraId) {
2737
+ // Same camera selected, just close the selector without switching
2738
+ return;
2739
+ }
3005
2740
  this.isSwitchingCamera = true;
3006
2741
  // Stop current video stream
3007
2742
  if (this.videoStream) {