@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,4 +1,4 @@
1
- import { r as registerInstance, c as createEvent, a as getElement, h } from './index-BP1Q4KOg.js';
1
+ import { r as registerInstance, c as createEvent, g as getElement, h } from './index-Drbzcuq-.js';
2
2
 
3
3
  class EventBusService {
4
4
  events = new Map();
@@ -204,7 +204,21 @@ class CameraService {
204
204
  tempStream.getTracks().forEach(track => track.stop());
205
205
  }
206
206
  const devices = await navigator.mediaDevices.enumerateDevices();
207
- this.availableCameras = devices.filter(device => device.kind === 'videoinput');
207
+ const allCameras = devices.filter(device => device.kind === 'videoinput');
208
+ // Filter out virtual cameras
209
+ this.availableCameras = allCameras.filter(camera => {
210
+ const isVirtual = this.isVirtualCamera(camera);
211
+ if (isVirtual) {
212
+ console.log(`Virtual camera detected and filtered: ${camera.label}`);
213
+ }
214
+ return !isVirtual;
215
+ });
216
+ // Check if there are physical cameras available
217
+ if (this.availableCameras.length === 0) {
218
+ console.log('No physical cameras available');
219
+ this.eventBus.emit('error', new Error('Cámaras virtuales no están permitidas. Use una cámara física.'));
220
+ return [];
221
+ }
208
222
  // Cache the results for optimization
209
223
  CameraService.deviceEnumerationCache = {
210
224
  devices: this.availableCameras,
@@ -254,6 +268,21 @@ class CameraService {
254
268
  });
255
269
  this.lastStreamConstraints = finalConstraints;
256
270
  const stream = await this.currentStreamPromise;
271
+ // Validate that active stream is not from a virtual camera
272
+ const videoTrack = stream.getVideoTracks()[0];
273
+ if (videoTrack) {
274
+ const settings = videoTrack.getSettings();
275
+ const deviceId = settings.deviceId;
276
+ if (deviceId) {
277
+ const devices = await navigator.mediaDevices.enumerateDevices();
278
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
279
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
280
+ console.log(`Virtual camera detected in active stream: ${currentDevice.label}`);
281
+ stream.getTracks().forEach(track => track.stop());
282
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
283
+ }
284
+ }
285
+ }
257
286
  return stream;
258
287
  }
259
288
  catch (error) {
@@ -271,7 +300,23 @@ class CameraService {
271
300
  audio: false
272
301
  });
273
302
  this.lastStreamConstraints = basicConstraints;
274
- return await this.currentStreamPromise;
303
+ const stream = await this.currentStreamPromise;
304
+ // Validate fallback stream as well
305
+ const videoTrack = stream.getVideoTracks()[0];
306
+ if (videoTrack) {
307
+ const settings = videoTrack.getSettings();
308
+ const deviceId = settings.deviceId;
309
+ if (deviceId) {
310
+ const devices = await navigator.mediaDevices.enumerateDevices();
311
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
312
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
313
+ console.log(`Virtual camera detected in fallback stream: ${currentDevice.label}`);
314
+ stream.getTracks().forEach(track => track.stop());
315
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
316
+ }
317
+ }
318
+ }
319
+ return stream;
275
320
  }
276
321
  }
277
322
  async switchCamera(cameraId) {
@@ -280,6 +325,12 @@ class CameraService {
280
325
  await this.enumerateDevices();
281
326
  return;
282
327
  }
328
+ // Validate that the target camera is not virtual
329
+ if (this.isVirtualCamera(selectedCamera)) {
330
+ console.log(`Attempted to switch to virtual camera: ${selectedCamera.label}`);
331
+ this.eventBus.emit('error', new Error('No se puede cambiar a cámara virtual'));
332
+ return;
333
+ }
283
334
  await this.setSelectedCamera(cameraId);
284
335
  }
