@jaak.ai/stamps 2.1.0-dev.2 → 2.1.0-dev.20

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 (53) 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 +542 -68
  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 +56 -12
  14. package/dist/collection/components/my-component/my-component.js +429 -67
  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 +32 -4
  19. package/dist/collection/services/DetectionService.js.map +1 -1
  20. package/dist/components/index.js +165 -113
  21. package/dist/components/index.js.map +1 -1
  22. package/dist/components/jaak-stamps.js +546 -68
  23. package/dist/components/jaak-stamps.js.map +1 -1
  24. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  25. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  26. package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
  27. package/dist/esm/index-Drbzcuq-.js.map +1 -0
  28. package/dist/esm/jaak-stamps-webcomponent.js +5 -4
  29. package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
  30. package/dist/esm/jaak-stamps.entry.js +542 -68
  31. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  32. package/dist/esm/loader.js +4 -3
  33. package/dist/esm/loader.js.map +1 -1
  34. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  35. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
  36. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  37. package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
  38. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
  39. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
  40. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
  41. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
  42. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
  43. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
  44. package/dist/types/components/my-component/my-component.d.ts +19 -0
  45. package/dist/types/services/CameraService.d.ts +1 -0
  46. package/dist/types/stencil-public-runtime.d.ts +12 -5
  47. package/package.json +3 -1
  48. package/dist/cjs/index-BfhtOB0D.js.map +0 -1
  49. package/dist/esm/index-BP1Q4KOg.js.map +0 -1
  50. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +0 -2
  51. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +0 -1
  52. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
  53. 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 {
@@ -672,7 +754,12 @@ class DetectionService {
672
754
  this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
673
755
  }
674
756
  catch (error) {
675
- if (error.message.includes('failed to allocate a buffer')) {
757
+ // Múltiples tipos de errores de memoria que pueden ocurrir
758
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
759
+ error.message.includes('Out of memory') ||
760
+ error.message.includes('RangeError: Out of memory') ||
761
+ error.message.includes('no available backend found');
762
+ if (isMemoryError) {
676
763
  const fallbackOptions = {
677
764
  executionProviders: ['wasm'],
678
765
  graphOptimizationLevel: 'disabled',
@@ -682,8 +769,17 @@ class DetectionService {
682
769
  executionMode: 'sequential',
683
770
  interOpNumThreads: 1,
684
771
  intraOpNumThreads: 1,
772
+ // Configuraciones adicionales para dispositivos con poca memoria
773
+ enableProfiling: false,
774
+ sessionLogSeverityLevel: 4,
775
+ sessionLogVerbosityLevel: 0,
685
776
  };
686
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
777
+ try {
778
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
779
+ }
780
+ catch (fallbackError) {
781
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
782
+ }
687
783
  }
688
784
  else {
689
785
  throw error;
@@ -723,7 +819,12 @@ class DetectionService {
723
819
  this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
724
820
  }
725
821
  catch (error) {
726
- if (error.message.includes('failed to allocate a buffer')) {
822
+ // Múltiples tipos de errores de memoria que pueden ocurrir
823
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
824
+ error.message.includes('Out of memory') ||
825
+ error.message.includes('RangeError: Out of memory') ||
826
+ error.message.includes('no available backend found');
827
+ if (isMemoryError) {
727
828
  const fallbackOptions = {
728
829
  executionProviders: ['wasm'],
729
830
  graphOptimizationLevel: 'disabled',
@@ -733,8 +834,17 @@ class DetectionService {
733
834
  executionMode: 'sequential',
734
835
  interOpNumThreads: 1,
735
836
  intraOpNumThreads: 1,
837
+ // Configuraciones adicionales para dispositivos con poca memoria
838
+ enableProfiling: false,
839
+ sessionLogSeverityLevel: 4,
840
+ sessionLogVerbosityLevel: 0,
736
841
  };
737
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
842
+ try {
843
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
844
+ }
845
+ catch (fallbackError) {
846
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
847
+ }
738
848
  }
739
849
  else {
740
850
  throw error;
@@ -1091,7 +1201,7 @@ class ServiceContainer {
1091
1201
  }
1092
1202
  }
1093
1203
 
1094
- 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{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@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}}.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)}}.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)}.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(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@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{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
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}}";
1095
1205
 
1096
1206
  const JaakStamps = class {
1097
1207
  constructor(hostRef) {
@@ -1147,6 +1257,10 @@ const JaakStamps = class {
1147
1257
  };
1148
1258
  backDocumentTimerRemaining = 0;
1149
1259
  showManualCaptureButton = false;
1260
+ performanceDegradedMode = false; // Auto-switched to manual mode due to performance
1261
+ showPerformanceMessage = false; // Show performance message temporarily
1262
+ captureStateVersion = 0; // Triggers re-render when StateManager changes
1263
+ processingButton = null; // Track which button is processing
1150
1264
  // Services
1151
1265
  serviceContainer;
1152
1266
  eventBus;
@@ -1165,12 +1279,12 @@ const JaakStamps = class {
1165
1279
  alignmentStartTime;
1166
1280
  alignmentTimer;
1167
1281
  backDocumentTimer;
1282
+ orientationTimer;
1168
1283
  // Performance monitoring
1169
1284
  performanceMetrics = {
1170
1285
  fps: 0,
1171
1286
  inferenceTime: 0,
1172
1287
  memoryUsage: 0,
1173
- cpuUsage: 0,
1174
1288
  onnxLoadTime: 0,
1175
1289
  frameProcessingTime: 0,
1176
1290
  totalDetections: 0,
@@ -1189,18 +1303,29 @@ const JaakStamps = class {
1189
1303
  MIN_INFERENCE_INTERVAL = 50;
1190
1304
  performanceHistory = [];
1191
1305
  PERFORMANCE_HISTORY_SIZE = 10;
1306
+ // Sistema simplificado de monitoreo de rendimiento
1307
+ PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
1308
+ SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
1309
+ slowFrameCount = 0; // Contador de frames consecutivos lentos
1310
+ processedFramesCount = 0; // Contador total de frames procesados
1311
+ hasAutoSwitchedToManual = false;
1192
1312
  // Canvas pool for optimized screenshot capture
1193
1313
  canvasPool = [];
1194
1314
  MAX_CANVAS_POOL_SIZE = 3;
1195
1315
  async componentDidLoad() {
1196
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1197
- await this.initializeServices();
1198
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1199
- await this.setupEventListeners();
1200
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1201
- await this.initializeComponent();
1202
- if (this.debug) {
1203
- this.initializePerformanceMonitor();
1316
+ try {
1317
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1318
+ await this.initializeServices();
1319
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1320
+ await this.setupEventListeners();
1321
+ this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1322
+ await this.initializeComponent();
1323
+ if (this.debug) {
1324
+ this.initializePerformanceMonitor();
1325
+ }
1326
+ }
1327
+ catch (error) {
1328
+ this.updateStatus('Error de inicialización', error.message, 'error');
1204
1329
  }
1205
1330
  }
1206
1331
  async initializeServices() {
@@ -1221,14 +1346,8 @@ const JaakStamps = class {
1221
1346
  this.detectionService = this.serviceContainer.getDetectionService();
1222
1347
  }
1223
1348
  async setupEventListeners() {
1224
- this.eventBus.on('state-changed', (data) => {
1225
- this.handleStateChange(data);
1226
- });
1227
- this.eventBus.on('camera-changed', () => {
1228
- // Camera changed event
1229
- });
1230
- this.eventBus.on('error', () => {
1231
- // Handle service errors
1349
+ this.eventBus.on('state-changed', () => {
1350
+ this.handleStateChange();
1232
1351
  });
1233
1352
  }
1234
1353
  async initializeComponent() {
@@ -1264,7 +1383,7 @@ const JaakStamps = class {
1264
1383
  if (!window.ort) {
1265
1384
  this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
1266
1385
  const script = document.createElement('script');
1267
- 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';
1268
1387
  document.head.appendChild(script);
1269
1388
  await new Promise((resolve) => {
1270
1389
  script.onload = () => {
@@ -1335,7 +1454,8 @@ const JaakStamps = class {
1335
1454
  return false;
1336
1455
  }
1337
1456
  }
1338
- handleStateChange(data) {
1457
+ handleStateChange() {
1458
+ // Handle state changes from StateManager if needed
1339
1459
  }
1340
1460
  initializeResizeObserver() {
1341
1461
  if ('ResizeObserver' in window && this.detectionContainer) {
@@ -1344,6 +1464,18 @@ const JaakStamps = class {
1344
1464
  });
1345
1465
  resizeObserver.observe(this.detectionContainer.parentElement);
1346
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
+ });
1347
1479
  }
1348
1480
  handleResize() {
1349
1481
  if (this.detectionContainer) {
@@ -1352,10 +1484,17 @@ const JaakStamps = class {
1352
1484
  this.updateMaskDimensions(rect);
1353
1485
  }
1354
1486
  }
1487
+ triggerRerender() {
1488
+ this.captureStateVersion = this.captureStateVersion + 1;
1489
+ }
1355
1490
  getGuideText(step) {
1356
1491
  if (step === 'completed') {
1357
1492
  return 'Proceso completado';
1358
1493
  }
1494
+ // Check if we should show performance message
1495
+ if (this.showPerformanceMessage && step === 'front') {
1496
+ return 'Modo manual activado por rendimiento. Use el botón para capturar.';
1497
+ }
1359
1498
  if (step === 'front') {
1360
1499
  // Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
1361
1500
  if (this.showManualCaptureButton) {
@@ -1513,8 +1652,14 @@ const JaakStamps = class {
1513
1652
  }
1514
1653
  }
1515
1654
  async skipBackCapture() {
1655
+ console.log('🟡 Botón clicked: Saltar reverso');
1656
+ // Set processing state immediately
1657
+ this.processingButton = 'skip-back';
1658
+ // Add small delay to show spinner
1659
+ await new Promise(resolve => setTimeout(resolve, 100));
1516
1660
  const readyCheck = this.isComponentReady();
1517
1661
  if (!readyCheck.ready) {
1662
+ this.processingButton = null;
1518
1663
  return { success: false, error: readyCheck.message };
1519
1664
  }
1520
1665
  try {
@@ -1531,6 +1676,10 @@ const JaakStamps = class {
1531
1676
  catch (error) {
1532
1677
  return { success: false, error: error.message };
1533
1678
  }
1679
+ finally {
1680
+ // Always clear processing state when done
1681
+ this.processingButton = null;
1682
+ }
1534
1683
  }
1535
1684
  async getStatus() {
1536
1685
  const readyCheck = this.isComponentReady();
@@ -1585,9 +1734,21 @@ const JaakStamps = class {
1585
1734
  if (this.stateManager) {
1586
1735
  this.stateManager.updateCaptureState({ isLoading: true });
1587
1736
  }
1588
- await this.detectionService.loadModel();
1737
+ try {
1738
+ await this.detectionService.loadModel();
1739
+ }
1740
+ catch (modelError) {
1741
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
1742
+ throw modelError;
1743
+ }
1589
1744
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1590
- await this.detectionService.loadClassificationModel();
1745
+ try {
1746
+ await this.detectionService.loadClassificationModel();
1747
+ }
1748
+ catch (classificationError) {
1749
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1750
+ throw classificationError;
1751
+ }
1591
1752
  const loadEndTime = performance.now();
1592
1753
  const loadTime = loadEndTime - loadStartTime;
1593
1754
  // Record ONNX load time
@@ -1696,17 +1857,63 @@ const JaakStamps = class {
1696
1857
  // Always allow getting capture delay, even during initialization
1697
1858
  return this.captureDelay;
1698
1859
  }
1860
+ // Memory and device capability detection
1861
+ checkDeviceCapabilities() {
1862
+ const deviceMemory = navigator.deviceMemory;
1863
+ // Siempre intentar usar ML, independientemente del dispositivo
1864
+ // Las restricciones anteriores no eran confiables
1865
+ return {
1866
+ canUseML: true,
1867
+ reason: 'Dispositivo compatible con detección automática.',
1868
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
1869
+ };
1870
+ }
1699
1871
  // DETECTION METHODS
1700
1872
  async startDetection() {
1701
1873
  try {
1874
+ // Verificar capacidades del dispositivo antes de cargar modelos
1875
+ const capabilities = this.checkDeviceCapabilities();
1876
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
1877
+ if (!capabilities.canUseML && this.useDocumentDetector) {
1878
+ this.switchToManualModeWithWarning(capabilities.reason);
1879
+ return;
1880
+ }
1702
1881
  // Paso 1: Verificar y cargar modelos si es necesario
1703
1882
  if (!this.detectionService.isModelLoaded()) {
1704
1883
  const loadStartTime = performance.now();
1705
1884
  this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1706
1885
  this.stateManager.updateCaptureState({ isLoading: true });
1707
- await this.detectionService.loadModel();
1886
+ try {
1887
+ await this.detectionService.loadModel();
1888
+ }
1889
+ catch (modelError) {
1890
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1891
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
1892
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1893
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
1894
+ return;
1895
+ }
1896
+ else {
1897
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
1898
+ throw modelError;
1899
+ }
1900
+ }
1708
1901
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1709
- await this.detectionService.loadClassificationModel();
1902
+ try {
1903
+ await this.detectionService.loadClassificationModel();
1904
+ }
1905
+ catch (classificationError) {
1906
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1907
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
1908
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1909
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
1910
+ return;
1911
+ }
1912
+ else {
1913
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1914
+ throw classificationError;
1915
+ }
1916
+ }
1710
1917
  const loadEndTime = performance.now();
1711
1918
  const loadTime = loadEndTime - loadStartTime;
1712
1919
  // Record ONNX load time
@@ -1716,19 +1923,40 @@ const JaakStamps = class {
1716
1923
  }
1717
1924
  // Paso 2: Detectar y configurar dispositivos
1718
1925
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1719
- await this.cameraService.enumerateDevices();
1720
- await this.updateCameraInfoWithAutofocus();
1926
+ try {
1927
+ await this.cameraService.enumerateDevices();
1928
+ }
1929
+ catch (enumerateError) {
1930
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1931
+ }
1932
+ try {
1933
+ await this.updateCameraInfoWithAutofocus();
1934
+ }
1935
+ catch (cameraInfoError) {
1936
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1937
+ }
1721
1938
  // Paso 3: Configurar cámara seleccionada
1722
1939
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1723
- const stream = await this.cameraService.setupCamera();
1724
- // Paso 4: Inicializar video y ocultar estado inmediatamente
1940
+ let stream;
1941
+ try {
1942
+ stream = await this.cameraService.setupCamera();
1943
+ }
1944
+ catch (setupError) {
1945
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1946
+ }
1947
+ // Paso 4: Inicializar video y mostrar estado de análisis inicial
1725
1948
  if (this.useDocumentDetector) {
1726
- this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
1949
+ this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
1727
1950
  }
1728
1951
  else {
1729
1952
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
1730
1953
  }
1731
- await this.initializeVideoStream(stream);
1954
+ try {
1955
+ await this.initializeVideoStream(stream);
1956
+ }
1957
+ catch (videoError) {
1958
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1959
+ }
1732
1960
  // Paso 5: Calibrar máscara
1733
1961
  if (this.detectionContainer) {
1734
1962
  const container = this.detectionContainer.parentElement;
@@ -1759,6 +1987,12 @@ const JaakStamps = class {
1759
1987
  this.videoRef.onloadedmetadata = async () => {
1760
1988
  await this.videoRef.play();
1761
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
+ }
1762
1996
  resolve();
1763
1997
  };
1764
1998
  });
@@ -1773,8 +2007,8 @@ const JaakStamps = class {
1773
2007
  const captureState = this.stateManager.getCaptureState();
1774
2008
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
1775
2009
  return;
1776
- // Show manual capture button when document detector is disabled
1777
- if (!this.useDocumentDetector) {
2010
+ // Show manual capture button when document detector is disabled or performance is degraded
2011
+ if (!this.useDocumentDetector || this.performanceDegradedMode) {
1778
2012
  this.showManualCaptureButton = true;
1779
2013
  // Continue the loop for UI updates but skip detection logic
1780
2014
  if (captureState.step !== 'completed') {
@@ -1782,7 +2016,7 @@ const JaakStamps = class {
1782
2016
  }
1783
2017
  return;
1784
2018
  }
1785
- else {
2019
+ else if (!this.performanceDegradedMode) {
1786
2020
  this.showManualCaptureButton = false;
1787
2021
  }
1788
2022
  if (captureState.isDetectionPaused) {
@@ -1812,10 +2046,27 @@ const JaakStamps = class {
1812
2046
  this.lastInferenceTime = currentTime;
1813
2047
  // Measure preprocessing and inference time
1814
2048
  const inferenceStartTime = performance.now();
1815
- const inputTensor = this.detectionService.preprocess(this.videoRef);
1816
- // Standard detection without quality validation
1817
- const detections = await this.detectionService.runInference(inputTensor);
1818
- const inferenceTime = performance.now() - inferenceStartTime;
2049
+ let inputTensor;
2050
+ let detections;
2051
+ let inferenceTime;
2052
+ try {
2053
+ inputTensor = this.detectionService.preprocess(this.videoRef);
2054
+ }
2055
+ catch (preprocessError) {
2056
+ this.switchToManualModeWithError('Error al procesar imagen');
2057
+ return;
2058
+ }
2059
+ try {
2060
+ // Standard detection without timeout (timeout is handled by frame counting)
2061
+ detections = await this.detectionService.runInference(inputTensor);
2062
+ inferenceTime = performance.now() - inferenceStartTime;
2063
+ }
2064
+ catch (inferenceError) {
2065
+ this.switchToManualModeWithError('Error en detección automática');
2066
+ return;
2067
+ }
2068
+ // Increment frame count
2069
+ this.processedFramesCount++;
1819
2070
  // Record ONNX performance metrics
1820
2071
  if (this.debug) {
1821
2072
  this.recordOnnxPerformance(0, inferenceTime);
@@ -1854,9 +2105,30 @@ const JaakStamps = class {
1854
2105
  }
1855
2106
  this.updateMaskColor(detections);
1856
2107
  // Record frame processing metrics
2108
+ const frameEndTime = performance.now();
2109
+ const totalFrameTime = frameEndTime - frameStartTime;
2110
+ // Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
2111
+ if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
2112
+ // Verificar si el frame es lento
2113
+ if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
2114
+ this.slowFrameCount++;
2115
+ // Si tenemos suficientes frames lentos consecutivos, cambiar a manual
2116
+ if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
2117
+ this.switchToManualMode();
2118
+ }
2119
+ }
2120
+ else {
2121
+ // Frame rápido, reiniciar contador
2122
+ if (this.slowFrameCount > 0) {
2123
+ this.slowFrameCount = 0;
2124
+ }
2125
+ }
2126
+ // Cambiar mensaje de estado después de analizar los primeros 2 frames
2127
+ if (this.processedFramesCount === 2) {
2128
+ this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
2129
+ }
2130
+ }
1857
2131
  if (this.debug) {
1858
- const frameEndTime = performance.now();
1859
- const totalFrameTime = frameEndTime - frameStartTime;
1860
2132
  this.recordFrameProcessing(totalFrameTime, detections.length);
1861
2133
  }
1862
2134
  // Continue detection loop
@@ -1892,6 +2164,12 @@ const JaakStamps = class {
1892
2164
  clearTimeout(this.alignmentTimer);
1893
2165
  this.alignmentTimer = undefined;
1894
2166
  }
2167
+ if (this.orientationTimer) {
2168
+ clearTimeout(this.orientationTimer);
2169
+ this.orientationTimer = undefined;
2170
+ }
2171
+ // Liberar recursos de ONNX al limpiar el componente
2172
+ this.releaseOnnxResources();
1895
2173
  if (this.performanceUpdateInterval) {
1896
2174
  clearInterval(this.performanceUpdateInterval);
1897
2175
  this.performanceUpdateInterval = undefined;
@@ -1912,7 +2190,7 @@ const JaakStamps = class {
1912
2190
  isCapturing: false
1913
2191
  };
1914
2192
  const cameraInfo = this.cameraInfoWithAutofocus;
1915
- return (index.h("div", { key: 'e0b32b1b41b58e3a58b552199384080e8f2c069b', class: "detector-container" }, index.h("div", { key: '03c77af311ff12d936994d629d58550cbaae190c', class: "video-container" }, index.h("video", { key: '94f15b34f34367a10d17816a31cd147adb57bc90', 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: '982be192792dfaf644eb173bfee9b26ebd229288', 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: {
1916
2194
  position: 'absolute',
1917
2195
  left: `${box.x}px`,
1918
2196
  top: `${box.y}px`,
@@ -1921,9 +2199,9 @@ const JaakStamps = class {
1921
2199
  border: '2px solid #32406C',
1922
2200
  pointerEvents: 'none',
1923
2201
  boxSizing: 'border-box'
1924
- } })))), this.isMaskReady && (index.h("div", { key: 'f5212ff70db65d2b836a3ac456a1701701630660', class: "overlay-mask" }, index.h("div", { key: '5080217f4962c2c7152def39cc978ba1c40a3a1c', class: "card-outline" }, index.h("div", { key: 'd37f1ca8723d79b10d1c6a3afaf54e2f2657ae79', class: "side side-top" }), index.h("div", { key: 'c5bb4f7d646a991f43598688ffa909c04697bb06', class: "side side-right" }), index.h("div", { key: '4a5040b4a236367dcdf72f4e72d497b8b6dee6c0', class: "side side-bottom" }), index.h("div", { key: 'c74ecef56eee8e92cd9491d693fa89a6c01bdbbd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '2a2932b0c03835193ebf86ddb5f13173b1dba367', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '41fcf2d79106d3ed928fc2842cb6a6725ba4d4f6', class: "back-capture-section" }, index.h("div", { key: '98dbe1bbe454bad42c3e155247f25a9151aaf1f9', class: "back-capture-buttons" }, !this.useDocumentDetector && (index.h("button", { key: '01693bc0d6915a4d02f318268020dfbcf3a70b94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), index.h("button", { key: '0a4ba55cbbea867b936a83a16a622a3c36a225f9', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, 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
1925
2203
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1926
- : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: '1db208a53dea37211bdd88254e575142b7abf91f', class: "camera-controls" }, index.h("button", { key: '90755839358f997c3ce2f4c36a8d17d39ea5f8ad', 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: 'ef7889ab82f9eaabcabec3e3dc3ebb7850818dd4', class: "camera-selector-dropdown" }, index.h("div", { key: 'e28fb005316a742318be3cf8582f520392b71bba', class: "camera-selector-header" }, index.h("span", { key: '97338030b1aedb50c5f5a588ea03c0f19bcb0ddd' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '1537c907dfab17c232a256f9fc9210cbd0125cc6', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '509178119e6eed5fe0a6ea287904a53bd9fbecbc', 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: '485f7e1a97c43ed280a106bde3c6ee910625025c', class: "device-info" }, index.h("small", { key: 'd76edba943f4e06c14024923496555fa1fe6d845' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'c5184dd93aa41cff61b88ea2046b481efc87c1f2', class: "manual-capture-section" }, index.h("button", { key: '6e45d9a0fec60469eb926ff353cb1a440d59f5fe', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (index.h("div", { key: '9e296a942ba51cc1fd152e76ae406620c84ed721', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '517db50f7273788d94d5e6db6c40f65e00e18784', class: "flip-animation" }, index.h("div", { key: 'fbf6a26c4cc0539bd16b45899d19984af3449610', class: "id-card-icon" }), index.h("div", { key: 'fb3a201864d04531a078074458aa6e7f6580d565', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '10c694e233d26523d538ff0d2edbf6dcfb8e37c8', class: "success-animation" }, index.h("div", { key: '87e545650825ab805e887667281ecdf8bdb34c45', class: "check-icon" }), index.h("div", { key: '359da71f391d68e9437b749b949ee90685b96b3f', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '7c5533bb875eb3d9cdcc6cfdfc20e4d5bd98d103', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: 'f32df46ab83279387de606eadf53cd4a4009f449', class: "status-spinner" })), index.h("div", { key: 'dd640de5a3b64f5f99363318e2fc70e32ccbc7db', class: "status-content" }, index.h("div", { key: '618c0ab9cb0a24b0af3f6946052beab0d660fe6d', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'e52c2a4945c7f5a190a446ac588aa1668c21f26d', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '61cea10bc3438fb74aa176d71c4238b070cb0f09', class: "performance-monitor" }, index.h("div", { key: '44f6b1accb56d99a236b22c0769f870638d9c768', class: "performance-expanded" }, index.h("div", { key: '6febc85e319752b0aebc0a4e4050ea6d597375a7', class: "metrics-row" }, index.h("div", { key: '96b81895ad3cd742c96308ed53a2126af76eb619', class: "metric-compact" }, index.h("span", { key: '774a11c6fed6a87db7673b018b3cdc378c5252ef', class: "metric-label" }, "FPS"), index.h("span", { key: 'cb866dcbea661e5f715b82c4dd760f786fd5bb2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'a935aaadb83ea400bfaa984d1d845bdfd87becc8', class: "metric-compact" }, index.h("span", { key: 'ffacaa2cb7838403a297145b70586e255cc845c7', class: "metric-label" }, "MEM"), index.h("span", { key: 'a1d7a7abec97dc0544d50b11b372f699f750b76f', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'bb3d156a46569a8c290bc518c42ec7957bc5b94e', class: "metrics-row" }, index.h("div", { key: '4cadd8f845e4c12120ed549e4a73e02c6cbd9257', class: "metric-compact" }, index.h("span", { key: '1fc5661222f495b79fd7eb126e04f52eecab6fb3', class: "metric-label" }, "INF"), index.h("span", { key: 'eca1f59d1c41ab99158eec81592bc7150bfeabe1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: 'f708e2757adcb03e633111d96bd6786fb6b80ea6', class: "metric-compact" }, index.h("span", { key: 'a13099ded64180996d6f65bf215483ba83daccbc', class: "metric-label" }, "FRAME"), index.h("span", { key: '2765cd1f7c1a49df9de64639cc1b777c79f89f4c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '13aaf85a1b4b66294c4204f02d956b64665aa766', class: "metrics-row" }, index.h("div", { key: '40012f3f646d70c535a606ebbe581a2fe6fd2e50', class: "metric-compact" }, index.h("span", { key: '03f8b17746fe404485820d06a50173678db2fd25', class: "metric-label" }, "DET"), index.h("span", { key: 'c4f87430f142355054d9361330d6a5d508974b70', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'cf366a41d7f7cda6046c32cc7c514eb7b46b05d1', class: "metric-compact" }, index.h("span", { key: '925e7de15b96ba4dbbcdbf8a9f0e3173bae49573', class: "metric-label" }, "RATE"), index.h("span", { key: '9701e60c7793f2c205f3c9ffd254b6a115b8a68c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '636aeb9b2b8dd992d8a28a8db44b213e12514096', class: "watermark" }, index.h("img", { key: '0407b550d962a6113e05ad75e25a1a1bbd76d9ce', 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" })))));
1927
2205
  }
1928
2206
  // Utility methods
1929
2207
  updateDetectionBoxes(boxes) {
@@ -2039,9 +2317,24 @@ const JaakStamps = class {
2039
2317
  }
2040
2318
  }
2041
2319
  async takeManualScreenshot() {
2042
- if (!this.videoRef)
2320
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
2321
+ // Set processing state immediately
2322
+ const captureState = this.stateManager?.getCaptureState();
2323
+ if (captureState?.step === 'front') {
2324
+ this.processingButton = 'capture-front';
2325
+ }
2326
+ else if (captureState?.step === 'back') {
2327
+ this.processingButton = 'capture-back';
2328
+ }
2329
+ console.log('🔵 processingButton set to:', this.processingButton);
2330
+ // Add small delay to show spinner
2331
+ await new Promise(resolve => setTimeout(resolve, 100));
2332
+ if (!this.videoRef) {
2333
+ this.processingButton = null;
2043
2334
  return;
2335
+ }
2044
2336
  // When using manual capture, use mask coordinates for cropping
2337
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
2045
2338
  await this.takeScreenshotWithMaskCoordinates();
2046
2339
  }
2047
2340
  async takeScreenshotWithMaskCoordinates() {
@@ -2105,18 +2398,25 @@ const JaakStamps = class {
2105
2398
  if (captureState.step === 'front') {
2106
2399
  this.stateManager.setCapturedImages({
2107
2400
  front: {
2108
- fullFrame: captureCanvas.toDataURL('image/png'),
2109
- cropped: croppedCanvas.toDataURL('image/png')
2401
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2402
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2110
2403
  }
2111
2404
  });
2112
2405
  // Check if document classification is enabled (independent of detector)
2113
2406
  if (this.useDocumentClassification) {
2114
- const classification = await this.detectionService.classifyDocument(croppedCanvas);
2115
- if (classification && classification.class === 'passport') {
2116
- this.completeProcess(true);
2117
- this.returnCanvasToPool(captureCanvas);
2118
- this.returnCanvasToPool(croppedCanvas);
2119
- return;
2407
+ try {
2408
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
2409
+ if (classification && classification.class === 'passport') {
2410
+ this.completeProcess(true);
2411
+ this.returnCanvasToPool(captureCanvas);
2412
+ this.returnCanvasToPool(croppedCanvas);
2413
+ // Clear processing button when passport is detected
2414
+ this.processingButton = null;
2415
+ return;
2416
+ }
2417
+ }
2418
+ catch (classifyError) {
2419
+ // No cambiar a manual aquí, solo registrar el error y continuar
2120
2420
  }
2121
2421
  }
2122
2422
  this.stateManager.updateCaptureState({
@@ -2124,22 +2424,28 @@ const JaakStamps = class {
2124
2424
  isDetectionPaused: true,
2125
2425
  showFlipAnimation: true
2126
2426
  });
2427
+ this.triggerRerender();
2127
2428
  setTimeout(() => {
2128
2429
  this.stateManager.updateCaptureState({
2129
2430
  showFlipAnimation: false,
2130
2431
  isDetectionPaused: false
2131
2432
  });
2433
+ this.triggerRerender();
2132
2434
  this.startBackDocumentTimer();
2435
+ // Clear processing button after animation completes
2436
+ this.processingButton = null;
2133
2437
  }, 3000);
2134
2438
  }
2135
2439
  else if (captureState.step === 'back') {
2136
2440
  this.stateManager.setCapturedImages({
2137
2441
  back: {
2138
- fullFrame: captureCanvas.toDataURL('image/png'),
2139
- cropped: croppedCanvas.toDataURL('image/png')
2442
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2443
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2140
2444
  }
2141
2445
  });
2142
2446
  this.completeProcess(false);
2447
+ // Clear processing button after back capture completes
2448
+ this.processingButton = null;
2143
2449
  }
2144
2450
  // Return canvases to pool after use
2145
2451
  this.returnCanvasToPool(captureCanvas);
@@ -2172,16 +2478,21 @@ const JaakStamps = class {
2172
2478
  if (captureState.step === 'front') {
2173
2479
  this.stateManager.setCapturedImages({
2174
2480
  front: {
2175
- fullFrame: captureCanvas.toDataURL('image/png'),
2176
- cropped: croppedCanvas.toDataURL('image/png')
2481
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2482
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2177
2483
  }
2178
2484
  });
2179
2485
  // Check if document classification is enabled
2180
2486
  if (this.useDocumentClassification) {
2181
- const classification = await this.detectionService.classifyDocument(croppedCanvas);
2182
- if (classification && classification.class === 'passport') {
2183
- this.completeProcess(true);
2184
- return;
2487
+ try {
2488
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
2489
+ if (classification && classification.class === 'passport') {
2490
+ this.completeProcess(true);
2491
+ return;
2492
+ }
2493
+ }
2494
+ catch (classifyError) {
2495
+ // No cambiar a manual aquí, solo registrar el error y continuar
2185
2496
  }
2186
2497
  }
2187
2498
  this.stateManager.updateCaptureState({
@@ -2200,8 +2511,8 @@ const JaakStamps = class {
2200
2511
  else if (captureState.step === 'back') {
2201
2512
  this.stateManager.setCapturedImages({
2202
2513
  back: {
2203
- fullFrame: captureCanvas.toDataURL('image/png'),
2204
- cropped: croppedCanvas.toDataURL('image/png')
2514
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
2515
+ cropped: croppedCanvas.toDataURL('image/jpeg')
2205
2516
  }
2206
2517
  });
2207
2518
  this.completeProcess(false);
@@ -2218,6 +2529,138 @@ const JaakStamps = class {
2218
2529
  cardOutline?.classList.remove('capturing');
2219
2530
  }, 600);
2220
2531
  }
2532
+ // Método simplificado para cambio a modo manual
2533
+ switchToManualMode() {
2534
+ // Only switch if detector is enabled and not already switched
2535
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2536
+ return;
2537
+ }
2538
+ // Detener todo el monitoreo de performance
2539
+ this.stopPerformanceMonitoring();
2540
+ // Liberar recursos de ONNX
2541
+ this.releaseOnnxResources();
2542
+ // Resetear máscara a blanco y limpiar detecciones
2543
+ this.resetMaskToWhite();
2544
+ this.detectionBoxes = [];
2545
+ this.hasAutoSwitchedToManual = true;
2546
+ this.performanceDegradedMode = true;
2547
+ this.showManualCaptureButton = true;
2548
+ this.showPerformanceMessage = true;
2549
+ // Hide performance message after 5 seconds
2550
+ setTimeout(() => {
2551
+ this.showPerformanceMessage = false;
2552
+ }, 5000);
2553
+ }
2554
+ // Método para cambiar a modo manual por errores de ONNX
2555
+ switchToManualModeWithWarning(warningMessage) {
2556
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2557
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2558
+ return;
2559
+ }
2560
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
2561
+ // Resetear máscara a blanco y limpiar detecciones
2562
+ this.resetMaskToWhite();
2563
+ this.detectionBoxes = [];
2564
+ // Cambiar a modo manual
2565
+ this.hasAutoSwitchedToManual = true;
2566
+ this.performanceDegradedMode = true;
2567
+ this.showManualCaptureButton = true;
2568
+ this.useDocumentDetector = false; // Desactivar detector automático
2569
+ // Actualizar el estado con mensaje informativo (no error)
2570
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
2571
+ // Continuar con la configuración de cámara
2572
+ this.continueWithCameraSetup();
2573
+ }
2574
+ async continueWithCameraSetup() {
2575
+ try {
2576
+ // Paso 2: Detectar y configurar dispositivos
2577
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
2578
+ try {
2579
+ await this.cameraService.enumerateDevices();
2580
+ }
2581
+ catch (enumerateError) {
2582
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
2583
+ }
2584
+ try {
2585
+ await this.updateCameraInfoWithAutofocus();
2586
+ }
2587
+ catch (cameraInfoError) {
2588
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
2589
+ }
2590
+ // Paso 3: Configurar cámara seleccionada
2591
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
2592
+ let stream;
2593
+ try {
2594
+ stream = await this.cameraService.setupCamera();
2595
+ }
2596
+ catch (setupError) {
2597
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
2598
+ }
2599
+ // Paso 4: Inicializar video
2600
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
2601
+ try {
2602
+ await this.initializeVideoStream(stream);
2603
+ }
2604
+ catch (videoError) {
2605
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
2606
+ }
2607
+ // Paso 5: Calibrar máscara
2608
+ if (this.detectionContainer) {
2609
+ const container = this.detectionContainer.parentElement;
2610
+ const rect = container.getBoundingClientRect();
2611
+ this.updateMaskDimensions(rect);
2612
+ }
2613
+ // Finalizar
2614
+ this.startTime = Date.now();
2615
+ this.stateManager.updateCaptureState({ isLoading: false });
2616
+ // Mostrar botón manual (ya debe estar habilitado)
2617
+ this.showManualCaptureButton = true;
2618
+ }
2619
+ catch (error) {
2620
+ this.updateStatus('Error de cámara', error.message, 'error');
2621
+ throw error;
2622
+ }
2623
+ }
2624
+ switchToManualModeWithError(errorMessage) {
2625
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2626
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2627
+ return;
2628
+ }
2629
+ // Liberar recursos de ONNX inmediatamente
2630
+ this.releaseOnnxResources();
2631
+ // Resetear máscara a blanco y limpiar detecciones
2632
+ this.resetMaskToWhite();
2633
+ this.detectionBoxes = [];
2634
+ // Cambiar a modo manual
2635
+ this.hasAutoSwitchedToManual = true;
2636
+ this.performanceDegradedMode = true;
2637
+ this.showManualCaptureButton = true;
2638
+ this.useDocumentDetector = false; // Desactivar detector automático
2639
+ // Actualizar el estado con mensaje de error
2640
+ this.updateStatus('Modo manual activado', errorMessage, 'error');
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
+ });
2648
+ }
2649
+ stopPerformanceMonitoring() {
2650
+ // Nota: No necesitamos limpiar más variables porque ya no se usarán
2651
+ // El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
2652
+ }
2653
+ // Método para liberar recursos de ONNX cuando se cambia a modo manual
2654
+ releaseOnnxResources() {
2655
+ try {
2656
+ // Liberar recursos del servicio de detección
2657
+ if (this.detectionService) {
2658
+ this.detectionService.cleanup();
2659
+ }
2660
+ }
2661
+ catch (error) {
2662
+ }
2663
+ }
2221
2664
  completeProcess(skippedBack = false) {
2222
2665
  this.stateManager.updateCaptureState({
2223
2666
  step: 'completed',
@@ -2288,6 +2731,12 @@ const JaakStamps = class {
2288
2731
  try {
2289
2732
  // Close the selector immediately when user selects a camera
2290
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
+ }
2291
2740
  this.isSwitchingCamera = true;
2292
2741
  // Stop current video stream
2293
2742
  if (this.videoStream) {
@@ -2331,6 +2780,13 @@ const JaakStamps = class {
2331
2780
  this.detectionBoxes = [];
2332
2781
  this.alignmentStartTime = undefined;
2333
2782
  this.hasDocumentDetected = false;
2783
+ // Reset sistema simplificado
2784
+ this.processedFramesCount = 0;
2785
+ this.slowFrameCount = 0;
2786
+ // Reset performance degradation state
2787
+ this.hasAutoSwitchedToManual = false;
2788
+ this.performanceDegradedMode = false;
2789
+ this.showPerformanceMessage = false;
2334
2790
  if (this.alignmentTimer) {
2335
2791
  clearTimeout(this.alignmentTimer);
2336
2792
  this.alignmentTimer = undefined;
@@ -2434,6 +2890,24 @@ const JaakStamps = class {
2434
2890
  // Performance is good, use base frame skip
2435
2891
  return this.BASE_FRAME_SKIP;
2436
2892
  }
2893
+ // Método para resetear la máscara a color blanco
2894
+ resetMaskToWhite() {
2895
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
2896
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
2897
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
2898
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
2899
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
2900
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
2901
+ // Remover todas las clases de alineación y estado perfecto
2902
+ cardOutline?.classList.remove('perfect-match');
2903
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
2904
+ topSide?.classList.remove('aligned');
2905
+ rightSide?.classList.remove('aligned');
2906
+ bottomSide?.classList.remove('aligned');
2907
+ leftSide?.classList.remove('aligned');
2908
+ // Resetear estado de alineación
2909
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
2910
+ }
2437
2911
  // Canvas pool management for screenshots
2438
2912
  getPooledCanvas(width, height) {
2439
2913
  // Try to find a canvas with matching dimensions