@jaak.ai/stamps 2.2.0-dev.9 → 2.3.0-dev.1

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 (30) hide show
  1. package/README.md +188 -8
  2. package/dist/cjs/jaak-stamps.cjs.entry.js +124 -25
  3. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  4. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  5. package/dist/collection/components/my-component/my-component.css +23 -2
  6. package/dist/collection/components/my-component/my-component.js +44 -16
  7. package/dist/collection/components/my-component/my-component.js.map +1 -1
  8. package/dist/collection/services/CameraService.js +41 -9
  9. package/dist/collection/services/CameraService.js.map +1 -1
  10. package/dist/collection/services/LicenseValidationService.js +1 -1
  11. package/dist/collection/services/LicenseValidationService.js.map +1 -1
  12. package/dist/collection/services/factories/DeviceStrategyFactory.js +7 -0
  13. package/dist/collection/services/factories/DeviceStrategyFactory.js.map +1 -1
  14. package/dist/collection/services/strategies/IOSSafariStrategy.js +33 -0
  15. package/dist/collection/services/strategies/IOSSafariStrategy.js.map +1 -0
  16. package/dist/components/jaak-stamps.js +124 -25
  17. package/dist/components/jaak-stamps.js.map +1 -1
  18. package/dist/esm/jaak-stamps.entry.js +124 -25
  19. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  20. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  21. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  22. package/dist/jaak-stamps-webcomponent/p-04f57d50.entry.js +7 -0
  23. package/dist/jaak-stamps-webcomponent/p-04f57d50.entry.js.map +1 -0
  24. package/dist/types/components/my-component/my-component.d.ts +2 -1
  25. package/dist/types/components.d.ts +2 -2
  26. package/dist/types/services/LicenseValidationService.d.ts +1 -1
  27. package/dist/types/services/strategies/IOSSafariStrategy.d.ts +12 -0
  28. package/package.json +1 -1
  29. package/dist/jaak-stamps-webcomponent/p-dfecb452.entry.js +0 -7
  30. package/dist/jaak-stamps-webcomponent/p-dfecb452.entry.js.map +0 -1
package/README.md CHANGED
@@ -56,23 +56,49 @@ yarn add @jaak.ai/stamps
56
56
 
57
57
  ### Properties/Attributes
58
58
 
59
+ #### License Properties (Required)
60
+
59
61
  | Property | Type | Default | Description |
60
62
  |----------|------|---------|-------------|
61
- | `license` | `string` | `undefined` | License key for component authentication (required) |
62
- | `environment` | `'dev' \| 'qa' \| 'staging' \| 'prod'` | `'prod'` | API environment for license validation |
63
- | `trace-id` | `string` | `undefined` | Optional trace ID for request tracing (auto-generated if not provided) |
63
+ | `license` | `string` | `undefined` | **[REQUIRED]** License key for component authentication |
64
+ | `license-environment` | `'dev' \| 'qa' \| 'sandbox' \| 'prod'` | `'prod'` | API environment for license validation |
64
65
  | `app-id` | `string` | `'jaak-stamps-web'` | Application identifier for analytics and tracking |
66
+ | `trace-id` | `string` | `undefined` | Optional trace ID for distributed tracing (auto-generated if not provided) |
67
+
68
+ #### Base Configuration Properties
69
+
70
+ | Property | Type | Default | Description |
71
+ |----------|------|---------|-------------|
65
72
  | `debug` | `boolean` | `false` | Enable debug mode with additional logging and overlays |
66
- | `mask-size` | `number` | `90` | Size percentage of the document detection mask |
67
- | `alignment-tolerance` | `number` | `15` | Tolerance level for document alignment detection |
68
- | `capture-delay` | `number` | `1500` | Delay in milliseconds before automatic capture |
69
- | `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping |
73
+ | `mask-size` | `number` | `90` | Size percentage of the document detection mask (50-100) |
74
+ | `alignment-tolerance` | `number` | `15` | Tolerance level in pixels for document alignment detection |
75
+ | `capture-delay` | `number` | `1500` | Delay in milliseconds before automatic capture (0-10000) |
76
+ | `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping (0-100) |
70
77
  | `preferred-camera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Preferred camera selection |
71
- | `use-document-classification` | `boolean` | `false` | Enable AI document classification |
78
+ | `use-document-classification` | `boolean` | `false` | Enable AI document type classification |
72
79
  | `use-document-detector` | `boolean` | `true` | Enable/disable AI document detection model |
73
80
  | `enable-back-document-timer` | `boolean` | `false` | Enable timer for back document capture |
74
81
  | `back-document-timer-duration` | `number` | `20` | Duration in seconds for back document capture timer |
75
82
 
83
+ #### Telemetry and OpenTelemetry Properties
84
+
85
+ | Property | Type | Default | Description |
86
+ |----------|------|---------|-------------|
87
+ | `telemetry-collector-url` | `string` | `'https://collector.jaak.ai/v1/traces'` | OTLP collector URL for distributed traces |
88
+ | `metrics-collector-url` | `string` | `'https://collector.jaak.ai/v1/metrics'` | OTLP collector URL for metrics |
89
+ | `enable-telemetry` | `boolean` | `true` | Enable distributed tracing with OpenTelemetry |
90
+ | `enable-metrics` | `boolean` | `true` | Enable metrics export to OpenTelemetry |
91
+ | `metrics-export-interval-millis` | `number` | `60000` | Metrics export interval in milliseconds |
92
+ | `propagate-trace-header-cors-urls` | `string` | `undefined` | Comma-separated URLs for W3C Trace Context header propagation |
93
+
94
+ #### Context Properties
95
+
96
+ | Property | Type | Default | Description |
97
+ |----------|------|---------|-------------|
98
+ | `customer-id` | `string` | `undefined` | Customer ID for telemetry and analytics |
99
+ | `tenant-id` | `string` | `undefined` | Tenant ID for multi-tenancy support |
100
+ | `environment` | `string` | `'production'` | Execution environment (development/staging/production) |
101
+
76
102
  ### Methods
77
103
 
78
104
  #### Camera Control
@@ -581,6 +607,160 @@ import { DocumentScannerComponent } from './document-scanner.component';
581
607
  export class DocumentScannerModule { }
582
608
  ```