285
336
  isRearCamera(stream) {
@@ -551,6 +602,37 @@ class CameraService {
551
602
  invalidateDeviceCache() {
552
603
  CameraService.deviceEnumerationCache = null;
553
604
  }
605
+ isVirtualCamera(device) {
606
+ const label = device.label.toLowerCase();
607
+ const virtualCameraIndicators = [
608
+ 'obs',
609
+ 'virtual',
610
+ 'snap camera',
611
+ 'manycam',
612
+ 'xsplit',
613
+ 'camtwist',
614
+ 'ecamm',
615
+ 'nvidia broadcast',
616
+ 'droidcam',
617
+ 'iriun',
618
+ 'elgato',
619
+ 'streamlabs',
620
+ 'wirecast',
621
+ 'zoom virtual',
622
+ 'teams virtual',
623
+ 'mmhmm',
624
+ 'camo',
625
+ 'reincubate camo',
626
+ 'webcamoid',
627
+ 'splitcam',
628
+ 'cheese',
629
+ 'guvcview',
630
+ 'yawcam',
631
+ 'webcam 7',
632
+ 'altserver'
633
+ ];
634
+ return virtualCameraIndicators.some(indicator => label.includes(indicator));
635
+ }
554
636
  }
555
637
 
556
638
  class LowMemoryDeviceStrategy {
@@ -648,10 +730,6 @@ class DetectionService {
648
730
  // Static canvas pool for reuse across instances
649
731
  static canvasPool = new Map();
650
732
  static MAX_POOL_SIZE = 5;
651
- // Resource management for optimization
652
- modelLoadController;
653
- classificationLoadController;
654
- modelBlobUrls = new Set();
655
733
  constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
656
734
  this.debug = debug;
657
735
  this.useDocumentClassification = useDocumentClassification;
@@ -669,26 +747,11 @@ class DetectionService {
669
747
  return;
670
748
  }
671
749
  try {
672
- // Create AbortController for this load operation
673
- this.modelLoadController = new AbortController();
674
750
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
675
751
  try {
676
- // Load model with abort signal support
677
- const response = await fetch(this.MODEL_PATH, {
678
- signal: this.modelLoadController.signal
679
- });
680
- if (!response.ok) {
681
- throw new Error(`Failed to fetch model: ${response.status}`);
682
- }
683
- const modelBuffer = await response.arrayBuffer();
684
- this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
752
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
685
753
  }
686
754
  catch (error) {
687
- // Handle abort signal
688
- if (error.name === 'AbortError') {
689
- console.log('Model loading was cancelled');
690
- return;
691
- }
692
755
  // Múltiples tipos de errores de memoria que pueden ocurrir
693
756
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
694
757
  error.message.includes('Out of memory') ||
@@ -710,15 +773,7 @@ class DetectionService {
710
773
  sessionLogVerbosityLevel: 0,
711
774
  };
712
775
  try {
713
- // Retry with fallback options and abort signal
714
- const fallbackResponse = await fetch(this.MODEL_PATH, {
715
- signal: this.modelLoadController.signal
716
- });
717
- if (!fallbackResponse.ok) {
718
- throw new Error(`Failed to fetch model with fallback: ${fallbackResponse.status}`);
719
- }
720
- const fallbackBuffer = await fallbackResponse.arrayBuffer();
721
- this.session = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
776
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
722
777
  }
723
778
  catch (fallbackError) {
724
779
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -739,13 +794,9 @@ class DetectionService {
739
794
  return;
740
795
  }
741
796
  try {
742
- // Create AbortController for classification model loading
743
- this.classificationLoadController = new AbortController();
744
797
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
745
798
  try {
746
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
747
- signal: this.classificationLoadController.signal
748
- });
799
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
749
800
  if (classResponse.ok) {
750
801
  this.mobileNetClassMap = await classResponse.json();
751
802
  }
@@ -763,22 +814,9 @@ class DetectionService {
763
814
  // Load model
764
815
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
765
816
  try {
766
- // Load classification model with abort signal support
767
- const response = await fetch(this.MOBILENET_MODEL_PATH, {
768
- signal: this.classificationLoadController.signal
769
- });
770
- if (!response.ok) {
771
- throw new Error(`Failed to fetch classification model: ${response.status}`);
772
- }
773
- const modelBuffer = await response.arrayBuffer();
774
- this.mobileNetSession = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
817
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
775
818
  }
776
819
  catch (error) {
777
- // Handle abort signal
778
- if (error.name === 'AbortError') {
779
- console.log('Classification model loading was cancelled');
780
- return;
781
- }
782
820
  // Múltiples tipos de errores de memoria que pueden ocurrir
783
821
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
784
822
  error.message.includes('Out of memory') ||
@@ -800,15 +838,7 @@ class DetectionService {
800
838
  sessionLogVerbosityLevel: 0,
801
839
  };
802
840
  try {
803
- // Retry with fallback options and abort signal
804
- const fallbackResponse = await fetch(this.MOBILENET_MODEL_PATH, {
805
- signal: this.classificationLoadController.signal
806
- });
807
- if (!fallbackResponse.ok) {
808
- throw new Error(`Failed to fetch classification model with fallback: ${fallbackResponse.status}`);
809
- }
810
- const fallbackBuffer = await fallbackResponse.arrayBuffer();
811
- this.mobileNetSession = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
841
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
812
842
  }
813
843
  catch (fallbackError) {
814
844
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -998,9 +1028,6 @@ class DetectionService {
998
1028
  return alignment.top && alignment.right && alignment.bottom && alignment.left;
999
1029
  }
1000
1030
  cleanup() {
1001
- // Cancel any ongoing requests
1002
- this.abortPendingRequests();
1003
- // Cleanup ONNX resources
1004
1031
  this.cleanupCanvasPool();
1005
1032
  if (this.session) {
1006
1033
  this.session.release?.();
@@ -1012,8 +1039,6 @@ class DetectionService {
1012
1039
  }
1013
1040
  this.mobileNetClassMap = undefined;
1014
1041
  this.modelLoaded = false;
1015
- // Cleanup blob URLs
1016
- this.revokeModelBlobUrls();
1017
1042
  }
1018
1043
  initializeCanvasPool() {
1019
1044
  this.preprocessCanvas = document.createElement('canvas');
@@ -1111,93 +1136,11 @@ class DetectionService {
1111
1136
  const pool = DetectionService.canvasPool.get(key);
1112
1137
  // Only return to pool if not at max capacity
1113
1138
  if (pool.length < DetectionService.MAX_POOL_SIZE) {
1114
- const ctx = canvas.getContext('2d');
1115
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1116
1139
  pool.push(canvas);
1117
1140
  }
1118
- // If pool is full, let the canvas be garbage collected
1119
1141
  }
1120
- // Resource management methods for cache and browser optimization
1121
- async clearModelCache() {
1122
- try {
1123
- // Clear cache specific to models
1124
- const cacheNames = await caches.keys();
1125
- const modelCaches = cacheNames.filter(name => name.includes('onnx') ||
1126
- name.includes('jaak-static') ||
1127
- name.includes('squeezenet') ||
1128
- name.includes('ddmyp'));
1129
- await Promise.all(modelCaches.map(cacheName => caches.delete(cacheName)));
1130
- if (this.debug) {
1131
- console.log(`🧹 Cleared ${modelCaches.length} model caches`);
1132
- }
1133
- }
1134
- catch (error) {
1135
- console.warn('Error clearing model cache:', error);
1136
- }
1137
- }
1138
- abortPendingRequests() {
1139
- // Cancel any fetch in progress
1140
- if (this.modelLoadController) {
1141
- this.modelLoadController.abort();
1142
- this.modelLoadController = undefined;
1143
- }
1144
- if (this.classificationLoadController) {
1145
- this.classificationLoadController.abort();
1146
- this.classificationLoadController = undefined;
1147
- }
1148
- }
1149
- revokeModelBlobUrls() {
1150
- // Revoke any blob URLs created for models
1151
- this.modelBlobUrls.forEach(url => URL.revokeObjectURL(url));
1152
- this.modelBlobUrls.clear();
1153
- }
1154
- // Progressive resource release for optimization
1155
- async releaseMobileNetResources() {
1156
- if (this.mobileNetSession) {
1157
- this.mobileNetSession.release?.();
1158
- this.mobileNetSession = undefined;
1159
- }
1160
- this.mobileNetClassMap = undefined;
1161
- if (this.classificationLoadController) {
1162
- this.classificationLoadController.abort();
1163
- this.classificationLoadController = undefined;
1164
- }
1165
- if (this.debug) {
1166
- console.log('🧹 Released MobileNet classification resources');
1167
- }
1168
- }
1169
- // Optimize canvas pool by reducing to specific size
1170
- optimizeCanvasPool(maxSize) {
1171
- DetectionService.canvasPool.forEach((pool, key) => {
1172
- while (pool.length > maxSize) {
1173
- const canvas = pool.pop();
1174
- // Explicitly clear and nullify for garbage collection
1175
- if (canvas) {
1176
- const ctx = canvas.getContext('2d');
1177
- if (ctx) {
1178
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1179
- }
1180
- }
1181
- }
1182
- if (pool.length === 0) {
1183
- DetectionService.canvasPool.delete(key);
1184
- }
1185
- });
1186
- if (this.debug) {
1187
- console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
1188
- }
1189
- }
1190
- // Clear all static canvas pools
1191
- static clearAllCanvasPools() {
1192
- DetectionService.canvasPool.forEach((pool) => {
1193
- pool.forEach(canvas => {
1194
- const ctx = canvas.getContext('2d');
1195
- if (ctx) {
1196
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1197
- }
1198
- });
1199
- pool.length = 0;
1200
- });
1142
+ // Static method to clear all canvas pools (useful for memory management)
1143
+ static clearCanvasPools() {
1201
1144
  DetectionService.canvasPool.clear();
1202
1145
  }
1203
1146
  // Method to get pool statistics for debugging
@@ -1256,7 +1199,7 @@ class ServiceContainer {
1256
1199
  }
1257
1200
  }
1258
1201
 
1259
- 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}}";
1202
+ 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}}";
1260
1203
 
1261
1204
  const JaakStamps = class {
1262
1205
  constructor(hostRef) {
@@ -1334,6 +1277,7 @@ const JaakStamps = class {
1334
1277
  alignmentStartTime;
1335
1278
  alignmentTimer;
1336
1279
  backDocumentTimer;
1280
+ orientationTimer;
1337
1281
  // Performance monitoring
1338
1282
  performanceMetrics = {
1339
1283
  fps: 0,
@@ -1437,7 +1381,7 @@ const JaakStamps = class {
1437
1381
  if (!window.ort) {
1438
1382
  this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
1439
1383
  const script = document.createElement('script');
1440
- script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
1384
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
1441
1385
  document.head.appendChild(script);
1442
1386
  await new Promise((resolve) => {
1443
1387
  script.onload = () => {
@@ -1518,6 +1462,18 @@ const JaakStamps = class {
1518
1462
  });
1519
1463
  resizeObserver.observe(this.detectionContainer.parentElement);
1520
1464
  }
1465
+ // Handle mobile device orientation changes
1466
+ window.addEventListener('orientationchange', () => {
1467
+ // Clear any existing orientation timer
1468
+ if (this.orientationTimer) {
1469
+ clearTimeout(this.orientationTimer);
1470
+ }
1471
+ // Add delay to ensure video dimensions are updated after rotation
1472
+ this.orientationTimer = window.setTimeout(() => {
1473
+ this.handleResize();
1474
+ this.orientationTimer = undefined;
1475
+ }, 300);
1476
+ });
1521
1477
  }
1522
1478
  handleResize() {
1523
1479
  if (this.detectionContainer) {
@@ -1674,13 +1630,7 @@ const JaakStamps = class {
1674
1630
  }
1675
1631
  try {
1676
1632
  this.exitSession();
1677
- // Aggressive cleanup when stopping capture
1678
- const cleanupResult = await this.aggressiveResourceCleanup();
1679
- return {
1680
- success: true,
1681
- resourcesFreed: cleanupResult.freed,
1682
- cleanupErrors: cleanupResult.errors
1683
- };
1633
+ return { success: true };
1684
1634
  }
1685
1635
  catch (error) {
1686
1636
  return { success: false, error: error.message };
@@ -1905,60 +1855,11 @@ const JaakStamps = class {
1905
1855
  // Always allow getting capture delay, even during initialization
1906
1856
  return this.captureDelay;
1907
1857
  }
1908
- async getResourceReport() {
1909
- // Public method to get resource usage report
1910
- return this.getResourceReportInternal();
1911
- }
1912
- async forceResourceCleanup() {
1913
- // Public method to force aggressive resource cleanup
1914
- const readyCheck = this.isComponentReady();
1915
- if (!readyCheck.ready) {
1916
- return { success: false, error: readyCheck.message };
1917
- }
1918
- try {
1919
- const cleanupResult = await this.aggressiveResourceCleanup();
1920
- return {
1921
- success: true,
1922
- resourcesFreed: cleanupResult.freed,
1923
- errors: cleanupResult.errors,
1924
- timestamp: new Date().toISOString()
1925
- };
1926
- }
1927
- catch (error) {
1928
- return { success: false, error: error.message };
1929
- }
1930
- }
1931
1858
  // Memory and device capability detection
1932
1859
  checkDeviceCapabilities() {
1933
1860
  const deviceMemory = navigator.deviceMemory;
1934
- const hardwareConcurrency = navigator.hardwareConcurrency || 1;
1935
- const isLowEndDevice = hardwareConcurrency <= 2;
1936
- const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1937
- const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
1938
- // Si tenemos info de memoria del dispositivo
1939
- if (deviceMemory && deviceMemory < 4) {
1940
- return {
1941
- canUseML: false,
1942
- reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
1943
- memoryMB: deviceMemory * 1024
1944
- };
1945
- }
1946
- // Dispositivos móviles de gama baja
1947
- if (isMobile && isLowEndDevice) {
1948
- return {
1949
- canUseML: false,
1950
- reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
1951
- memoryMB: null
1952
- };
1953
- }
1954
- // Safari tiene limitaciones conocidas con WASM
1955
- if (isSafari) {
1956
- return {
1957
- canUseML: false,
1958
- reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
1959
- memoryMB: null
1960
- };
1961
- }
1861
+ // Siempre intentar usar ML, independientemente del dispositivo
1862
+ // Las restricciones anteriores no eran confiables
1962
1863
  return {
1963
1864
  canUseML: true,
1964
1865
  reason: 'Dispositivo compatible con detección automática.',
@@ -2084,6 +1985,12 @@ const JaakStamps = class {
2084
1985
  this.videoRef.onloadedmetadata = async () => {
2085
1986
  await this.videoRef.play();
2086
1987
  this.stateManager.updateCaptureState({ isVideoActive: true });
1988
+ // Recalculate mask dimensions when new camera stream loads
1989
+ if (this.detectionContainer) {
1990
+ const container = this.detectionContainer.parentElement;
1991
+ const rect = container.getBoundingClientRect();
1992
+ this.updateMaskDimensions(rect);
1993
+ }
2087
1994
  resolve();
2088
1995
  };
2089
1996
  });
@@ -2255,6 +2162,10 @@ const JaakStamps = class {
2255
2162
  clearTimeout(this.alignmentTimer);
2256
2163
  this.alignmentTimer = undefined;
2257
2164
  }
2165
+ if (this.orientationTimer) {
2166
+ clearTimeout(this.orientationTimer);
2167
+ this.orientationTimer = undefined;
2168
+ }
2258
2169
  // Liberar recursos de ONNX al limpiar el componente
2259
2170
  this.releaseOnnxResources();
2260
2171
  if (this.performanceUpdateInterval) {
@@ -2263,182 +2174,11 @@ const JaakStamps = class {
2263
2174
  }
2264
2175
  // Clear canvas pool
2265
2176
  this.canvasPool = [];
2266
- // Clean DOM references and memory leaks
2267
- this.cleanupDOMReferences();
2268
2177
  this.detectionBoxes = [];
2269
2178
  this.alignmentStartTime = undefined;
2270
2179
  this.hasDocumentDetected = false;
2271
2180
  this.serviceContainer?.cleanup();
2272
2181
  }
2273
- // AGGRESSIVE RESOURCE CLEANUP METHODS
2274
- // Clean DOM references and memory leaks
2275
- cleanupDOMReferences() {
2276
- // Remove event listeners that could maintain references
2277
- if (this.videoRef) {
2278
- // Remove common event listeners (if any were added)
2279
- this.videoRef.srcObject = null;
2280
- this.videoRef = undefined;
2281
- }
2282
- // Clean detection container
2283
- if (this.detectionContainer) {
2284
- this.detectionContainer = undefined;
2285
- }
2286
- // Clean references to images in the DOM and revoke blob URLs
2287
- const images = this.el.shadowRoot?.querySelectorAll('img');
2288
- images?.forEach(img => {
2289
- if (img.src.startsWith('blob:')) {
2290
- URL.revokeObjectURL(img.src);
2291
- }
2292
- img.src = '';
2293
- });
2294
- }
2295
- // Clear browser cache and storage
2296
- async clearBrowserCache() {
2297
- try {
2298
- // 1. Clear Service Worker cache if exists
2299
- if ('serviceWorker' in navigator) {
2300
- const registrations = await navigator.serviceWorker.getRegistrations();
2301
- await Promise.all(registrations.map(registration => registration.unregister()));
2302
- }
2303
- // 2. Clear Cache API specific to the component
2304
- const cacheNames = await caches.keys();
2305
- const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2306
- await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2307
- // 3. Force garbage collection if available (only in dev)
2308
- if (this.debug && window.gc) {
2309
- window.gc();
2310
- }
2311
- }
2312
- catch (error) {
2313
- console.warn('Error clearing browser cache:', error);
2314
- }
2315
- }
2316
- // Clear local storage specific to the component
2317
- async clearLocalStorage() {
2318
- try {
2319
- // Clear localStorage specific to the component
2320
- const keysToRemove = [];
2321
- for (let i = 0; i < localStorage.length; i++) {
2322
- const key = localStorage.key(i);
2323
- if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2324
- keysToRemove.push(key);
2325
- }
2326
- }
2327
- keysToRemove.forEach(key => localStorage.removeItem(key));
2328
- // Clear sessionStorage
2329
- const sessionKeysToRemove = [];
2330
- for (let i = 0; i < sessionStorage.length; i++) {
2331
- const key = sessionStorage.key(i);
2332
- if (key && (key.includes('jaak') || key.includes('onnx'))) {
2333
- sessionKeysToRemove.push(key);
2334
- }
2335
- }
2336
- sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2337
- }
2338
- catch (error) {
2339
- console.warn('Error clearing local storage:', error);
2340
- }
2341
- }
2342
- // Aggressive resource cleanup combining all strategies
2343
- async aggressiveResourceCleanup() {
2344
- const freed = [];
2345
- const errors = [];
2346
- try {
2347
- // 1. Release ONNX resources
2348
- this.releaseOnnxResources();
2349
- freed.push('ONNX models');
2350
- // 2. Clear model cache in detection service
2351
- if (this.detectionService) {
2352
- await this.detectionService.clearModelCache();
2353
- freed.push('Model cache');
2354
- }
2355
- // 3. Clear browser cache
2356
- await this.clearBrowserCache();
2357
- freed.push('Browser cache');
2358
- // 4. Clear local storage
2359
- await this.clearLocalStorage();
2360
- freed.push('Local storage');
2361
- // 5. Clean DOM references
2362
- this.cleanupDOMReferences();
2363
- freed.push('DOM references');
2364
- // 6. Optimize canvas pools
2365
- if (this.detectionService) {
2366
- this.detectionService.optimizeCanvasPool(0);
2367
- freed.push('Canvas pools');
2368
- }
2369
- // 7. Clear component canvas pool
2370
- this.canvasPool = [];
2371
- freed.push('Component canvas pool');
2372
- // 8. Force garbage collection (if available)
2373
- if (this.debug && window.gc) {
2374
- window.gc();
2375
- freed.push('Garbage collection');
2376
- }
2377
- }
2378
- catch (error) {
2379
- errors.push(error.message);
2380
- }
2381
- return { freed, errors };
2382
- }
2383
- // Progressive resource release after front capture
2384
- async onFrontCaptureComplete() {
2385
- try {
2386
- // Release classification model if not needed for back
2387
- if (this.detectionService && !this.useDocumentClassification) {
2388
- await this.detectionService.releaseMobileNetResources();
2389
- }
2390
- // Optimize canvas pool to minimum needed for back capture
2391
- if (this.detectionService) {
2392
- this.detectionService.optimizeCanvasPool(1);
2393
- }
2394
- // Reduce component canvas pool
2395
- while (this.canvasPool.length > 1) {
2396
- const canvas = this.canvasPool.pop();
2397
- if (canvas) {
2398
- const ctx = canvas.getContext('2d');
2399
- if (ctx) {
2400
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2401
- }
2402
- }
2403
- }
2404
- if (this.debug) {
2405
- console.log('🧹 Progressive cleanup after front capture completed');
2406
- }
2407
- }
2408
- catch (error) {
2409
- console.warn('Error in progressive cleanup:', error);
2410
- }
2411
- }
2412
- // Resource reporting for debugging
2413
- getResourceReportInternal() {
2414
- const report = {
2415
- timestamp: new Date().toISOString(),
2416
- component: {
2417
- hasVideoStream: !!this.videoStream,
2418
- hasAnimationFrame: !!this.animationId,
2419
- canvasPoolSize: this.canvasPool.length,
2420
- detectionBoxesCount: this.detectionBoxes.length,
2421
- performanceMonitorActive: !!this.performanceUpdateInterval
2422
- }
2423
- };
2424
- // Add detection service stats if available
2425
- if (this.detectionService) {
2426
- report.detectionService = {
2427
- isModelLoaded: this.detectionService.isModelLoaded(),
2428
- canvasPoolStats: this.detectionService.getPoolStats()
2429
- };
2430
- }
2431
- // Add memory info if available
2432
- if ('memory' in performance) {
2433
- const memInfo = performance.memory;
2434
- report.memory = {
2435
- usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2436
- totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2437
- jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2438
- };
2439
- }
2440
- return report;
2441
- }
2442
2182
  render() {
2443
2183
  const captureState = this.stateManager?.getCaptureState() || {
2444
2184
  isVideoActive: false,
@@ -2448,7 +2188,7 @@ const JaakStamps = class {
2448
2188
  isCapturing: false
2449
2189
  };
2450
2190
  const cameraInfo = this.cameraInfoWithAutofocus;
2451
- return (h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, 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' } }), h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
2191
+ return (h("div", { key: 'f9cbca2fd6b8548a29ace7fcb972b53f402f61af', class: "detector-container" }, h("div", { key: '4f3cac734eb6bfd44a8de644eadca17e7893e11a', class: "video-container" }, 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' } }), h("div", { key: '38c8ad8b691db60e9b5a2a8b641fe6febbeacfca', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
2452
2192
  position: 'absolute',
2453
2193
  left: `${box.x}px`,
2454
2194
  top: `${box.y}px`,
@@ -2457,9 +2197,9 @@ const JaakStamps = class {
2457
2197
  border: '2px solid #32406C',
2458
2198
  pointerEvents: 'none',
2459
2199
  boxSizing: 'border-box'
2460
- } })))), this.isMaskReady && (h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2200
+ } })))), this.isMaskReady && (h("div", { key: 'c56fde56b7a86a4e8a4cf558745291ea5cde12fd', class: "overlay-mask" }, h("div", { key: '04ed3f01f271dc84eed594cb4b60c2453645bd41', class: "card-outline" }, h("div", { key: '648bd423a5e886d4768115237de5896c7223b30b', class: "side side-top" }), h("div", { key: 'b8878059493ca61961b9b3c82ecc18b50b79ac85', class: "side side-right" }), h("div", { key: '09e5ad64ff33dbd5c112bf52b71e14409ab6aa65', class: "side side-bottom" }), h("div", { key: 'b8ba3d83c9bc1b4a0da859d7ed4b9a15eaddcc2a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '3279701a240f77ac96e68a34ad95c099340c9a6a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2adf96d0a57e482372fd9586229dac2301b64fd4', class: "back-capture-section" }, h("div", { key: '305a147c9cfee854c60ac946a48a1d21b2792725', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '43270a0647f39307229c540dd5130fb58b1ff4fb', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: 'eb4666603774564eef95a120ac13faeb9e10c8b4', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'cac667d9548ae2314ad5a677c5ed90f25184f728', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '0af7aa4106ce189ae2d79bec78ede01abe25b1e7', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2461
2201
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
2462
- : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, 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 ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2202
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'de6e5c4fad4d4d996b894457c9af52bc71e533fb', class: "camera-controls" }, 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 ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '5fb73f958b9a5a6f9242032c4530b0bdab84f6c5', class: "camera-selector-dropdown" }, h("div", { key: '8ee2d26b1001e571e7b6c326e927926fa08287fd', class: "camera-selector-header" }, h("span", { key: '78350b0fb1bd933f7c8c59d8dab0b7122ffbe363' }, "Seleccionar C\u00E1mara"), h("button", { key: 'bb750e0ddd5a1ee5ff96bd57805a7f90b8821e9f', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd0f6343cf1b7367f3da33ec8cf64891109d923b8', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'c9642fbfb0acea766d3f0542f4cc9f17f4af4d7b', class: "device-info" }, h("small", { key: 'b6e9507ff7addf011f339f6c22a88a96b40db68a' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '04ae9262409284782850b90945eb7038673e2097', class: "manual-capture-section" }, h("button", { key: '27c315e503ccba468b89ebd58d6b70f40ddd06ba', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '8f2dcd2df9a218b37eb0b8204a8724362cfeaa53', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '6fd6fe1d0213b01c8119a67fb8108f73227feee1', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'fba3816b48b99ca64510ecbefc0d41431569c9ae', class: "flip-animation" }, h("div", { key: 'beea1c8245b5d81f1008efc6d1b079a61c821e7f', class: "id-card-icon" }), h("div", { key: 'f0fbc34f619a653e2c0ade764ca99e7aa582d157', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'e4a0e6d31112e3a50024d1d0430b4ad997049fe7', class: "success-animation" }, h("div", { key: '5dd3f781f55630bc904c81207548ae35bc79b452', class: "check-icon" }), h("div", { key: 'a10ebc557582325091efb0abd12d62b27a43b98c', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '975865955dee1803b5215ecf049701c98b324855', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '84f6f67202e26ce80f7fc5d5f321e23b5bc55c57', class: "status-spinner" })), h("div", { key: 'a1ae56ed156e8feb279a1bf160f749c5d0b5ce45', class: "status-content" }, h("div", { key: 'd62932554cea257ce19763df26e0ba702e50eeca', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'c5506fa12dd0d37d26dc26c9f2381c843330451f', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '2097eda63185f336f7319014fae2ef6db1e41761', class: "performance-monitor" }, h("div", { key: 'd611c494c6b1a5930ef64aebdcba41e5d94c9ee6', class: "performance-expanded" }, h("div", { key: '800d46831e7b9bec260d9c5e67ea1a3eb9c2933e', class: "metrics-row" }, h("div", { key: 'dd0db1faa9050d98e71da4b5cd26a4a484c37c64', class: "metric-compact" }, h("span", { key: 'f0e6ea61a35ddb2ae5c2a6aff8c255a02c5c1219', class: "metric-label" }, "FPS"), h("span", { key: 'cbf4df4d142f6f01e45f566a8ede8fbbf29ad673', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '83861ad31fa46c8199a5583e4b3f8eefd44bacd6', class: "metric-compact" }, h("span", { key: '41b8de4cda47959dfc9df393e95fdadde534dd21', class: "metric-label" }, "MEM"), h("span", { key: 'b268f13cc78066e240ed158f1443113a94827aff', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '9f9a9d0690906b186492af23b900c5fa0f5cff89', class: "metrics-row" }, h("div", { key: '84fa6e453dc36e4a74e8b573447b17230766229e', class: "metric-compact" }, h("span", { key: 'f17d52899090c89f532d74c552dd8c5338fc2d83', class: "metric-label" }, "INF"), h("span", { key: '2beca0d7ce55ed574f6b01efc086c5846983e024', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '981e300cf076e81bfe946974f6cffd63619eacde', class: "metric-compact" }, h("span", { key: '3dfb0c996a69eb87b0f711322675cad69c471146', class: "metric-label" }, "FRAME"), h("span", { key: '3ae2ac6e90cda77f23e559f3197c3b761f9bdc40', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '2c51216838d87728d9d7764e5ba8a50fa02eb493', class: "metrics-row" }, h("div", { key: 'eda810ffb80ec38ab84583f59b26706f90a50fc9', class: "metric-compact" }, h("span", { key: '978ba9c0c67d6da6b29b3535af33a314bab9276a', class: "metric-label" }, "DET"), h("span", { key: '3e5e169fd4a3906283d8f918b5bbc7c5df6ccd0f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'b4b915c745e35c09470a4dc5edfc112d32cfb8b6', class: "metric-compact" }, h("span", { key: '3e2a7b88a4446813c955efb0dfa9e2958e8e1448', class: "metric-label" }, "RATE"), h("span", { key: 'f4f073f7ee622167a241c6a1c535ef580c13d9ab', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '76c556e92f6d7980539f1a30efe8751f665164fb', class: "watermark" }, h("img", { key: 'af23e115d6b4e92a72e55d6d98fde0e4b1d09e9b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2463
2203
  }
2464
2204
  // Utility methods
2465
2205
  updateDetectionBoxes(boxes) {
@@ -2656,8 +2396,8 @@ const JaakStamps = class {
2656
2396
  if (captureState.step === 'front') {
2657
2397
  this.stateManager.setCapturedImages({
2658
2398
  front: {
2659
- fullFrame: captureCanvas.toDataURL('image/png'),
2660
- cropped: croppedCanvas.toDataURL('image/png')
2399
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2400
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2661
2401
  }
2662
2402
  });
2663
2403
  // Check if document classification is enabled (independent of detector)
@@ -2683,8 +2423,6 @@ const JaakStamps = class {
2683
2423
  showFlipAnimation: true
2684
2424
  });
2685
2425
  this.triggerRerender();
2686
- // Progressive resource cleanup after front capture
2687
- await this.onFrontCaptureComplete();
2688
2426
  setTimeout(() => {
2689
2427
  this.stateManager.updateCaptureState({
2690
2428
  showFlipAnimation: false,
@@ -2699,8 +2437,8 @@ const JaakStamps = class {
2699
2437
  else if (captureState.step === 'back') {
2700
2438
  this.stateManager.setCapturedImages({
2701
2439
  back: {
2702
- fullFrame: captureCanvas.toDataURL('image/png'),
2703
- cropped: croppedCanvas.toDataURL('image/png')
2440
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2441
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2704
2442
  }
2705
2443
  });
2706
2444
  this.completeProcess(false);
@@ -2738,8 +2476,8 @@ const JaakStamps = class {
2738
2476
  if (captureState.step === 'front') {
2739
2477
  this.stateManager.setCapturedImages({
2740
2478
  front: {
2741
- fullFrame: captureCanvas.toDataURL('image/png'),
2742
- cropped: croppedCanvas.toDataURL('image/png')
2479
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2480
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2743
2481
  }
2744
2482
  });
2745
2483
  // Check if document classification is enabled
@@ -2760,8 +2498,6 @@ const JaakStamps = class {
2760
2498
  isDetectionPaused: true,
2761
2499
  showFlipAnimation: true
2762
2500
  });
2763
- // Progressive resource cleanup after front capture
2764
- await this.onFrontCaptureComplete();
2765
2501
  setTimeout(() => {
2766
2502
  this.stateManager.updateCaptureState({
2767
2503
  showFlipAnimation: false,
@@ -2773,8 +2509,8 @@ const JaakStamps = class {
2773
2509
  else if (captureState.step === 'back') {
2774
2510
  this.stateManager.setCapturedImages({
2775
2511
  back: {
2776
- fullFrame: captureCanvas.toDataURL('image/png'),
2777
- cropped: croppedCanvas.toDataURL('image/png')
2512
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2513
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2778
2514
  }
2779
2515
  });
2780
2516
  this.completeProcess(false);
@@ -2900,10 +2636,13 @@ const JaakStamps = class {
2900
2636
  this.useDocumentDetector = false; // Desactivar detector automático
2901
2637
  // Actualizar el estado con mensaje de error
2902
2638
  this.updateStatus('Modo manual activado', errorMessage, 'error');
2903
- // Ocultar mensaje después de 5 segundos
2904
- setTimeout(() => {
2905
- this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
2906
- }, 5000);
2639
+ // Continuar con la configuración de cámara para mostrar el video
2640
+ this.continueWithCameraSetup().catch(() => {
2641
+ // Si falla la configuración de cámara, mantener el mensaje de error pero cambiar el estado
2642
+ setTimeout(() => {
2643
+ this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
2644
+ }, 5000);
2645
+ });
2907
2646
  }
2908
2647
  stopPerformanceMonitoring() {
2909
2648
  // Nota: No necesitamos limpiar más variables porque ya no se usarán
@@ -2939,16 +2678,6 @@ const JaakStamps = class {
2939
2678
  setTimeout(() => {
2940
2679
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2941
2680
  }, 3000);
2942
- // Aggressive cleanup after process completion with delay
2943
- setTimeout(async () => {
2944
- const cleanupResult = await this.aggressiveResourceCleanup();
2945
- if (this.debug) {
2946
- console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2947
- if (cleanupResult.errors.length > 0) {
2948
- console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2949
- }
2950
- }
2951
- }, 1000); // Wait 1 second after completion
2952
2681
  }
2953
2682
  stopDetection() {
2954
2683
  if (this.animationId) {
@@ -3000,6 +2729,12 @@ const JaakStamps = class {
3000
2729
  try {
3001
2730
  // Close the selector immediately when user selects a camera
3002
2731
  this.showCameraSelector = false;
2732
+ // Check if user selected the same camera that's already active
2733
+ const currentCameraId = this.cameraService.getSelectedCameraId();
2734
+ if (currentCameraId === cameraId) {
2735
+ // Same camera selected, just close the selector without switching
2736
+ return;
2737
+ }
3003
2738
  this.isSwitchingCamera = true;
3004
2739
  // Stop current video stream
3005
2740
  if (this.videoStream) {