@jaak.ai/stamps 2.1.0-dev.1 → 2.1.0-dev.11

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 (56) hide show
  1. package/README.md +11 -1
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
  3. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
  4. package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
  5. package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
  6. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
  7. package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
  8. package/dist/cjs/jaak-stamps.cjs.entry.js +644 -60
  9. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  10. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  11. package/dist/cjs/loader.cjs.js +4 -3
  12. package/dist/cjs/loader.cjs.js.map +1 -1
  13. package/dist/collection/collection-manifest.json +1 -1
  14. package/dist/collection/components/my-component/my-component.css +210 -11
  15. package/dist/collection/components/my-component/my-component.js +618 -58
  16. package/dist/collection/components/my-component/my-component.js.map +1 -1
  17. package/dist/collection/services/DetectionService.js +48 -5
  18. package/dist/collection/services/DetectionService.js.map +1 -1
  19. package/dist/collection/services/ServiceContainer.js +3 -2
  20. package/dist/collection/services/ServiceContainer.js.map +1 -1
  21. package/dist/components/index.js +165 -113
  22. package/dist/components/index.js.map +1 -1
  23. package/dist/components/jaak-stamps.js +650 -60
  24. package/dist/components/jaak-stamps.js.map +1 -1
  25. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  26. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  27. package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
  28. package/dist/esm/index-Drbzcuq-.js.map +1 -0
  29. package/dist/esm/jaak-stamps-webcomponent.js +5 -4
  30. package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
  31. package/dist/esm/jaak-stamps.entry.js +644 -60
  32. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  33. package/dist/esm/loader.js +4 -3
  34. package/dist/esm/loader.js.map +1 -1
  35. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  36. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
  37. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  38. package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
  39. package/dist/jaak-stamps-webcomponent/p-5f7301f1.entry.js +2 -0
  40. package/dist/jaak-stamps-webcomponent/p-5f7301f1.entry.js.map +1 -0
  41. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
  42. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
  43. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
  44. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
  45. package/dist/types/components/my-component/my-component.d.ts +25 -0
  46. package/dist/types/components.d.ts +8 -0
  47. package/dist/types/services/DetectionService.d.ts +2 -1
  48. package/dist/types/services/ServiceContainer.d.ts +1 -0
  49. package/dist/types/stencil-public-runtime.d.ts +12 -5
  50. package/package.json +3 -1
  51. package/dist/cjs/index-BfhtOB0D.js.map +0 -1
  52. package/dist/esm/index-BP1Q4KOg.js.map +0 -1
  53. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
  54. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
  55. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
  56. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +0 -1