583
609
 
610
+ ## Telemetry and Observability (OpenTelemetry)
611
+
612
+ Jaak Stamps includes full **OpenTelemetry** integration for distributed tracing, metrics, and performance monitoring in production environments.
613
+
614
+ ### Distributed Tracing
615
+
616
+ The component implements distributed tracing following the **W3C Trace Context** standard, providing:
617
+
618
+ - Complete flow tracking of document capture processes
619
+ - Request correlation between frontend and backend
620
+ - Performance bottleneck identification
621
+ - Context propagation via `traceparent` headers
622
+
623
+ **Automatically Recorded Spans:**
624
+ - `component.initialize` - Component initialization
625
+ - `capture.start` - Capture process start
626
+ - `model.preload` - AI model preloading
627
+ - `model.detection.load` - Detection model loading
628
+ - `model.classification.load` - Classification model loading
629
+
630
+ ### Performance Metrics
631
+
632
+ The component exports detailed metrics to OpenTelemetry:
633
+
634
+ **Counters:**
635
+ - `capture.counter` - Number of completed captures
636
+ - `error.counter` - Error count by type
637
+ - `model.load.counter` - Model load count
638
+ - `user.interaction.counter` - User interactions
639
+
640
+ **Histograms (Latencies):**
641
+ - `capture.latency` - Total capture time
642
+ - `model.load.latency` - Model loading time
643
+ - `detection.latency` - Per-frame detection time
644
+ - `image.size` - Captured image sizes
645
+
646
+ **Gauges (Current State):**
647
+ - `active.sessions` - Active sessions
648
+ - `memory.usage` - Memory usage
649
+
650
+ ### Telemetry Configuration
651
+
652
+ ```html
653
+ <jaak-stamps
654
+ license="your-license-key"
655
+ enable-telemetry="true"
656
+ enable-metrics="true"
657
+ telemetry-collector-url="https://collector.jaak.ai/v1/traces"
658
+ metrics-collector-url="https://collector.jaak.ai/v1/metrics"
659
+ metrics-export-interval-millis="60000"
660
+ propagate-trace-header-cors-urls="https://api.example.com,https://backend.example.com"
661
+ customer-id="customer123"
662
+ tenant-id="tenant456"
663
+ environment="production"
664
+ trace-id="optional-custom-trace-id">
665
+ </jaak-stamps>
666
+ ```
667
+
668
+ ### Using Trace ID
669
+
670
+ The component automatically generates a unique trace ID for each session, or accepts a custom one:
671
+
672
+ ```javascript
673
+ const scanner = document.getElementById('documentScanner');
674
+
675
+ // Listen for trace ID generation
676
+ scanner.addEventListener('traceIdGenerated', (event) => {
677
+ const traceId = event.detail.traceId;
678
+ console.log('Trace ID for this session:', traceId);
679
+
680
+ // Use this traceId to correlate requests in your backend
681
+ fetch('/api/process-document', {
682
+ method: 'POST',
683
+ headers: {
684
+ 'X-Trace-Id': traceId,
685
+ 'Content-Type': 'application/json'
686
+ },
687
+ body: JSON.stringify({ /* data */ })
688
+ });
689
+ });
690
+ ```
691
+
692
+ ### Enriched Attributes
693
+
694
+ Traces and metrics automatically include:
695
+
696
+ - **User Agent:** Browser, version, operating system
697
+ - **Geolocation:** Country, region, city (when available)
698
+ - **Device Memory:** Device memory capacity
699
+ - **Customer Context:** `customerId`, `tenantId`, `environment`
700
+ - **Service Info:** Service name, component version
701
+
702
+ ### Context Propagation (CORS)
703
+
704
+ To propagate traces to your backend services:
705
+
706
+ ```html
707
+ <jaak-stamps
708
+ propagate-trace-header-cors-urls="https://api.example.com,https://services.example.com">
709
+ </jaak-stamps>
710
+ ```
711
+
712
+ This automatically configures `fetch` and `XMLHttpRequest` interceptors to include the `traceparent` header in requests to those URLs.
713
+
714
+ ### Visualization with Jaeger/Zipkin
715
+
716
+ Exported traces are compatible with:
717
+ - **Jaeger** - Distributed tracing system
718
+ - **Zipkin** - Alternative tracing system
719
+ - **Grafana Tempo** - Grafana trace backend
720
+ - Any OTLP-compatible backend (OpenTelemetry Protocol)
721
+
722
+ ## License Validation
723
+
724
+ The component **requires a valid license** to function. Validation occurs automatically when the component loads.
725
+
726
+ ### License Configuration
727
+
728
+ ```html
729
+ <jaak-stamps
730
+ license="your-license-key-here"
731
+ license-environment="prod"
732
+ app-id="my-application">
733
+ </jaak-stamps>
734
+ ```
735
+
736
+ ### Available Environments
737
+
738
+ | Environment | Validation URL | Use Case |
739
+ |-------------|---------------|----------|
740
+ | `dev` | `https://api.dev.jaak.ai` | Local development |
741
+ | `qa` | `https://api.qa.jaak.ai` | Quality assurance testing |
742
+ | `sandbox` | `https://api.sandbox.jaak.ai` | Integration testing |
743
+ | `prod` | `https://services.api.jaak.ai` | Production |
744
+
745
+ ### License Error Handling
746
+
747
+ If the license is invalid, the component will display an error and not function:
748
+
749
+ ```javascript
750
+ const scanner = document.getElementById('documentScanner');
751
+
752
+ scanner.addEventListener('isReady', (event) => {
753
+ if (!event.detail) {
754
+ console.error('Error: Invalid license or component not initialized');
755
+ }
756
+ });
757
+ ```
758
+
759
+ **Common error messages:**
760
+ - `"License key is required"` - No license provided
761
+ - `"Invalid license key"` - Invalid or expired license
762
+ - `"License validation failed"` - Validation error
763
+
584
764
  ## Style Guide