@@ -630,6 +630,7 @@ class DeviceStrategyFactory {
630
630
  class DetectionService {
631
631
  debug;
632
632
  useDocumentClassification;
633
+ useDocumentDetector;
633
634
  session;
634
635
  mobileNetSession;
635
636
  mobileNetClassMap;
@@ -647,9 +648,10 @@ class DetectionService {
647
648
  // Static canvas pool for reuse across instances
648
649
  static canvasPool = new Map();
649
650
  static MAX_POOL_SIZE = 5;
650
- constructor(debug = false, useDocumentClassification = false) {
651
+ constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
651
652
  this.debug = debug;
652
653
  this.useDocumentClassification = useDocumentClassification;
654
+ this.useDocumentDetector = useDocumentDetector;
653
655
  this.deviceStrategy = DeviceStrategyFactory.createStrategy();
654
656
  this.initializeCanvasPool();
655
657
  }
@@ -657,13 +659,23 @@ class DetectionService {
657
659
  if (this.modelLoaded || this.session) {
658
660
  return;
659
661
  }
662
+ if (!this.useDocumentDetector) {
663
+ // Skip loading the detection model but mark as "loaded" for compatibility
664
+ this.modelLoaded = true;
665
+ return;
666
+ }
660
667
  try {
661
668
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
662
669
  try {
663
670
  this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
664
671
  }
665
672
  catch (error) {
666
- if (error.message.includes('failed to allocate a buffer')) {
673
+ // Múltiples tipos de errores de memoria que pueden ocurrir
674
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
675
+ error.message.includes('Out of memory') ||
676
+ error.message.includes('RangeError: Out of memory') ||
677
+ error.message.includes('no available backend found');
678
+ if (isMemoryError) {
667
679
  const fallbackOptions = {
668
680
  executionProviders: ['wasm'],
669
681
  graphOptimizationLevel: 'disabled',
@@ -673,8 +685,17 @@ class DetectionService {
673
685
  executionMode: 'sequential',
674
686
  interOpNumThreads: 1,
675
687
  intraOpNumThreads: 1,
688
+ // Configuraciones adicionales para dispositivos con poca memoria
689
+ enableProfiling: false,
690
+ sessionLogSeverityLevel: 4,
691
+ sessionLogVerbosityLevel: 0,
676
692
  };
677
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
693
+ try {
694
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
695
+ }
696
+ catch (fallbackError) {
697
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
698
+ }
678
699
  }
679
700
  else {
680
701
  throw error;
@@ -714,7 +735,12 @@ class DetectionService {
714
735
  this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
715
736
  }
716
737
  catch (error) {
717
- if (error.message.includes('failed to allocate a buffer')) {
738
+ // Múltiples tipos de errores de memoria que pueden ocurrir
739
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
740
+ error.message.includes('Out of memory') ||
741
+ error.message.includes('RangeError: Out of memory') ||
742
+ error.message.includes('no available backend found');
743
+ if (isMemoryError) {
718
744
  const fallbackOptions = {
719
745
  executionProviders: ['wasm'],
720
746
  graphOptimizationLevel: 'disabled',
@@ -724,8 +750,17 @@ class DetectionService {
724
750
  executionMode: 'sequential',
725
751
  interOpNumThreads: 1,
726
752
  intraOpNumThreads: 1,
753
+ // Configuraciones adicionales para dispositivos con poca memoria
754
+ enableProfiling: false,
755
+ sessionLogSeverityLevel: 4,
756
+ sessionLogVerbosityLevel: 0,
727
757
  };
728
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
758
+ try {
759
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
760
+ }
761
+ catch (fallbackError) {
762
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
763
+ }
729
764
  }
730
765
  else {
731
766
  throw error;
@@ -737,6 +772,10 @@ class DetectionService {
737
772
  }
738
773
  }
739
774
  isModelLoaded() {
775
+ if (!this.useDocumentDetector) {
776
+ // If detector is disabled, consider it "loaded" for compatibility
777
+ return this.modelLoaded;
778
+ }
740
779
  return this.modelLoaded && !!this.session;
741
780
  }
742
781
  preprocess(video) {
@@ -761,6 +800,10 @@ class DetectionService {
761
800
  return new window.ort.Tensor("float16", float16Data, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
762
801
  }
763
802
  async runInference(inputTensor) {
803
+ if (!this.useDocumentDetector) {
804
+ // Return empty array when document detector is disabled
805
+ return [];
806
+ }
764
807
  if (!this.session) {
765
808
  throw new Error('Detection model not loaded');
766
809
  }
@@ -1038,7 +1081,7 @@ class ServiceContainer {
1038
1081
  const eventBus = new EventBusService();
1039
1082
  const stateManager = new StateManagerService(eventBus);
1040
1083
  const cameraService = new CameraService(eventBus, config.preferredCamera);
1041
- const detectionService = new DetectionService(config.debug, config.useDocumentClassification);
1084
+ const detectionService = new DetectionService(config.debug, config.useDocumentClassification, config.useDocumentDetector);
1042
1085
  // Register services
1043
1086
  this.services.set('eventBus', eventBus);
1044
1087
  this.services.set('stateManager', stateManager);
@@ -1074,7 +1117,7 @@ class ServiceContainer {
1074
1117
  }
1075
1118
  }
1076
1119
 
1077
- 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%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);padding:12px 20px;border-radius:8px;max-width:300px;z-index:20;border:1px solid rgba(255, 255, 255, 0.1)}.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}.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}.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)}}.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}}.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:slideInFromLeft 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}}";
1120
+ const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
1078
1121
 
1079
1122
  const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
1080
1123
  constructor() {
@@ -1090,6 +1133,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1090
1133
  maskSize = 90;
1091
1134
  cropMargin = 20;
1092
1135
  useDocumentClassification = false;
1136
+ useDocumentDetector = true;
1093
1137
  preferredCamera = 'auto';
1094
1138
  captureDelay = 1500;
1095
1139
  enableBackDocumentTimer = false;
@@ -1130,6 +1174,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1130
1174
  detectionRate: 0
1131
1175
  };
1132
1176
  backDocumentTimerRemaining = 0;
1177
+ showManualCaptureButton = false;
1178
+ performanceDegradedMode = false; // Auto-switched to manual mode due to performance
1179
+ showPerformanceMessage = false; // Show performance message temporarily
1180
+ captureStateVersion = 0; // Triggers re-render when StateManager changes
1181
+ processingButton = null; // Track which button is processing
1133
1182
  // Services
1134
1183
  serviceContainer;
1135
1184
  eventBus;
@@ -1148,12 +1197,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1148
1197
  alignmentStartTime;
1149
1198
  alignmentTimer;
1150
1199
  backDocumentTimer;
1200
+ orientationTimer;
1151
1201
  // Performance monitoring
1152
1202
  performanceMetrics = {
1153
1203
  fps: 0,
1154
1204
  inferenceTime: 0,
1155
1205
  memoryUsage: 0,
1156
- cpuUsage: 0,
1157
1206
  onnxLoadTime: 0,
1158
1207
  frameProcessingTime: 0,
1159
1208
  totalDetections: 0,
@@ -1172,18 +1221,29 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1172
1221
  MIN_INFERENCE_INTERVAL = 50;
1173
1222
  performanceHistory = [];
1174
1223
  PERFORMANCE_HISTORY_SIZE = 10;
1224
+ // Sistema simplificado de monitoreo de rendimiento
1225
+ PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
1226
+ SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
1227
+ slowFrameCount = 0; // Contador de frames consecutivos lentos
1228
+ processedFramesCount = 0; // Contador total de frames procesados
1229
+ hasAutoSwitchedToManual = false;
1175
1230
  // Canvas pool for optimized screenshot capture
1176
1231
  canvasPool = [];
1177
1232
  MAX_CANVAS_POOL_SIZE = 3;
1178
1233
  async componentDidLoad() {
1179
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1180
- await this.initializeServices();
1181
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1182
- await this.setupEventListeners();
1183
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1184
- await this.initializeComponent();
1185
- if (this.debug) {
1186
- this.initializePerformanceMonitor();
1234
+ try {
1235
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1236
+ await this.initializeServices();
1237
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1238
+ await this.setupEventListeners();
1239
+ this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1240
+ await this.initializeComponent();
1241
+ if (this.debug) {
1242
+ this.initializePerformanceMonitor();
1243
+ }
1244
+ }
1245
+ catch (error) {
1246
+ this.updateStatus('Error de inicialización', error.message, 'error');
1187
1247
  }
1188
1248
  }
1189
1249
  async initializeServices() {
@@ -1193,6 +1253,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1193
1253
  maskSize: this.maskSize,
1194
1254
  cropMargin: this.cropMargin,
1195
1255
  useDocumentClassification: this.useDocumentClassification,
1256
+ useDocumentDetector: this.useDocumentDetector,
1196
1257
  preferredCamera: this.preferredCamera,
1197
1258
  captureDelay: this.captureDelay
1198
1259
  };
@@ -1203,14 +1264,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1203
1264
  this.detectionService = this.serviceContainer.getDetectionService();
1204
1265
  }
1205
1266
  async setupEventListeners() {
1206
- this.eventBus.on('state-changed', (data) => {
1207
- this.handleStateChange(data);
1208
- });
1209
- this.eventBus.on('camera-changed', () => {
1210
- // Camera changed event
1211
- });
1212
- this.eventBus.on('error', () => {
1213
- // Handle service errors
1267
+ this.eventBus.on('state-changed', () => {
1268
+ this.handleStateChange();
1214
1269
  });
1215
1270
  }
1216
1271
  async initializeComponent() {
@@ -1250,27 +1305,39 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1250
1305
  document.head.appendChild(script);
1251
1306
  await new Promise((resolve) => {
1252
1307
  script.onload = () => {
1253
- setTimeout(() => {
1254
- this.finalizeInitialization();
1308
+ setTimeout(async () => {
1309
+ await this.finalizeInitialization();
1255
1310
  resolve(undefined);
1256
1311
  }, 300);
1257
1312
  };
1258
1313
  });
1259
1314
  }
1260
1315
  else {
1261
- setTimeout(() => {
1262
- this.finalizeInitialization();
1316
+ setTimeout(async () => {
1317
+ await this.finalizeInitialization();
1263
1318
  }, 300);
1264
1319
  }
1265
1320
  }
1266
- finalizeInitialization() {
1321
+ async finalizeInitialization() {
1267
1322
  this.stateManager.updateCaptureState({ isLoading: false });
1268
- this.currentStatus = {
1269
- message: 'Listo para capturar',
1270
- description: '',
1271
- type: 'ready',
1272
- isInitialized: true
1273
- };
1323
+ // Check camera permissions before showing "ready" status
1324
+ const hasPermissions = await this.checkCameraPermissions();
1325
+ if (hasPermissions) {
1326
+ this.currentStatus = {
1327
+ message: 'Listo para capturar',
1328
+ description: '',
1329
+ type: 'ready',
1330
+ isInitialized: true
1331
+ };
1332
+ }
1333
+ else {
1334
+ this.currentStatus = {
1335
+ message: 'Permisos de cámara requeridos',
1336
+ description: 'Por favor, otorgue permisos de cámara para continuar',
1337
+ type: 'error',
1338
+ isInitialized: true
1339
+ };
1340
+ }
1274
1341
  this.emitReadyEvent();
1275
1342
  }
1276
1343
  updateStatus(message, description, type = 'loading') {
@@ -1285,7 +1352,28 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1285
1352
  const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
1286
1353
  this.isReady.emit(isDocumentReady);
1287
1354
  }
1288
- handleStateChange(data) {
1355
+ async checkCameraPermissions() {
1356
+ try {
1357
+ if (!navigator.permissions) {
1358
+ // Fallback: try to access camera directly
1359
+ try {
1360
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
1361
+ stream.getTracks().forEach(track => track.stop());
1362
+ return true;
1363
+ }
1364
+ catch {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ const permission = await navigator.permissions.query({ name: 'camera' });
1369
+ return permission.state === 'granted';
1370
+ }
1371
+ catch (error) {
1372
+ return false;
1373
+ }
1374
+ }
1375
+ handleStateChange() {
1376
+ // Handle state changes from StateManager if needed
1289
1377
  }
1290
1378
  initializeResizeObserver() {
1291
1379
  if ('ResizeObserver' in window && this.detectionContainer) {
@@ -1294,6 +1382,18 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1294
1382
  });
1295
1383
  resizeObserver.observe(this.detectionContainer.parentElement);
1296
1384
  }
1385
+ // Handle mobile device orientation changes
1386
+ window.addEventListener('orientationchange', () => {
1387
+ // Clear any existing orientation timer
1388
+ if (this.orientationTimer) {
1389
+ clearTimeout(this.orientationTimer);
1390
+ }
1391
+ // Add delay to ensure video dimensions are updated after rotation
1392
+ this.orientationTimer = window.setTimeout(() => {
1393
+ this.handleResize();
1394
+ this.orientationTimer = undefined;
1395
+ }, 300);
1396
+ });
1297
1397
  }
1298
1398
  handleResize() {
1299
1399
  if (this.detectionContainer) {
@@ -1302,6 +1402,36 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1302
1402
  this.updateMaskDimensions(rect);
1303
1403
  }
1304
1404
  }
1405
+ triggerRerender() {
1406
+ this.captureStateVersion = this.captureStateVersion + 1;
1407
+ }
1408
+ getGuideText(step) {
1409
+ if (step === 'completed') {
1410
+ return 'Proceso completado';
1411
+ }
1412
+ // Check if we should show performance message
1413
+ if (this.showPerformanceMessage && step === 'front') {
1414
+ return 'Modo manual activado por rendimiento. Use el botón para capturar.';
1415
+ }
1416
+ if (step === 'front') {
1417
+ // Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
1418
+ if (this.showManualCaptureButton) {
1419
+ return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
1420
+ }
1421
+ // Instrucción básica de alineación
1422
+ return 'Alinee su identificación con el marco';
1423
+ }
1424
+ else if (step === 'back') {
1425
+ // Para el reverso, mostrar instrucciones específicas según el modo
1426
+ if (this.useDocumentDetector) {
1427
+ return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
1428
+ }
1429
+ else {
1430
+ return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
1431
+ }
1432
+ }
1433
+ return 'Alinee su identificación con el marco';
1434
+ }
1305
1435
  updateMaskDimensions(containerRect) {
1306
1436
  if (!this.videoRef)
1307
1437
  return;
@@ -1440,8 +1570,14 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1440
1570
  }
1441
1571
  }
1442
1572
  async skipBackCapture() {
1573
+ console.log('🟡 Botón clicked: Saltar reverso');
1574
+ // Set processing state immediately
1575
+ this.processingButton = 'skip-back';
1576
+ // Add small delay to show spinner
1577
+ await new Promise(resolve => setTimeout(resolve, 100));
1443
1578
  const readyCheck = this.isComponentReady();
1444
1579
  if (!readyCheck.ready) {
1580
+ this.processingButton = null;
1445
1581
  return { success: false, error: readyCheck.message };
1446
1582
  }
1447
1583
  try {
@@ -1458,6 +1594,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1458
1594
  catch (error) {
1459
1595
  return { success: false, error: error.message };
1460
1596
  }
1597
+ finally {
1598
+ // Always clear processing state when done
1599
+ this.processingButton = null;
1600
+ }
1461
1601
  }
1462
1602
  async getStatus() {
1463
1603
  const readyCheck = this.isComponentReady();
@@ -1512,9 +1652,21 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1512
1652
  if (this.stateManager) {
1513
1653
  this.stateManager.updateCaptureState({ isLoading: true });
1514
1654
  }
1515
- await this.detectionService.loadModel();
1655
+ try {
1656
+ await this.detectionService.loadModel();
1657
+ }
1658
+ catch (modelError) {
1659
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
1660
+ throw modelError;
1661
+ }
1516
1662
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1517
- await this.detectionService.loadClassificationModel();
1663
+ try {
1664
+ await this.detectionService.loadClassificationModel();
1665
+ }
1666
+ catch (classificationError) {
1667
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1668
+ throw classificationError;
1669
+ }
1518
1670
  const loadEndTime = performance.now();
1519
1671
  const loadTime = loadEndTime - loadStartTime;
1520
1672
  // Record ONNX load time
@@ -1623,17 +1775,63 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1623
1775
  // Always allow getting capture delay, even during initialization
1624
1776
  return this.captureDelay;
1625
1777
  }
1778
+ // Memory and device capability detection
1779
+ checkDeviceCapabilities() {
1780
+ const deviceMemory = navigator.deviceMemory;
1781
+ // Siempre intentar usar ML, independientemente del dispositivo
1782
+ // Las restricciones anteriores no eran confiables
1783
+ return {
1784
+ canUseML: true,
1785
+ reason: 'Dispositivo compatible con detección automática.',
1786
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
1787
+ };
1788
+ }
1626
1789
  // DETECTION METHODS
1627
1790
  async startDetection() {
1628
1791
  try {
1792
+ // Verificar capacidades del dispositivo antes de cargar modelos
1793
+ const capabilities = this.checkDeviceCapabilities();
1794
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
1795
+ if (!capabilities.canUseML && this.useDocumentDetector) {
1796
+ this.switchToManualModeWithWarning(capabilities.reason);
1797
+ return;
1798
+ }
1629
1799
  // Paso 1: Verificar y cargar modelos si es necesario
1630
1800
  if (!this.detectionService.isModelLoaded()) {
1631
1801
  const loadStartTime = performance.now();
1632
1802
  this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1633
1803
  this.stateManager.updateCaptureState({ isLoading: true });
1634
- await this.detectionService.loadModel();
1804
+ try {
1805
+ await this.detectionService.loadModel();
1806
+ }
1807
+ catch (modelError) {
1808
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1809
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
1810
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1811
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
1812
+ return;
1813
+ }
1814
+ else {
1815
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
1816
+ throw modelError;
1817
+ }
1818
+ }
1635
1819
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1636
- await this.detectionService.loadClassificationModel();
1820
+ try {
1821
+ await this.detectionService.loadClassificationModel();
1822
+ }
1823
+ catch (classificationError) {
1824
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1825
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
1826
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1827
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
1828
+ return;
1829
+ }
1830
+ else {
1831
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1832
+ throw classificationError;
1833
+ }
1834
+ }
1637
1835
  const loadEndTime = performance.now();
1638
1836
  const loadTime = loadEndTime - loadStartTime;
1639
1837
  // Record ONNX load time
@@ -1643,14 +1841,40 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1643
1841
  }
1644
1842
  // Paso 2: Detectar y configurar dispositivos
1645
1843
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1646
- await this.cameraService.enumerateDevices();
1647
- await this.updateCameraInfoWithAutofocus();
1844
+ try {
1845
+ await this.cameraService.enumerateDevices();
1846
+ }
1847
+ catch (enumerateError) {
1848
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1849
+ }
1850
+ try {
1851
+ await this.updateCameraInfoWithAutofocus();
1852
+ }
1853
+ catch (cameraInfoError) {
1854
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1855
+ }
1648
1856
  // Paso 3: Configurar cámara seleccionada
1649
1857
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1650
- const stream = await this.cameraService.setupCamera();
1651
- // Paso 4: Inicializar video y ocultar estado inmediatamente
1652
- this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
1653
- await this.initializeVideoStream(stream);
1858
+ let stream;
1859
+ try {
1860
+ stream = await this.cameraService.setupCamera();
1861
+ }
1862
+ catch (setupError) {
1863
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1864
+ }
1865
+ // Paso 4: Inicializar video y mostrar estado de análisis inicial
1866
+ if (this.useDocumentDetector) {
1867
+ this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
1868
+ }
1869
+ else {
1870
+ this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
1871
+ }
1872
+ try {
1873
+ await this.initializeVideoStream(stream);
1874
+ }
1875
+ catch (videoError) {
1876
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1877
+ }
1654
1878
  // Paso 5: Calibrar máscara
1655
1879
  if (this.detectionContainer) {
1656
1880
  const container = this.detectionContainer.parentElement;
@@ -1660,6 +1884,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1660
1884
  // Finalizar e iniciar captura
1661
1885
  this.startTime = Date.now();
1662
1886
  this.stateManager.updateCaptureState({ isLoading: false });
1887
+ // Mostrar botón manual si el detector está deshabilitado
1888
+ if (!this.useDocumentDetector) {
1889
+ this.showManualCaptureButton = true;
1890
+ }
1663
1891
  this.detectFrame();
1664
1892
  }
1665
1893
  catch (err) {
@@ -1677,6 +1905,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1677
1905
  this.videoRef.onloadedmetadata = async () => {
1678
1906
  await this.videoRef.play();
1679
1907
  this.stateManager.updateCaptureState({ isVideoActive: true });
1908
+ // Recalculate mask dimensions when new camera stream loads
1909
+ if (this.detectionContainer) {
1910
+ const container = this.detectionContainer.parentElement;
1911
+ const rect = container.getBoundingClientRect();
1912
+ this.updateMaskDimensions(rect);
1913
+ }
1680
1914
  resolve();
1681
1915
  };
1682
1916
  });
@@ -1691,6 +1925,18 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1691
1925
  const captureState = this.stateManager.getCaptureState();
1692
1926
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
1693
1927
  return;
1928
+ // Show manual capture button when document detector is disabled or performance is degraded
1929
+ if (!this.useDocumentDetector || this.performanceDegradedMode) {
1930
+ this.showManualCaptureButton = true;
1931
+ // Continue the loop for UI updates but skip detection logic
1932
+ if (captureState.step !== 'completed') {
1933
+ this.animationId = requestAnimationFrame(() => this.detectFrame());
1934
+ }
1935
+ return;
1936
+ }
1937
+ else if (!this.performanceDegradedMode) {
1938
+ this.showManualCaptureButton = false;
1939
+ }
1694
1940
  if (captureState.isDetectionPaused) {
1695
1941
  if (captureState.step !== 'completed') {
1696
1942
  this.animationId = requestAnimationFrame(() => this.detectFrame());
@@ -1718,10 +1964,27 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1718
1964
  this.lastInferenceTime = currentTime;
1719
1965
  // Measure preprocessing and inference time
1720
1966
  const inferenceStartTime = performance.now();
1721
- const inputTensor = this.detectionService.preprocess(this.videoRef);
1722
- // Standard detection without quality validation
1723
- const detections = await this.detectionService.runInference(inputTensor);
1724
- const inferenceTime = performance.now() - inferenceStartTime;
1967
+ let inputTensor;
1968
+ let detections;
1969
+ let inferenceTime;
1970
+ try {
1971
+ inputTensor = this.detectionService.preprocess(this.videoRef);
1972
+ }
1973
+ catch (preprocessError) {
1974
+ this.switchToManualModeWithError('Error al procesar imagen');
1975
+ return;
1976
+ }
1977
+ try {
1978
+ // Standard detection without timeout (timeout is handled by frame counting)
1979
+ detections = await this.detectionService.runInference(inputTensor);
1980
+ inferenceTime = performance.now() - inferenceStartTime;
1981
+ }
1982
+ catch (inferenceError) {
1983
+ this.switchToManualModeWithError('Error en detección automática');
1984
+ return;
1985
+ }
1986
+ // Increment frame count
1987
+ this.processedFramesCount++;
1725
1988
  // Record ONNX performance metrics
1726
1989
  if (this.debug) {
1727
1990
  this.recordOnnxPerformance(0, inferenceTime);
@@ -1760,9 +2023,30 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1760
2023
  }
1761
2024
  this.updateMaskColor(detections);
1762
2025
  // Record frame processing metrics
2026
+ const frameEndTime = performance.now();
2027
+ const totalFrameTime = frameEndTime - frameStartTime;
2028
+ // Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
2029
+ if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
2030
+ // Verificar si el frame es lento
2031
+ if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
2032
+ this.slowFrameCount++;
2033
+ // Si tenemos suficientes frames lentos consecutivos, cambiar a manual
2034
+ if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
2035
+ this.switchToManualMode();
2036
+ }
2037
+ }
2038
+ else {
2039
+ // Frame rápido, reiniciar contador
2040
+ if (this.slowFrameCount > 0) {
2041
+ this.slowFrameCount = 0;
2042
+ }
2043
+ }
2044
+ // Cambiar mensaje de estado después de analizar los primeros 2 frames
2045
+ if (this.processedFramesCount === 2) {
2046
+ this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
2047
+ }
2048
+ }
1763
2049
  if (this.debug) {
1764
- const frameEndTime = performance.now();
1765
- const totalFrameTime = frameEndTime - frameStartTime;
1766
2050
  this.recordFrameProcessing(totalFrameTime, detections.length);
1767
2051
  }
1768
2052
  // Continue detection loop
@@ -1798,6 +2082,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1798
2082
  clearTimeout(this.alignmentTimer);
1799
2083
  this.alignmentTimer = undefined;
1800
2084
  }
2085
+ if (this.orientationTimer) {
2086
+ clearTimeout(this.orientationTimer);
2087
+ this.orientationTimer = undefined;
2088
+ }
2089
+ // Liberar recursos de ONNX al limpiar el componente
2090
+ this.releaseOnnxResources();
1801
2091
  if (this.performanceUpdateInterval) {
1802
2092
  clearInterval(this.performanceUpdateInterval);
1803
2093
  this.performanceUpdateInterval = undefined;
@@ -1818,7 +2108,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1818
2108
  isCapturing: false
1819
2109
  };
1820
2110
  const cameraInfo = this.cameraInfoWithAutofocus;
1821
- return (h("div", { key: '69a8df7969288e19088e8809f1fefcc89b237cda', class: "detector-container" }, h("div", { key: '79e59a315d266be9d4fb5eed5740e5ad825d5890', class: "video-container" }, h("video", { key: 'a3312788c739748b774a25acb30c2dcea7f01695', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'ddab107500c2dab24c140049bef0b44aafc35dc7', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
2111
+ return (h("div", { key: 'dda3e3054a18dac59fd6dab4bf722f405d1c1b0f', class: "detector-container" }, h("div", { key: 'c260bf52307e072a634cddcc273c1f55037d01ef', class: "video-container" }, h("video", { key: '5f20ce9d95c5ece7ab90a2da30f28034b86a57a0', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '6afd8a46c16592c67e6bb93f1655de1660338f30', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
1822
2112
  position: 'absolute',
1823
2113
  left: `${box.x}px`,
1824
2114
  top: `${box.y}px`,
@@ -1827,9 +2117,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1827
2117
  border: '2px solid #32406C',
1828
2118
  pointerEvents: 'none',
1829
2119
  boxSizing: 'border-box'
1830
- } })))), this.isMaskReady && (h("div", { key: '370337089a3de949b8cb4ae84dc0037e6044b2d5', class: "overlay-mask" }, h("div", { key: 'cb35c2e42bf1729ad02a8fe7e7abaf27cf1a8d7a', class: "card-outline" }, h("div", { key: '637d72ada63805e7f25051cd1ad9093030cb025a', class: "side side-top" }), h("div", { key: 'c7a465aa686afa41f80433d949715b40f6f349dc', class: "side side-right" }), h("div", { key: '384aade0610b5bc7bdd7db350a70d82f9bebd8ab', class: "side side-bottom" }), h("div", { key: '5506cc346f3fd718e3fda4365f69ea780304b6d6', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'f5b9b62937eabab7c096b7417a13bbf6f822ee8a', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '74b122125a42839d2600cf99fb855140042ab761', class: "skip-section" }, h("div", { key: '0b4a39d1599854d6c445e2c35dcc89a0e4970846', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), h("button", { key: 'b52b47df390cc099c8847ca8a676270bb154294d', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2120
+ } })))), this.isMaskReady && (h("div", { key: '178f2022bed334f8c9c8c866a92b44c8cb0369b1', class: "overlay-mask" }, h("div", { key: 'acb1bc996409095afd29ffa5a038c3eeae6c7ed9', class: "card-outline" }, h("div", { key: 'dcc952d9011c5fd13b229f3a759cc74721a1ee11', class: "side side-top" }), h("div", { key: 'addb25f6c41ad10a09b6918e6357052294d14adf', class: "side side-right" }), h("div", { key: 'a1ec08f3bf1b16731a224c034c76dd8c6412597b', class: "side side-bottom" }), h("div", { key: '22f3c158f2c2bb1dc582f796c39ea60bc11505dd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '5b150f7df5b0c5402e42bae4601936d4e5d1f5d4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '17b0a81437eb8691cb4dc5042c72bda01ac92be0', class: "back-capture-section" }, h("div", { key: 'a65ccbd5f53b06ec340b36a0a2348cf04920b3df', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: '383d7398079dbb8b1adfe695407cc598059b2a94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '86bddcfeb6f90da9b18926f4c6e1208296502dcf', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: '5c9d116cbb9b5bc16481b2b90c8451182ad436c5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '881f00cfc3a24167bc6b8da5849cb404101b692d', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1831
2121
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1832
- : 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: 'f04e47749a25197c434b849844c67eadca5c6af3', class: "camera-controls" }, h("button", { key: '82625eef057f59b6dac11c4443ccb315231eac7f', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '61cfb89cd9bb549cfff2cb70293db06ee6cdf06b', class: "camera-selector-dropdown" }, h("div", { key: '60f751d8db7b305f2bdfdc089d2110bcff940150', class: "camera-selector-header" }, h("span", { key: '9dae045a34cc06f323ff4850921b495fc845be47' }, "Seleccionar C\u00E1mara"), h("button", { key: '4584d7964d91ddd7f3d980c3e5e90493530d33bd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '9881f2b4fc6ffbdc76db06a7b49d060cefbe6277', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '3dfe9ffc7b4cc9afb199a78026cef6d4b469805e', class: "device-info" }, h("small", { key: 'bddc865a30469bd2724624b2f7c8f641f9d97a07' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2fd963e44f69f80d17c6bbbfa3c9b5e11bc816be', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'ba4872ea5388a0ac3134be61feff5412373465ab', class: "flip-animation" }, h("div", { key: 'e83a92cea79f3e85aefc6964c5d9de7b495d511e', class: "id-card-icon" }), h("div", { key: '1d06b51fd6ad14d9b37aa6bca72bd13c951a386a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '503c377896d5fdc3ae8476b09f511d571f23ae72', class: "success-animation" }, h("div", { key: 'e1929c245b0d1479a6fb1513b0b37c53070c4291', class: "check-icon" }), h("div", { key: '024c897c70427b205390770fc3dfb999ffcc84d3', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '54e350707628e5c88ae2900e412e6bda2eace943', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '82ea10569ed46285686cf3e63885ca1941bb559c', class: "status-spinner" })), h("div", { key: '1fecf179c10965cccf1be3dc0a54afd377ba38ad', class: "status-content" }, h("div", { key: '1b301c1bbb02e5cac2e6babcb20c190662d45c68', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '5b6c61308d17176daade02c951a436d114a74100', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'b8104db64208dc87a617a072957c007f28055194', class: "performance-monitor" }, h("div", { key: 'e66aec071f10821be2713858dcc251c04f480e63', class: "performance-expanded" }, h("div", { key: '1ae09a91d0704ab21b9eb51097cf452fd5f44359', class: "metrics-row" }, h("div", { key: '340d4d16d048a5217fe13d5de63618c9ea221165', class: "metric-compact" }, h("span", { key: '20cfda14969ef70d232edd68f2e9902bd44b5d9a', class: "metric-label" }, "FPS"), h("span", { key: '3a089c2814a71ed6513bf05b0120a66aa159daee', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '23f700def988c3220fc5df37aeb57a0a20ff56b3', class: "metric-compact" }, h("span", { key: '441e8490a871469813a6d832114e03583d375e91', class: "metric-label" }, "MEM"), h("span", { key: '51f7c49164c9d5e699141c92c88375fc99b312ed', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '6547fee80e2a1db7c9ff889a1c478fc8248159e6', class: "metrics-row" }, h("div", { key: '7b2d1642fd122ee0fbed36387afb23152afe10c2', class: "metric-compact" }, h("span", { key: 'a9137119bf7da366f18feaf8f28807ee40ad7a25', class: "metric-label" }, "INF"), h("span", { key: '5cbc0c040102688f1039cdcd4bb58020248cf700', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'cb4a86f5da46e4081d997c8b8bb6a899f06dfcd2', class: "metric-compact" }, h("span", { key: '88ad5e1d842eb156cf6813ecaa9d2bec76a5999e', class: "metric-label" }, "FRAME"), h("span", { key: 'a9b14cd98b64dcfc058d3b68a3c168a9e092fb3c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '86e01f3596682cb8ad2ff5464748e6ecb7db374a', class: "metrics-row" }, h("div", { key: 'e73b9d39e9bcbc5d46fa0ba0a737700e3e5566d9', class: "metric-compact" }, h("span", { key: '8ecfe969dfe77c87d9da76d7390706014b4872f3', class: "metric-label" }, "DET"), h("span", { key: 'bd1a6a52e0bd1fb4d377413d7515e4e0f00da10f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '36539ae98787fd60642082db1bf89031003c4bef', class: "metric-compact" }, h("span", { key: '7f3a81629c09053e3beeca97f485da0f60412052', class: "metric-label" }, "RATE"), h("span", { key: '72399e0ac39270c82b265ce64e402f84cda9fb2c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4e3a914ba2f5993f85de7b9dd6dc93d76b41c96d', class: "watermark" }, h("img", { key: 'aa634c2ba2090f64ee9b41aa1d75a9114d46fd63', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2122
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'd929fc0eeeeee944f3d722fde98c52642443e9a5', class: "camera-controls" }, h("button", { key: '1afcfae9d4a23aaede153f27ba6895a33182ada1', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: 'f37feefd9def935417467dffb52b9fe8d8acea79', class: "camera-selector-dropdown" }, h("div", { key: 'a8479ec6a6c4076b5dc40677e1988295100e4d0d', class: "camera-selector-header" }, h("span", { key: '074f02967354293a1e5df4e3ac8a126faee33d04' }, "Seleccionar C\u00E1mara"), h("button", { key: 'd6ba0252fb269c3a04967fb0e5c075166f202d71', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '63f5f9be2b05da71f4fe5a4e38b11ab3e39fb1e0', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '4ca46fdebe0eaa078f1dae4d2f1af03321577c7f', class: "device-info" }, h("small", { key: '10c54ddc5a196b11a69077b045119187ed09f4be' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '25393018948798f4d9d5f44738e61c7a0a6be990', class: "manual-capture-section" }, h("button", { key: '7a6c8686c0db3e352f9689bc41f7fd3f612c800c', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '1916b97da3f8dce7d06b86b21f0361dd7ce10b95', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: 'aed05a3280dc96121564ffea87945f441d4286f6', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '63d262e5e10325d145fada8e91a878c47305f2b9', class: "flip-animation" }, h("div", { key: '2a3699f47e595c5e4ab00dfd11e1e66ab2e13039', class: "id-card-icon" }), h("div", { key: 'df195fe9a6db4ec3e14b04b4e932d55adb7c3f3c', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '670341c302a31c8017a9083ebf35e2988122ac3f', class: "success-animation" }, h("div", { key: '27b0b0d91de6a882182b6ccdebecc7c440997e36', class: "check-icon" }), h("div", { key: '71ee5327faf48ea26c2ae36a8a6906c448c3a389', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '39c92aa0b82f6169058b1092894845fcba3f27b4', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '74b02ba81756f0e8bfb6e2feafdb2c44fb80afad', class: "status-spinner" })), h("div", { key: 'c7419efb2c6e2760019ecce619534e24ae3d7351', class: "status-content" }, h("div", { key: '57f315384630f17f6332eddfea628b333299e831', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'ac8632fdd68c24ed00192cb04a13026ada1f953d', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'e51286a839795b18cfac2874e7e1f6e9f8e3bb4b', class: "performance-monitor" }, h("div", { key: 'f8cd289d260af9df2daf23bdfb8db70fe8bf6f45', class: "performance-expanded" }, h("div", { key: 'bf2becb2ee3ad3bffb82b0e90246a4385a19f567', class: "metrics-row" }, h("div", { key: 'f334c2b5de45df73aec6e06cd371151110789a45', class: "metric-compact" }, h("span", { key: 'cbf32f72eb3aaf1bf770d651c32123a43b81035c', class: "metric-label" }, "FPS"), h("span", { key: '9d2d2f46ba3fdb23c010f781044f00b1dd1250e6', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'f29406e8a5b00f5838a144de9fda77ce53177d43', class: "metric-compact" }, h("span", { key: '8f44e29f6134c340f0a58f8aa321612c8a9bbfb8', class: "metric-label" }, "MEM"), h("span", { key: '7a58e164d292329d95054b2679a90e031f6fe4c2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'd0284a0e65fc2879557d73fc2e03295c29f512ae', class: "metrics-row" }, h("div", { key: '78dcbce552e65c95fcd01158f3aa8916ef0e7175', class: "metric-compact" }, h("span", { key: '673cfbc8c9dabb020a3d3c18ee07558a61da9b51', class: "metric-label" }, "INF"), h("span", { key: '833761b7a875c931a8b31913930c3b132f71f618', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '324801e3e69a8de181755367201d8e8c643e8d72', class: "metric-compact" }, h("span", { key: 'f928d9b2dfbf81f5738ab08a42ebb76ff4a483de', class: "metric-label" }, "FRAME"), h("span", { key: 'dc9f2e4a1e848be4d240d89c89126203555ae203', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '7ca0a7b44719b1884334edab586267f021cf86c1', class: "metrics-row" }, h("div", { key: '262667c4320d337dae2220b77f142cf293542562', class: "metric-compact" }, h("span", { key: '578b53d251e58933f654a5afe558931e4333f022', class: "metric-label" }, "DET"), h("span", { key: '4fb36eaebebd73e5cb06d582fa3b4ece61463761', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'c17192e3964164ad603427de3befa1615b027022', class: "metric-compact" }, h("span", { key: 'd004e74797b9468e94c2af996bb62c1a3ca8d58e', class: "metric-label" }, "RATE"), h("span", { key: '0960e8760fd0ac2456a9e76598e5392c24e42d3b', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3588fba1fe51af1335d643107ba0355aca6f4f8c', class: "watermark" }, h("img", { key: 'd53ec6b0ee9f97f602f4f8857ec605427b2ee478', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1833
2123
  }
1834
2124
  // Utility methods
1835
2125
  updateDetectionBoxes(boxes) {
@@ -1944,6 +2234,141 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1944
2234
  }
1945
2235
  }
1946
2236
  }
2237
+ async takeManualScreenshot() {
2238
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
2239
+ // Set processing state immediately
2240
+ const captureState = this.stateManager?.getCaptureState();
2241
+ if (captureState?.step === 'front') {
2242
+ this.processingButton = 'capture-front';
2243
+ }
2244
+ else if (captureState?.step === 'back') {
2245
+ this.processingButton = 'capture-back';
2246
+ }
2247
+ console.log('🔵 processingButton set to:', this.processingButton);
2248
+ // Add small delay to show spinner
2249
+ await new Promise(resolve => setTimeout(resolve, 100));
2250
+ if (!this.videoRef) {
2251
+ this.processingButton = null;
2252
+ return;
2253
+ }
2254
+ // When using manual capture, use mask coordinates for cropping
2255
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
2256
+ await this.takeScreenshotWithMaskCoordinates();
2257
+ }
2258
+ async takeScreenshotWithMaskCoordinates() {
2259
+ if (!this.videoRef || !this.detectionContainer)
2260
+ return;
2261
+ this.stateManager.updateCaptureState({ isCapturing: true });
2262
+ this.triggerCaptureAnimation();
2263
+ // Use pooled canvas for optimization
2264
+ const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
2265
+ const captureCtx = captureCanvas.getContext('2d', { alpha: false });
2266
+ captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
2267
+ captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
2268
+ // Calculate mask coordinates for cropping
2269
+ const container = this.detectionContainer.parentElement;
2270
+ const containerRect = container.getBoundingClientRect();
2271
+ // Get mask dimensions from CSS properties
2272
+ const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
2273
+ const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
2274
+ const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
2275
+ const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
2276
+ // Convert percentages to actual video coordinates
2277
+ const videoWidth = this.videoRef.videoWidth;
2278
+ const videoHeight = this.videoRef.videoHeight;
2279
+ const videoAspectRatio = videoWidth / videoHeight;
2280
+ const containerAspectRatio = containerRect.width / containerRect.height;
2281
+ let displayedVideoWidth, displayedVideoHeight;
2282
+ let videoOffsetX = 0, videoOffsetY = 0;
2283
+ if (videoAspectRatio > containerAspectRatio) {
2284
+ displayedVideoWidth = containerRect.width;
2285
+ displayedVideoHeight = containerRect.width / videoAspectRatio;
2286
+ videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
2287
+ }
2288
+ else {
2289
+ displayedVideoHeight = containerRect.height;
2290
+ displayedVideoWidth = containerRect.height * videoAspectRatio;
2291
+ videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
2292
+ }
2293
+ // Calculate mask coordinates in video space
2294
+ const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
2295
+ const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
2296
+ const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
2297
+ const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
2298
+ // Convert to video coordinates
2299
+ const scaleX = videoWidth / displayedVideoWidth;
2300
+ const scaleY = videoHeight / displayedVideoHeight;
2301
+ const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
2302
+ const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
2303
+ const maskWidthInVideo = maskWidthInContainer * scaleX;
2304
+ const maskHeightInVideo = maskHeightInContainer * scaleY;
2305
+ // Calculate crop coordinates
2306
+ const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
2307
+ const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
2308
+ const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
2309
+ const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
2310
+ // Use pooled canvas for cropped version
2311
+ const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
2312
+ const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
2313
+ croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
2314
+ croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
2315
+ const captureState = this.stateManager.getCaptureState();
2316
+ if (captureState.step === 'front') {
2317
+ this.stateManager.setCapturedImages({
2318
+ front: {
2319
+ fullFrame: captureCanvas.toDataURL('image/png'),
2320
+ cropped: croppedCanvas.toDataURL('image/png')
2321
+ }
2322
+ });
2323
+ // Check if document classification is enabled (independent of detector)
2324
+ if (this.useDocumentClassification) {
2325
+ try {
2326
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
2327
+ if (classification && classification.class === 'passport') {
2328
+ this.completeProcess(true);
2329
+ this.returnCanvasToPool(captureCanvas);
2330
+ this.returnCanvasToPool(croppedCanvas);
2331
+ // Clear processing button when passport is detected
2332
+ this.processingButton = null;
2333
+ return;
2334
+ }
2335
+ }
2336
+ catch (classifyError) {
2337
+ // No cambiar a manual aquí, solo registrar el error y continuar
2338
+ }
2339
+ }
2340
+ this.stateManager.updateCaptureState({
2341
+ step: 'back',
2342
+ isDetectionPaused: true,
2343
+ showFlipAnimation: true
2344
+ });
2345
+ this.triggerRerender();
2346
+ setTimeout(() => {
2347
+ this.stateManager.updateCaptureState({
2348
+ showFlipAnimation: false,
2349
+ isDetectionPaused: false
2350
+ });
2351
+ this.triggerRerender();
2352
+ this.startBackDocumentTimer();
2353
+ // Clear processing button after animation completes
2354
+ this.processingButton = null;
2355
+ }, 3000);
2356
+ }
2357
+ else if (captureState.step === 'back') {
2358
+ this.stateManager.setCapturedImages({
2359
+ back: {
2360
+ fullFrame: captureCanvas.toDataURL('image/png'),
2361
+ cropped: croppedCanvas.toDataURL('image/png')
2362
+ }
2363
+ });
2364
+ this.completeProcess(false);
2365
+ // Clear processing button after back capture completes
2366
+ this.processingButton = null;
2367
+ }
2368
+ // Return canvases to pool after use
2369
+ this.returnCanvasToPool(captureCanvas);
2370
+ this.returnCanvasToPool(croppedCanvas);
2371
+ }
1947
2372
  async takeScreenshot() {
1948
2373
  if (!this.videoRef || !this.lastDetectedBox)
1949
2374
  return;
@@ -1977,10 +2402,15 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1977
2402
  });
1978
2403
  // Check if document classification is enabled
1979
2404
  if (this.useDocumentClassification) {
1980
- const classification = await this.detectionService.classifyDocument(croppedCanvas);
1981
- if (classification && classification.class === 'passport') {
1982
- this.completeProcess(true);
1983
- return;
2405
+ try {
2406
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
2407
+ if (classification && classification.class === 'passport') {
2408
+ this.completeProcess(true);
2409
+ return;
2410
+ }
2411
+ }
2412
+ catch (classifyError) {
2413
+ // No cambiar a manual aquí, solo registrar el error y continuar
1984
2414
  }
1985
2415
  }
1986
2416
  this.stateManager.updateCaptureState({
@@ -2017,6 +2447,135 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2017
2447
  cardOutline?.classList.remove('capturing');
2018
2448
  }, 600);
2019
2449
  }
2450
+ // Método simplificado para cambio a modo manual
2451
+ switchToManualMode() {
2452
+ // Only switch if detector is enabled and not already switched
2453
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2454
+ return;
2455
+ }
2456
+ // Detener todo el monitoreo de performance
2457
+ this.stopPerformanceMonitoring();
2458
+ // Liberar recursos de ONNX
2459
+ this.releaseOnnxResources();
2460
+ // Resetear máscara a blanco y limpiar detecciones
2461
+ this.resetMaskToWhite();
2462
+ this.detectionBoxes = [];
2463
+ this.hasAutoSwitchedToManual = true;
2464
+ this.performanceDegradedMode = true;
2465
+ this.showManualCaptureButton = true;
2466
+ this.showPerformanceMessage = true;
2467
+ // Hide performance message after 5 seconds
2468
+ setTimeout(() => {
2469
+ this.showPerformanceMessage = false;
2470
+ }, 5000);
2471
+ }
2472
+ // Método para cambiar a modo manual por errores de ONNX
2473
+ switchToManualModeWithWarning(warningMessage) {
2474
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2475
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2476
+ return;
2477
+ }
2478
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
2479
+ // Resetear máscara a blanco y limpiar detecciones
2480
+ this.resetMaskToWhite();
2481
+ this.detectionBoxes = [];
2482
+ // Cambiar a modo manual
2483
+ this.hasAutoSwitchedToManual = true;
2484
+ this.performanceDegradedMode = true;
2485
+ this.showManualCaptureButton = true;
2486
+ this.useDocumentDetector = false; // Desactivar detector automático
2487
+ // Actualizar el estado con mensaje informativo (no error)
2488
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
2489
+ // Continuar con la configuración de cámara
2490
+ this.continueWithCameraSetup();
2491
+ }
2492
+ async continueWithCameraSetup() {
2493
+ try {
2494
+ // Paso 2: Detectar y configurar dispositivos
2495
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
2496
+ try {
2497
+ await this.cameraService.enumerateDevices();
2498
+ }
2499
+ catch (enumerateError) {
2500
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
2501
+ }
2502
+ try {
2503
+ await this.updateCameraInfoWithAutofocus();
2504
+ }
2505
+ catch (cameraInfoError) {
2506
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
2507
+ }
2508
+ // Paso 3: Configurar cámara seleccionada
2509
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
2510
+ let stream;
2511
+ try {
2512
+ stream = await this.cameraService.setupCamera();
2513
+ }
2514
+ catch (setupError) {
2515
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
2516
+ }
2517
+ // Paso 4: Inicializar video
2518
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
2519
+ try {
2520
+ await this.initializeVideoStream(stream);
2521
+ }
2522
+ catch (videoError) {
2523
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
2524
+ }
2525
+ // Paso 5: Calibrar máscara
2526
+ if (this.detectionContainer) {
2527
+ const container = this.detectionContainer.parentElement;
2528
+ const rect = container.getBoundingClientRect();
2529
+ this.updateMaskDimensions(rect);
2530
+ }
2531
+ // Finalizar
2532
+ this.startTime = Date.now();
2533
+ this.stateManager.updateCaptureState({ isLoading: false });
2534
+ // Mostrar botón manual (ya debe estar habilitado)
2535
+ this.showManualCaptureButton = true;
2536
+ }
2537
+ catch (error) {
2538
+ this.updateStatus('Error de cámara', error.message, 'error');
2539
+ throw error;
2540
+ }
2541
+ }
2542
+ switchToManualModeWithError(errorMessage) {
2543
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2544
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2545
+ return;
2546
+ }
2547
+ // Liberar recursos de ONNX inmediatamente
2548
+ this.releaseOnnxResources();
2549
+ // Resetear máscara a blanco y limpiar detecciones
2550
+ this.resetMaskToWhite();
2551
+ this.detectionBoxes = [];
2552
+ // Cambiar a modo manual
2553
+ this.hasAutoSwitchedToManual = true;
2554
+ this.performanceDegradedMode = true;
2555
+ this.showManualCaptureButton = true;
2556
+ this.useDocumentDetector = false; // Desactivar detector automático
2557
+ // Actualizar el estado con mensaje de error
2558
+ this.updateStatus('Modo manual activado', errorMessage, 'error');
2559
+ // Ocultar mensaje después de 5 segundos
2560
+ setTimeout(() => {
2561
+ this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
2562
+ }, 5000);
2563
+ }
2564
+ stopPerformanceMonitoring() {
2565
+ // Nota: No necesitamos limpiar más variables porque ya no se usarán
2566
+ // El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
2567
+ }
2568
+ // Método para liberar recursos de ONNX cuando se cambia a modo manual
2569
+ releaseOnnxResources() {
2570
+ try {
2571
+ // Liberar recursos del servicio de detección
2572
+ if (this.detectionService) {
2573
+ this.detectionService.cleanup();
2574
+ }
2575
+ }
2576
+ catch (error) {
2577
+ }
2578
+ }
2020
2579
  completeProcess(skippedBack = false) {
2021
2580
  this.stateManager.updateCaptureState({
2022
2581
  step: 'completed',
@@ -2130,6 +2689,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2130
2689
  this.detectionBoxes = [];
2131
2690
  this.alignmentStartTime = undefined;
2132
2691
  this.hasDocumentDetected = false;
2692
+ // Reset sistema simplificado
2693
+ this.processedFramesCount = 0;
2694
+ this.slowFrameCount = 0;
2695
+ // Reset performance degradation state
2696
+ this.hasAutoSwitchedToManual = false;
2697
+ this.performanceDegradedMode = false;
2698
+ this.showPerformanceMessage = false;
2133
2699
  if (this.alignmentTimer) {
2134
2700
  clearTimeout(this.alignmentTimer);
2135
2701
  this.alignmentTimer = undefined;
@@ -2233,6 +2799,24 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2233
2799
  // Performance is good, use base frame skip
2234
2800
  return this.BASE_FRAME_SKIP;
2235
2801
  }
2802
+ // Método para resetear la máscara a color blanco
2803
+ resetMaskToWhite() {
2804
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
2805
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
2806
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
2807
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
2808
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
2809
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
2810
+ // Remover todas las clases de alineación y estado perfecto
2811
+ cardOutline?.classList.remove('perfect-match');
2812
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
2813
+ topSide?.classList.remove('aligned');
2814
+ rightSide?.classList.remove('aligned');
2815
+ bottomSide?.classList.remove('aligned');
2816
+ leftSide?.classList.remove('aligned');
2817
+ // Resetear estado de alineación
2818
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
2819
+ }
2236
2820
  // Canvas pool management for screenshots
2237
2821
  getPooledCanvas(width, height) {
2238
2822
  // Try to find a canvas with matching dimensions
@@ -2264,12 +2848,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2264
2848
  // If pool is full, let the canvas be garbage collected
2265
2849
  }
2266
2850
  static get style() { return myComponentCss; }
2267
- }, [1, "jaak-stamps", {
2851
+ }, [257, "jaak-stamps", {
2268
2852
  "debug": [4],
2269
2853
  "alignmentTolerance": [2, "alignment-tolerance"],
2270
2854
  "maskSize": [2, "mask-size"],
2271
2855
  "cropMargin": [2, "crop-margin"],
2272
2856
  "useDocumentClassification": [4, "use-document-classification"],
2857
+ "useDocumentDetector": [4, "use-document-detector"],
2273
2858
  "preferredCamera": [1, "preferred-camera"],
2274
2859
  "captureDelay": [2, "capture-delay"],
2275
2860
  "enableBackDocumentTimer": [4, "enable-back-document-timer"],
@@ -2285,6 +2870,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2285
2870
  "currentStatus": [32],
2286
2871
  "performanceData": [32],
2287
2872
  "backDocumentTimerRemaining": [32],
2873
+ "showManualCaptureButton": [32],
2874
+ "performanceDegradedMode": [32],
2875
+ "showPerformanceMessage": [32],
2876
+ "captureStateVersion": [32],
2877
+ "processingButton": [32],
2288
2878
  "getCapturedImages": [64],
2289
2879
  "isProcessCompleted": [64],
2290
2880
  "startCapture": [64],