585
765
 
586
766
  ### CSS Custom Properties
@@ -532,17 +532,30 @@ class CameraService {
532
532
  }
533
533
  const constraints = { ...videoConstraints };
534
534
  if (capabilities.width && capabilities.height) {
535
- const maxWidth = Math.min(capabilities.width.max, 1920);
536
- const maxHeight = Math.min(capabilities.height.max, 1080);
537
535
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
538
- if (isTablet) {
539
- constraints.width = { ideal: Math.min(maxWidth, 1280) };
540
- constraints.height = { ideal: Math.min(maxHeight, 720) };
536
+ const isMobileDevice = this.deviceType === 'mobile' ||
537
+ /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
538
+ // Optimize resolution based on device type to prevent "slow motion" effect
539
+ // on devices with high-resolution cameras
540
+ let targetWidth;
541
+ let targetHeight;
542
+ if (isMobileDevice && !isTablet) {
543
+ // Mobile phones: Use 720p max to ensure smooth detection performance
544
+ targetWidth = Math.min(capabilities.width.max, 1280);
545
+ targetHeight = Math.min(capabilities.height.max, 720);
546
+ }
547
+ else if (isTablet) {
548
+ // Tablets: Can handle slightly higher resolution
549
+ targetWidth = Math.min(capabilities.width.max, 1280);
550
+ targetHeight = Math.min(capabilities.height.max, 720);
541
551
  }
542
552
  else {
543
- constraints.width = { ideal: maxWidth };
544
- constraints.height = { ideal: maxHeight };
553
+ // Desktop: Can handle full HD
554
+ targetWidth = Math.min(capabilities.width.max, 1920);
555
+ targetHeight = Math.min(capabilities.height.max, 1080);
545
556
  }
557
+ constraints.width = { ideal: targetWidth };
558
+ constraints.height = { ideal: targetHeight };
546
559
  }
547
560
  // Configure autofocus if camera has the capability
548
561
  const capabilitiesAny = capabilities;
@@ -562,9 +575,28 @@ class CameraService {
562
575
  await this.detectDeviceType();
563
576
  }
564
577
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
578
+ const isMobileDevice = this.deviceType === 'mobile' ||
579
+ /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
580
+ // Optimized fallback constraints for device type
581
+ let fallbackWidth;
582
+ let fallbackHeight;
583
+ if (isMobileDevice && !isTablet) {
584
+ // Mobile phones: Use 720p for smooth performance
585
+ fallbackWidth = 1280;
586
+ fallbackHeight = 720;
587
+ }
588
+ else if (isTablet) {
589
+ fallbackWidth = 1280;
590
+ fallbackHeight = 720;
591
+ }
592
+ else {
593
+ // Desktop
594
+ fallbackWidth = 1920;
595
+ fallbackHeight = 1080;
596
+ }
565
597
  const fallbackConstraints = {
566
- width: { ideal: isTablet ? 1280 : 1920 },
567
- height: { ideal: isTablet ? 720 : 1080 }
598
+ width: { ideal: fallbackWidth },
599
+ height: { ideal: fallbackHeight }
568
600
  };
569
601
  if (this.selectedCameraId) {
570
602
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
@@ -697,9 +729,48 @@ class HighPerformanceDeviceStrategy {
697
729
  }
698
730
  }
699
731
 
732
+ /**
733
+ * Strategy for iOS Safari to prevent jetsam memory kills.
734
+ * WebKit 26+ has a known bug with ONNX Runtime JSEP mode that causes memory exhaustion.
735
+ * This strategy forces pure WASM execution without GPU acceleration.
736
+ * See: https://github.com/microsoft/onnxruntime/issues/26827
737
+ */
738
+ class IOSSafariStrategy {
739
+ getDeviceInfo() {
740
+ return {
741
+ estimatedRAM: 3, // Assume low memory for iOS
742
+ isLowMemory: true,
743
+ isSlowConnection: false
744
+ };
745
+ }
746
+ getSessionOptions(_debug) {
747
+ return {
748
+ // Force pure WASM - no WebGL, no WebGPU, no JSEP
749
+ executionProviders: ['wasm'],
750
+ graphOptimizationLevel: 'disabled', // Disable graph optimization to reduce memory
751
+ logSeverityLevel: 4,
752
+ logVerbosityLevel: 0,
753
+ enableCpuMemArena: false, // Don't use CPU memory arena
754
+ enableMemPattern: false, // Don't use memory pattern optimization
755
+ executionMode: 'sequential', // Sequential execution uses less memory
756
+ interOpNumThreads: 1, // Single thread
757
+ intraOpNumThreads: 1, // Single thread
758
+ };
759
+ }
760
+ shouldUseSequentialLoading() {
761
+ return true;
762
+ }
763
+ }
764
+
700
765
  class DeviceStrategyFactory {
701
766
  static createStrategy() {
702
767
  const nav = navigator;
768
+ const ua = navigator.userAgent;
769
+ // iOS Safari requires special handling due to WebKit memory constraints
770
+ const isIOSSafari = /iPad|iPhone|iPod/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS|OPiOS|EdgiOS/.test(ua);
771
+ if (isIOSSafari) {
772
+ return new IOSSafariStrategy();
773
+ }
703
774
  const memory = nav.deviceMemory || nav.hardwareConcurrency || 4;
704
775
  const isLowMemory = memory <= 4;
705
776
  if (isLowMemory) {
@@ -18459,7 +18530,7 @@ class LicenseValidationService {
18459
18530
  static ENVIRONMENT_URLS = {
18460
18531
  dev: 'https://api.dev.jaak.ai',
18461
18532
  qa: 'https://api.qa.jaak.ai',
18462
- staging: 'https://api.sandbox.jaak.ai',
18533
+ sandbox: 'https://api.sandbox.jaak.ai',
18463
18534
  prod: 'https://services.api.jaak.ai',
18464
18535
  };
18465
18536
  constructor(environment = 'prod') {
@@ -18559,7 +18630,7 @@ class LicenseValidationService {
18559
18630
  }
18560
18631
  }
18561
18632
 
18562
- const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.license-error-container{display:flex;align-items:center;justify-content:center;padding:40px 20px;height:100%;width:100%;background:#f5f7fa;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif}.license-error-card{background:#ffffff;border-radius:8px;box-shadow:0 2px 12px rgba(0, 0, 0, 0.1);max-width:480px;width:100%;padding:48px 32px;text-align:center}.license-error-icon{color:#dc3545;margin-bottom:24px}.license-error-title{color:#2c3e50;font-size:24px;font-weight:600;margin:0 0 16px 0}.license-error-message{color:#495057;font-size:16px;line-height:1.6;margin:0 0 24px 0}.license-error-help{color:#6c757d;font-size:14px;margin:0 0 32px 0}.license-error-help a{color:#007bff;text-decoration:none;font-weight:500}.license-error-help a:hover{text-decoration:underline}.license-error-footer{color:#adb5bd;font-size:12px;font-weight:500;padding-top:24px;border-top:1px solid #e9ecef}@media (max-width: 640px){.license-error-card{padding:32px 24px}.license-error-title{font-size:20px}.license-error-message{font-size:14px}}";
18633
+ 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-color:#000;border:none;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}video::-webkit-media-controls{display:none !important}video::-webkit-media-controls-panel{display:none !important}video::-webkit-media-controls-play-button{display:none !important}video::-webkit-media-controls-start-playback-button{display:none !important}video::-webkit-media-controls-enclosure{display:none !important}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.license-error-container{display:flex;align-items:center;justify-content:center;padding:40px 20px;height:100%;width:100%;background:#f5f7fa;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif}.license-error-card{background:#ffffff;border-radius:8px;box-shadow:0 2px 12px rgba(0, 0, 0, 0.1);max-width:480px;width:100%;padding:48px 32px;text-align:center}.license-error-icon{color:#dc3545;margin-bottom:24px}.license-error-title{color:#2c3e50;font-size:24px;font-weight:600;margin:0 0 16px 0}.license-error-message{color:#495057;font-size:16px;line-height:1.6;margin:0 0 24px 0}.license-error-help{color:#6c757d;font-size:14px;margin:0 0 32px 0}.license-error-help a{color:#007bff;text-decoration:none;font-weight:500}.license-error-help a:hover{text-decoration:underline}.license-error-footer{color:#adb5bd;font-size:12px;font-weight:500;padding-top:24px;border-top:1px solid #e9ecef}@media (max-width: 640px){.license-error-card{padding:32px 24px}.license-error-title{font-size:20px}.license-error-message{font-size:14px}}";
18563
18634
 
18564
18635
  const JaakStamps = class {
18565
18636
  constructor(hostRef) {
@@ -18679,6 +18750,7 @@ const JaakStamps = class {
18679
18750
  MAX_FAILURES = 30;
18680
18751
  lastInferenceTime = 0;
18681
18752
  MIN_INFERENCE_INTERVAL = 50;
18753
+ MOBILE_MIN_INFERENCE_INTERVAL = 100; // Higher interval for mobile to prevent slow motion
18682
18754
  performanceHistory = [];
18683
18755
  PERFORMANCE_HISTORY_SIZE = 10;
18684
18756
  // Sistema simplificado de monitoreo de rendimiento
@@ -18843,10 +18915,25 @@ const JaakStamps = class {
18843
18915
  if (!window.ort) {
18844
18916
  this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
18845
18917
  const script = document.createElement('script');
18846
- script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
18918
+ // iOS Safari uses WASM-only build to prevent memory issues (WebKit 26+ bug)
18919
+ const ua = navigator.userAgent;
18920
+ const isIOSSafari = /iPad|iPhone|iPod/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS|OPiOS|EdgiOS/.test(ua);
18921
+ if (isIOSSafari) {
18922
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.wasm.min.js';
18923
+ }
18924
+ else {
18925
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
18926
+ }
18847
18927
  document.head.appendChild(script);
18848
18928
  await new Promise((resolve) => {
18849
18929
  script.onload = () => {
18930
+ // Configure ONNX for iOS Safari low memory mode
18931
+ if (isIOSSafari && window.ort?.env) {
18932
+ const ort = window.ort;
18933
+ ort.env.wasm.simd = false;
18934
+ ort.env.wasm.numThreads = 1;
18935
+ ort.env.wasm.proxy = false;
18936
+ }
18850
18937
  setTimeout(async () => {
18851
18938
  await this.finalizeInitialization();
18852
18939
  resolve(undefined);
@@ -19543,9 +19630,14 @@ const JaakStamps = class {
19543
19630
  return;
19544
19631
  }
19545
19632
  this.frameSkipCounter = 0;
19546
- // Throttle inference
19633
+ // Throttle inference - use higher interval for mobile devices to prevent slow motion effect
19547
19634
  const currentTime = Date.now();
19548
- if (currentTime - this.lastInferenceTime < this.MIN_INFERENCE_INTERVAL) {
19635
+ const isMobileDevice = this.cameraInfoWithAutofocus.deviceType === 'mobile' ||
19636
+ /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
19637
+ const isTablet = this.cameraInfoWithAutofocus.deviceType === 'tablet' ||
19638
+ (/iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768);
19639
+ const minInterval = (isMobileDevice && !isTablet) ? this.MOBILE_MIN_INFERENCE_INTERVAL : this.MIN_INFERENCE_INTERVAL;
19640
+ if (currentTime - this.lastInferenceTime < minInterval) {
19549
19641
  if (captureState.step !== 'completed') {
19550
19642
  this.animationId = requestAnimationFrame(() => this.detectFrame());
19551
19643
  }
@@ -19698,7 +19790,7 @@ const JaakStamps = class {
19698
19790
  isCapturing: false
19699
19791
  };
19700
19792
  const cameraInfo = this.cameraInfoWithAutofocus;
19701
- return (index.h("div", { key: 'a52b452a7aef6574a5e4b74a88c7bd3f9d45fd15', class: "detector-container" }, !this.licenseValid && this.licenseError && (index.h("div", { key: '18a99579e0b74ceb2cf1c4c3e21a864d2ec7d944', class: "license-error-container" }, index.h("div", { key: '3431722f4f47824c172a5b6f3a2ad9bd3bf377f2', class: "license-error-card" }, index.h("svg", { key: 'bf3854de3ba865a58c5c91cfd7a85d3d725be0df', class: "license-error-icon", width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, index.h("path", { key: '209e3a2a22333ec90e102a7156eb644b529f47c7', d: "M12 22C12 22 20 18 20 12V5L12 2L4 5V12C4 18 12 22 12 22Z", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), index.h("path", { key: '7b0dc1cd29a9055e552885b23f4537c19932556d', d: "M12 8V12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), index.h("circle", { key: '8ea686ebfb31a5744303f313fd104c03c02a5472', cx: "12", cy: "16", r: "0.5", fill: "currentColor", stroke: "currentColor", "stroke-width": "1" })), index.h("h2", { key: '7e0ec3ec23b80176cbec9c706999746d62486f3c', class: "license-error-title" }, "Licencia Requerida"), index.h("p", { key: '261a91e2c20feb09319dc4f319fb79611cc36a46', class: "license-error-message" }, this.licenseError), index.h("p", { key: '2e3cc8da0f4a8ebfd52db469b0f0900514d1d7f8', class: "license-error-help" }, "Contacte a soporte: ", index.h("a", { key: '283eea87e2f69dce1ea7376e3a63fed780633499', href: "mailto:support@jaak.ai" }, "support@jaak.ai")), index.h("div", { key: 'b4227ed25a4fb4d953c753671d27ac8c931a435f', class: "license-error-footer" }, "JAAK Stamps")))), this.licenseValid && (index.h("div", { key: 'cb33431cf6992f6e7466273406f729c42c1cbcdb', class: "video-container" }, index.h("video", { key: 'b44befd61ad616fd36fe1e98fd089a2443bc65b6', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '8cee95ddab5f771a87d43fa434a3ba0c241d2f3e', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
19793
+ return (index.h("div", { key: 'dc3aed0b4d2d84039b4a123d4bdfe484ea3320ef', class: "detector-container" }, !this.licenseValid && this.licenseError && (index.h("div", { key: 'a9da275905e2d051252f191be944675781632d10', class: "license-error-container" }, index.h("div", { key: 'a36f7408f0d862edd1183ba813c64d9be91f15c6', class: "license-error-card" }, index.h("svg", { key: 'a8e5f3eefc224e16fb62fc0b6d152c85cee249d1', class: "license-error-icon", width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, index.h("path", { key: 'f645fb46a62004309dee1ede871d980c104fedc5', d: "M12 22C12 22 20 18 20 12V5L12 2L4 5V12C4 18 12 22 12 22Z", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), index.h("path", { key: 'ee472eb3d17cddf46bef7e96561296ff6fbb1aed', d: "M12 8V12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), index.h("circle", { key: 'c58c787c6c9e307159b3103fdd240e282823aa2f', cx: "12", cy: "16", r: "0.5", fill: "currentColor", stroke: "currentColor", "stroke-width": "1" })), index.h("h2", { key: 'f737ae630fb7661d12a17b7e48cb2c3a1cb6cb4e', class: "license-error-title" }, "Licencia Requerida"), index.h("p", { key: 'fb5cf8d815cddc79f7b5e9ff39f34852a43bc976', class: "license-error-message" }, this.licenseError), index.h("p", { key: 'b47831ee55110b100aa0ea9cbe21d39d2fc19306', class: "license-error-help" }, "Contacte a soporte: ", index.h("a", { key: '52eb8dea1dfdc989b921134f57f1f3f188429061', href: "mailto:support@jaak.ai" }, "support@jaak.ai")), index.h("div", { key: 'fd78565f67049165236694de5ddd415421d0063e', class: "license-error-footer" }, "JAAK Stamps")))), this.licenseValid && (index.h("div", { key: '19e5854e1683f57dbde2d857289095db75084496', class: "video-container" }, index.h("video", { key: 'e44fbc4c020317899620e8aa9e7e3c854ca5a2d9', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '2b3af557f8ccb39fd7dbcef07fa6d41d021fd772', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
19702
19794
  position: 'absolute',
19703
19795
  left: `${box.x}px`,
19704
19796
  top: `${box.y}px`,
@@ -19707,9 +19799,9 @@ const JaakStamps = class {
19707
19799
  border: '2px solid #32406C',
19708
19800
  pointerEvents: 'none',
19709
19801
  boxSizing: 'border-box'
19710
- } })))), this.isMaskReady && (index.h("div", { key: '00a749ad234294695d9a292911b41fdacafd219a', class: "overlay-mask" }, index.h("div", { key: 'e344df8a709fca5225985ccc9f8def75ddbd393c', class: "card-outline" }, index.h("div", { key: '2dcae9e414f2b7131397d49a3d5ecabfc7cd5eb8', class: "side side-top" }), index.h("div", { key: '48ac3f06fa4a2c83741df378719911f682811caa', class: "side side-right" }), index.h("div", { key: '238a3269b2a12d528e8a1889d3109ee319c7fd88', class: "side side-bottom" }), index.h("div", { key: 'b02eec6c102d7e9fb03ef24dfce7c75e081ff1c4', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '15588129ca8027a6ec1c81d70001142986966826', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'f15fd9bddc20c54afcca7be79c3cf2f9ee2b74f7', class: "back-capture-section" }, index.h("div", { key: 'e3284e1301840a9555857fa879de9f4ca91dc21b', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (index.h("button", { key: '8508d13e117deb3b223308497b1db976643b86f2', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '99bd464541d9ee8589bc11e937c5ad6bca9d95c8', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: '3de5f0a142f9dfdfe4578c0fcbe004bb5fe94c56', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '268ce949bf5155b3729298f23eea0d7ec15b3590', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
19802
+ } })))), this.isMaskReady && (index.h("div", { key: '2cf20cef5e5dc4d5d3282413dce2af0f75b1babe', class: "overlay-mask" }, index.h("div", { key: '241879ebba739d63a736eca258b735c5c8fdac00', class: "card-outline" }, index.h("div", { key: '30d895b1a438636d9cd9a759b0fc096446e256a1', class: "side side-top" }), index.h("div", { key: 'dcca6a422e4c1c20bfc1bd17d7f45305422386a8', class: "side side-right" }), index.h("div", { key: '2cec938bd863737ae9c0906ed0bce2f16b7461fa', class: "side side-bottom" }), index.h("div", { key: 'c6045def438350be85f4333b9166111a9820c8fd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '187bb5013fe6e7454bfc498b3f24844bb16881a3', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'a8ffba382875432493eb6564e4d8872d5d7e485a', class: "back-capture-section" }, index.h("div", { key: '562c149a1f81b8a73df631c81dc2119c1b9427cd', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (index.h("button", { key: '6dc9a222959df902caec68d72c885fba11e41a79', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: 'dd3ae6cdba5f4e6085d4d31abebda12f1c1367f8', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: 'cddd1951d4370da5165bf0fa450e3c176ec6cf61', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: 'da8c1c0af97e164150110cce97d4f4d83e9ffb20', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
19711
19803
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
19712
- : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'bb5c00ad882c303d8a2b6ddc79c9d5b27ff9824e', class: "camera-controls" }, index.h("button", { key: '7d231b550b58a8336f125a1a7fe7235c8bb3d7dd', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '95d1e0d0937f7ef821ff3ad167cd42e25fd1c87f', class: "camera-selector-dropdown" }, index.h("div", { key: 'e42219e5895c82f40c559c27c4f80f3145fc0a0e', class: "camera-selector-header" }, index.h("span", { key: '544a6fb2e08211ab6c44b0bfb281124bee318fae' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '7c4a71243278491ee7dbcc7ab2564bc7e92f5da3', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'c9d24e0bd906d20370214fdfd24ae91abfda98e1', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '8c2e1d2ed02bc55c84f3f726e5be577686d0f32d', class: "device-info" }, index.h("small", { key: '142410a86e4f819b96ffc9573816994351588a3e' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '48c33b2d6c8c80d18fba6e5f454faaa24c28fbd9', class: "manual-capture-section" }, index.h("button", { key: 'e3d5bfe9ef8b8f16bf669b66bce4e7a500113798', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: 'f50640fe4308c765011e6ed175528920228df5e7', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '080ab315293fa1fe5b1d97aa2511a2ffe2b013f4', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: 'b062c546af50b799bb1fb7dda07b3ffa601c4bac', class: "flip-animation" }, index.h("div", { key: 'f7162dc31673c7db1d8e03a658a722a3d323b28e', class: "id-card-icon" }), index.h("div", { key: 'd950952b480342d80d37df5c48031d72be4ac01a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'bd576fd28065aa712e42636c5fd0e3566d26cc25', class: "success-animation" }, index.h("div", { key: '6ca5c24510df790bd1c955c5e2936909c4767560', class: "check-icon" }), index.h("div", { key: '3420164eacddbe1a9aa0a181528ba0be7683af56', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '8ec2b295b233e7bb8ab0cc1c183adedd4e3c7e04', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '97c8e7e6219984673ebc631da111c17bf72a83cb', class: "status-spinner" })), index.h("div", { key: '7ca06c48f295892ff77eceff6245891ef7d9dd5e', class: "status-content" }, index.h("div", { key: '08056c7ec8fd66d23e0b6714c08aeccc092cf46a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '1e32ca14de53cc2f105822ed5a58e691918b307b', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: 'c25022d9aadd37ead9ae7d1cf7c7d916cdeb9cc1', class: "performance-monitor" }, index.h("div", { key: 'f0f99c7e820532432ba64726ef0c2aeabeb3309f', class: "performance-expanded" }, index.h("div", { key: 'e839e90c9cb0f33068ac3311e41914aa52610149', class: "metrics-row" }, index.h("div", { key: '9f74f4877a048e63f542f2df54f569f79d6f8bd0', class: "metric-compact" }, index.h("span", { key: '21ec7255706d6fb2cfec035c4be92295ee0216b6', class: "metric-label" }, "FPS"), index.h("span", { key: 'c8be17f3dc752304bf6b7fd9746f11783f7ff6ca', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '79187257ea30ead7fb41534a94272a3a855e050b', class: "metric-compact" }, index.h("span", { key: 'cc53dc282c1fe4234217207c2085fa2be06a5458', class: "metric-label" }, "MEM"), index.h("span", { key: '032002ff63b3a2349b34833668fed8d6337e2063', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'e628febd5bbfb9f593e029b10827635ddd0344fb', class: "metrics-row" }, index.h("div", { key: 'b2fe558bab709ff6b6781648b13ebc66238f3298', class: "metric-compact" }, index.h("span", { key: 'd7d816aff11503657781f8ebe9cb5f0f07c7279e', class: "metric-label" }, "INF"), index.h("span", { key: '3d735d092b5b39d0e96f4ac59b3bf2acde739fab', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '9a83fd4fe43e8a3760aaea1f134c3101b8757681', class: "metric-compact" }, index.h("span", { key: '8136ab82b50230537f19cd58ed2da0956d291287', class: "metric-label" }, "FRAME"), index.h("span", { key: '6221cc10edff8c4b0e4034a3734deb548416a3e6', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: 'a31bad44d5c2c5fd5417ed82589c0f103bba1d32', class: "metrics-row" }, index.h("div", { key: '1441d11e0d83b3b44dfa963e415c3074691eb4f2', class: "metric-compact" }, index.h("span", { key: '9fe5643129aa7a842f75644b6f71b1ff80993f1b', class: "metric-label" }, "DET"), index.h("span", { key: '0604065a3867e71a745bfb4e804c7e96048cb70e', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'daff0fede819f2b59a4ddb83560dc53f6151c2c9', class: "metric-compact" }, index.h("span", { key: '75a55c4a4e36b98af3620866ec6f7842b9ef81f7', class: "metric-label" }, "RATE"), index.h("span", { key: '61776c3fd4c3200d5e18cc28d917c538b282625a', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '4092c684ee3a148ec40e266fd16a718f2c2f2f52', class: "watermark" }, index.h("img", { key: 'd7bae6157d22675f8b71dbf1afdf345374398c82', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" }))))));
19804
+ : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'd6478c7161d61e1f9f95a9d7bd13cd0cde157061', class: "camera-controls" }, index.h("button", { key: 'dfede9b72f07c88fc5133d3951ddfb6e507b8e8a', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '9218f824cc57b7a8b08f87ed0728c3995fa8b57e', class: "camera-selector-dropdown" }, index.h("div", { key: '48828e7532f49c3e9eece8051585aa803ea3c69e', class: "camera-selector-header" }, index.h("span", { key: '49488cbea9f196788e4110cb9b1f132b8097a771' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '5b762a2da1719c645a490e5234703ba07c50d294', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '4c13d185e4166dc59621dd8a79a2e7123d810dff', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '6860e0fbc0578a74fcd59b3567dd6e4f9a5a4139', class: "device-info" }, index.h("small", { key: '45e6045c7fdc0f4d58bc3c4da059a6b7c2ce09f6' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '600d4932b6a7ae7529ef9dc8c22bb352e0f3ae5d', class: "manual-capture-section" }, index.h("button", { key: '6ad22fbe065602d9214530c374aed05d728114cc', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: 'd54d5624764ef2774bac1f77551d4c657f4b3e92', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: 'ab8c46dd497c3911e72d685310c4dccd0e5aca57', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '545fc8d38085825921e5d61e9d20960b5767a50a', class: "flip-animation" }, index.h("div", { key: 'fd8540ca1e388070233e366aef791e00c1970de1', class: "id-card-icon" }), index.h("div", { key: 'f952bdf0b5eccffe5d6b36930250e699ebfb8fd8', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'cd9b11e3de2f799096b610582b66e044eda64c2e', class: "success-animation" }, index.h("div", { key: '73091c9dae5e6c0f2dbd23bf2e0b760f2495f33b', class: "check-icon" }), index.h("div", { key: 'ad2f9b0b90190b385740bb2fd4ed9454858f13a2', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: 'd74d66f12fab510f0aff01f0e233482ed6f63e46', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '9060ae79961c7b1b473d5148493fda6462e50388', class: "status-spinner" })), index.h("div", { key: 'fd0c61e49a751afd826d30ed9f63b96b58c8e275', class: "status-content" }, index.h("div", { key: 'd0f0e155fba7a09658f7c21beae5f07c26c0701e', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '0694da4ec0ef14750097e69e8caf1db88cf58ed5', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '277369112303b12a3cef89c1b7218fdc6f130723', class: "performance-monitor" }, index.h("div", { key: 'f6456cfd210c0d4b8b02b2b73d2405fa7c12923d', class: "performance-expanded" }, index.h("div", { key: '2cd33cbccf2cf3422f09bd3b914a3bc37b0a1190', class: "metrics-row" }, index.h("div", { key: '7871e7fe4f63249731ff33f87023351d9c5ded77', class: "metric-compact" }, index.h("span", { key: 'b864eb43b44838fd5c67c8e430bfa04553130473', class: "metric-label" }, "FPS"), index.h("span", { key: '7a0e6f4ebcf80b1483102094e0906b2b1a5aa83b', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'ef5e590957e12bd6e9371710654278396baa5314', class: "metric-compact" }, index.h("span", { key: '5cc740e4a2a1e07eb53bcb0cc8c5c3df59f27730', class: "metric-label" }, "MEM"), index.h("span", { key: '758018f1731d7f26aa5d84e7563b059f352be361', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '1bedd37486ae4b8ba547507f2664ea19c67a18c7', class: "metrics-row" }, index.h("div", { key: '4f99be14cda01b199cae93905043a6e03a852d1b', class: "metric-compact" }, index.h("span", { key: 'c29a4a3461633e776cd86168c59c89c6b39af18d', class: "metric-label" }, "INF"), index.h("span", { key: '9401828ffd1d3e41ce473ba3d0aec592a683472f', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '7d4fdad150bb665640add08464ea361aeaa8ef6b', class: "metric-compact" }, index.h("span", { key: '7c21d775970466b339fdbb392227ec7006b87e31', class: "metric-label" }, "FRAME"), index.h("span", { key: '255d408312a11f350ea75654fc64edf0d4bcfef2', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '54ab129c3ffda4f1a32a2b0f437a7c38cb480aba', class: "metrics-row" }, index.h("div", { key: 'a246db2ef3a69d0d64827a73dd32968cb1e978e9', class: "metric-compact" }, index.h("span", { key: '8da1c9a5be45bcc23584234cb04f0e75fa3662c6', class: "metric-label" }, "DET"), index.h("span", { key: 'ad05c2b885a6b344391f70ce3acee8b41523743d', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '7449e8f7cbd15a6b828b90c923212c3578de3d11', class: "metric-compact" }, index.h("span", { key: 'da37f89e9641edfdfd60de7fdbf2f6285a2a4120', class: "metric-label" }, "RATE"), index.h("span", { key: 'de71b2072ce451a304ceb6b158d32b0ecde6d64a', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '6e2c10e239bca24062cd2972b5e4a3eb0951d0eb', class: "watermark" }, index.h("img", { key: '63fbe0ade07a191ccc9c007fc2813ac8f6e5ab2f', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" }))))));
19713
19805
  }
19714
19806
  // Utility methods
19715
19807
  updateDetectionBoxes(boxes) {
@@ -20432,28 +20524,35 @@ const JaakStamps = class {
20432
20524
  }
20433
20525
  // Adaptive frame skipping based on performance
20434
20526
  getAdaptiveFrameSkip() {
20527
+ // Check if running on mobile device for more aggressive frame skipping
20528
+ const isMobileDevice = this.cameraInfoWithAutofocus.deviceType === 'mobile' ||
20529
+ /Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
20530
+ const isTablet = this.cameraInfoWithAutofocus.deviceType === 'tablet' ||
20531
+ (/iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768);
20532
+ // Mobile-specific base frame skip (more aggressive to prevent slow motion effect)
20533
+ const mobileBaseSkip = isMobileDevice && !isTablet ? 4 : this.BASE_FRAME_SKIP;
20435
20534
  // Base conditions for high frame skip
20436
20535
  if (this.consecutiveFailures > 20)
20437
20536
  return this.MAX_FRAME_SKIP;
20438
20537
  if (this.performanceMetrics.inferenceTime > 200)
20439
- return 6;
20538
+ return Math.max(6, mobileBaseSkip);
20440
20539
  if (this.performanceMetrics.memoryUsage > 200)
20441
- return 5;
20540
+ return Math.max(5, mobileBaseSkip);
20442
20541
  // Calculate average processing time
20443
20542
  if (this.performanceHistory.length > 0) {
20444
20543
  const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
20445
20544
  if (avgProcessingTime > 100)
20446
- return 4;
20545
+ return Math.max(4, mobileBaseSkip);
20447
20546
  if (avgProcessingTime > 80)
20448
- return 3;
20547
+ return Math.max(3, mobileBaseSkip);
20449
20548
  if (avgProcessingTime > 60)
20450
- return this.BASE_FRAME_SKIP + 1;
20549
+ return Math.max(this.BASE_FRAME_SKIP + 1, mobileBaseSkip);
20451
20550
  }
20452
20551
  // Low memory devices get higher frame skip
20453
20552
  if (this.performanceMetrics.memoryUsage > 150)
20454
- return 4;
20455
- // Performance is good, use base frame skip
20456
- return this.BASE_FRAME_SKIP;
20553
+ return Math.max(4, mobileBaseSkip);
20554
+ // Mobile devices use higher base frame skip for smoother video
20555
+ return mobileBaseSkip;
20457
20556
  }
20458
20557
  // Método para resetear la máscara a color blanco
20459
20558
  resetMaskToWhite() {