@jaak.ai/stamps 2.1.0-dev.1 → 2.1.0-dev.3
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.
- package/README.md +11 -1
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +225 -18
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.css +164 -9
- package/dist/collection/components/my-component/my-component.js +230 -16
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/DetectionService.js +16 -1
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/ServiceContainer.js +3 -2
- package/dist/collection/services/ServiceContainer.js.map +1 -1
- package/dist/components/jaak-stamps.js +228 -18
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +225 -18
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-4b2f9fb3.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-4b2f9fb3.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +8 -0
- package/dist/types/components.d.ts +8 -0
- package/dist/types/services/DetectionService.d.ts +2 -1
- package/dist/types/services/ServiceContainer.d.ts +1 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ yarn add @jaak.ai/stamps
|
|
|
65
65
|
| `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping |
|
|
66
66
|
| `preferred-camera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Preferred camera selection |
|
|
67
67
|
| `use-document-classification` | `boolean` | `false` | Enable AI document classification |
|
|
68
|
+
| `use-document-detector` | `boolean` | `true` | Enable/disable AI document detection model |
|
|
68
69
|
| `enable-back-document-timer` | `boolean` | `false` | Enable timer for back document capture |
|
|
69
70
|
| `back-document-timer-duration` | `number` | `20` | Duration in seconds for back document capture timer |
|
|
70
71
|
|
|
@@ -207,6 +208,7 @@ interface StatusResponse {
|
|
|
207
208
|
id="documentScanner"
|
|
208
209
|
preferred-camera="auto"
|
|
209
210
|
capture-delay="1500"
|
|
211
|
+
use-document-detector="true"
|
|
210
212
|
debug="false">
|
|
211
213
|
</jaak-stamps>
|
|
212
214
|
|
|
@@ -319,6 +321,7 @@ const DocumentScanner: React.FC = () => {
|
|
|
319
321
|
preferred-camera="auto"
|
|
320
322
|
capture-delay={1500}
|
|
321
323
|
use-document-classification={true}
|
|
324
|
+
use-document-detector={true}
|
|
322
325
|
/>
|
|
323
326
|
|
|
324
327
|
<div style={{ marginTop: '20px' }}>
|
|
@@ -389,7 +392,8 @@ interface CapturedImages {
|
|
|
389
392
|
#scanner
|
|
390
393
|
preferred-camera="auto"
|
|
391
394
|
[capture-delay]="1500"
|
|
392
|
-
[use-document-classification]="true"
|
|
395
|
+
[use-document-classification]="true"
|
|
396
|
+
[use-document-detector]="true">
|
|
393
397
|
</jaak-stamps>
|
|
394
398
|
|
|
395
399
|
<div class="controls">
|
|
@@ -741,6 +745,12 @@ A: Enable document classification:
|
|
|
741
745
|
**Q: Can I customize the detection area?**
|
|
742
746
|
A: Yes, use the `mask-size` property to adjust the detection frame size.
|
|
743
747
|
|
|
748
|
+
**Q: Can I disable automatic document detection?**
|
|
749
|
+
A: Yes, set `use-document-detector="false"` to disable AI detection and enable manual capture mode:
|
|
750
|
+
```html
|
|
751
|
+
<jaak-stamps use-document-detector="false"></jaak-stamps>
|
|
752
|
+
```
|
|
753
|
+
|
|
744
754
|
**Q: How do I get only the cropped images?**
|
|
745
755
|
A: Access the `cropped` property from the captured images:
|
|
746
756
|
```javascript
|
|
@@ -18,7 +18,7 @@ var patchBrowser = () => {
|
|
|
18
18
|
|
|
19
19
|
patchBrowser().then(async (options) => {
|
|
20
20
|
await index.globalScripts();
|
|
21
|
-
return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
|
|
21
|
+
return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"captureStateVersion":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
exports.setNonce = index.setNonce;
|
|
@@ -632,6 +632,7 @@ class DeviceStrategyFactory {
|
|
|
632
632
|
class DetectionService {
|
|
633
633
|
debug;
|
|
634
634
|
useDocumentClassification;
|
|
635
|
+
useDocumentDetector;
|
|
635
636
|
session;
|
|
636
637
|
mobileNetSession;
|
|
637
638
|
mobileNetClassMap;
|
|
@@ -649,9 +650,10 @@ class DetectionService {
|
|
|
649
650
|
// Static canvas pool for reuse across instances
|
|
650
651
|
static canvasPool = new Map();
|
|
651
652
|
static MAX_POOL_SIZE = 5;
|
|
652
|
-
constructor(debug = false, useDocumentClassification = false) {
|
|
653
|
+
constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
|
|
653
654
|
this.debug = debug;
|
|
654
655
|
this.useDocumentClassification = useDocumentClassification;
|
|
656
|
+
this.useDocumentDetector = useDocumentDetector;
|
|
655
657
|
this.deviceStrategy = DeviceStrategyFactory.createStrategy();
|
|
656
658
|
this.initializeCanvasPool();
|
|
657
659
|
}
|
|
@@ -659,6 +661,11 @@ class DetectionService {
|
|
|
659
661
|
if (this.modelLoaded || this.session) {
|
|
660
662
|
return;
|
|
661
663
|
}
|
|
664
|
+
if (!this.useDocumentDetector) {
|
|
665
|
+
// Skip loading the detection model but mark as "loaded" for compatibility
|
|
666
|
+
this.modelLoaded = true;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
662
669
|
try {
|
|
663
670
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
664
671
|
try {
|
|
@@ -739,6 +746,10 @@ class DetectionService {
|
|
|
739
746
|
}
|
|
740
747
|
}
|
|
741
748
|
isModelLoaded() {
|
|
749
|
+
if (!this.useDocumentDetector) {
|
|
750
|
+
// If detector is disabled, consider it "loaded" for compatibility
|
|
751
|
+
return this.modelLoaded;
|
|
752
|
+
}
|
|
742
753
|
return this.modelLoaded && !!this.session;
|
|
743
754
|
}
|
|
744
755
|
preprocess(video) {
|
|
@@ -763,6 +774,10 @@ class DetectionService {
|
|
|
763
774
|
return new window.ort.Tensor("float16", float16Data, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
|
|
764
775
|
}
|
|
765
776
|
async runInference(inputTensor) {
|
|
777
|
+
if (!this.useDocumentDetector) {
|
|
778
|
+
// Return empty array when document detector is disabled
|
|
779
|
+
return [];
|
|
780
|
+
}
|
|
766
781
|
if (!this.session) {
|
|
767
782
|
throw new Error('Detection model not loaded');
|
|
768
783
|
}
|
|
@@ -1040,7 +1055,7 @@ class ServiceContainer {
|
|
|
1040
1055
|
const eventBus = new EventBusService();
|
|
1041
1056
|
const stateManager = new StateManagerService(eventBus);
|
|
1042
1057
|
const cameraService = new CameraService(eventBus, config.preferredCamera);
|
|
1043
|
-
const detectionService = new DetectionService(config.debug, config.useDocumentClassification);
|
|
1058
|
+
const detectionService = new DetectionService(config.debug, config.useDocumentClassification, config.useDocumentDetector);
|
|
1044
1059
|
// Register services
|
|
1045
1060
|
this.services.set('eventBus', eventBus);
|
|
1046
1061
|
this.services.set('stateManager', stateManager);
|
|
@@ -1076,7 +1091,7 @@ class ServiceContainer {
|
|
|
1076
1091
|
}
|
|
1077
1092
|
}
|
|
1078
1093
|
|
|
1079
|
-
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}}";
|
|
1094
|
+
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
|
|
1080
1095
|
|
|
1081
1096
|
const JaakStamps = class {
|
|
1082
1097
|
constructor(hostRef) {
|
|
@@ -1090,6 +1105,7 @@ const JaakStamps = class {
|
|
|
1090
1105
|
maskSize = 90;
|
|
1091
1106
|
cropMargin = 20;
|
|
1092
1107
|
useDocumentClassification = false;
|
|
1108
|
+
useDocumentDetector = true;
|
|
1093
1109
|
preferredCamera = 'auto';
|
|
1094
1110
|
captureDelay = 1500;
|
|
1095
1111
|
enableBackDocumentTimer = false;
|
|
@@ -1130,6 +1146,8 @@ const JaakStamps = class {
|
|
|
1130
1146
|
detectionRate: 0
|
|
1131
1147
|
};
|
|
1132
1148
|
backDocumentTimerRemaining = 0;
|
|
1149
|
+
showManualCaptureButton = false;
|
|
1150
|
+
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
1133
1151
|
// Services
|
|
1134
1152
|
serviceContainer;
|
|
1135
1153
|
eventBus;
|
|
@@ -1193,6 +1211,7 @@ const JaakStamps = class {
|
|
|
1193
1211
|
maskSize: this.maskSize,
|
|
1194
1212
|
cropMargin: this.cropMargin,
|
|
1195
1213
|
useDocumentClassification: this.useDocumentClassification,
|
|
1214
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
1196
1215
|
preferredCamera: this.preferredCamera,
|
|
1197
1216
|
captureDelay: this.captureDelay
|
|
1198
1217
|
};
|
|
@@ -1250,27 +1269,39 @@ const JaakStamps = class {
|
|
|
1250
1269
|
document.head.appendChild(script);
|
|
1251
1270
|
await new Promise((resolve) => {
|
|
1252
1271
|
script.onload = () => {
|
|
1253
|
-
setTimeout(() => {
|
|
1254
|
-
this.finalizeInitialization();
|
|
1272
|
+
setTimeout(async () => {
|
|
1273
|
+
await this.finalizeInitialization();
|
|
1255
1274
|
resolve(undefined);
|
|
1256
1275
|
}, 300);
|
|
1257
1276
|
};
|
|
1258
1277
|
});
|
|
1259
1278
|
}
|
|
1260
1279
|
else {
|
|
1261
|
-
setTimeout(() => {
|
|
1262
|
-
this.finalizeInitialization();
|
|
1280
|
+
setTimeout(async () => {
|
|
1281
|
+
await this.finalizeInitialization();
|
|
1263
1282
|
}, 300);
|
|
1264
1283
|
}
|
|
1265
1284
|
}
|
|
1266
|
-
finalizeInitialization() {
|
|
1285
|
+
async finalizeInitialization() {
|
|
1267
1286
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1287
|
+
// Check camera permissions before showing "ready" status
|
|
1288
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
1289
|
+
if (hasPermissions) {
|
|
1290
|
+
this.currentStatus = {
|
|
1291
|
+
message: 'Listo para capturar',
|
|
1292
|
+
description: '',
|
|
1293
|
+
type: 'ready',
|
|
1294
|
+
isInitialized: true
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
this.currentStatus = {
|
|
1299
|
+
message: 'Permisos de cámara requeridos',
|
|
1300
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
1301
|
+
type: 'error',
|
|
1302
|
+
isInitialized: true
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1274
1305
|
this.emitReadyEvent();
|
|
1275
1306
|
}
|
|
1276
1307
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -1285,6 +1316,26 @@ const JaakStamps = class {
|
|
|
1285
1316
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
1286
1317
|
this.isReady.emit(isDocumentReady);
|
|
1287
1318
|
}
|
|
1319
|
+
async checkCameraPermissions() {
|
|
1320
|
+
try {
|
|
1321
|
+
if (!navigator.permissions) {
|
|
1322
|
+
// Fallback: try to access camera directly
|
|
1323
|
+
try {
|
|
1324
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
1325
|
+
stream.getTracks().forEach(track => track.stop());
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
catch {
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
1333
|
+
return permission.state === 'granted';
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1288
1339
|
handleStateChange(data) {
|
|
1289
1340
|
}
|
|
1290
1341
|
initializeResizeObserver() {
|
|
@@ -1302,6 +1353,32 @@ const JaakStamps = class {
|
|
|
1302
1353
|
this.updateMaskDimensions(rect);
|
|
1303
1354
|
}
|
|
1304
1355
|
}
|
|
1356
|
+
triggerRerender() {
|
|
1357
|
+
this.captureStateVersion = this.captureStateVersion + 1;
|
|
1358
|
+
}
|
|
1359
|
+
getGuideText(step) {
|
|
1360
|
+
if (step === 'completed') {
|
|
1361
|
+
return 'Proceso completado';
|
|
1362
|
+
}
|
|
1363
|
+
if (step === 'front') {
|
|
1364
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
1365
|
+
if (this.showManualCaptureButton) {
|
|
1366
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
1367
|
+
}
|
|
1368
|
+
// Instrucción básica de alineación
|
|
1369
|
+
return 'Alinee su identificación con el marco';
|
|
1370
|
+
}
|
|
1371
|
+
else if (step === 'back') {
|
|
1372
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
1373
|
+
if (this.useDocumentDetector) {
|
|
1374
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return 'Alinee su identificación con el marco';
|
|
1381
|
+
}
|
|
1305
1382
|
updateMaskDimensions(containerRect) {
|
|
1306
1383
|
if (!this.videoRef)
|
|
1307
1384
|
return;
|
|
@@ -1649,7 +1726,12 @@ const JaakStamps = class {
|
|
|
1649
1726
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1650
1727
|
const stream = await this.cameraService.setupCamera();
|
|
1651
1728
|
// Paso 4: Inicializar video y ocultar estado inmediatamente
|
|
1652
|
-
this.
|
|
1729
|
+
if (this.useDocumentDetector) {
|
|
1730
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
1731
|
+
}
|
|
1732
|
+
else {
|
|
1733
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1734
|
+
}
|
|
1653
1735
|
await this.initializeVideoStream(stream);
|
|
1654
1736
|
// Paso 5: Calibrar máscara
|
|
1655
1737
|
if (this.detectionContainer) {
|
|
@@ -1660,6 +1742,10 @@ const JaakStamps = class {
|
|
|
1660
1742
|
// Finalizar e iniciar captura
|
|
1661
1743
|
this.startTime = Date.now();
|
|
1662
1744
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1745
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
1746
|
+
if (!this.useDocumentDetector) {
|
|
1747
|
+
this.showManualCaptureButton = true;
|
|
1748
|
+
}
|
|
1663
1749
|
this.detectFrame();
|
|
1664
1750
|
}
|
|
1665
1751
|
catch (err) {
|
|
@@ -1691,6 +1777,18 @@ const JaakStamps = class {
|
|
|
1691
1777
|
const captureState = this.stateManager.getCaptureState();
|
|
1692
1778
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
1693
1779
|
return;
|
|
1780
|
+
// Show manual capture button when document detector is disabled
|
|
1781
|
+
if (!this.useDocumentDetector) {
|
|
1782
|
+
this.showManualCaptureButton = true;
|
|
1783
|
+
// Continue the loop for UI updates but skip detection logic
|
|
1784
|
+
if (captureState.step !== 'completed') {
|
|
1785
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
1786
|
+
}
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
this.showManualCaptureButton = false;
|
|
1791
|
+
}
|
|
1694
1792
|
if (captureState.isDetectionPaused) {
|
|
1695
1793
|
if (captureState.step !== 'completed') {
|
|
1696
1794
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
@@ -1818,7 +1916,7 @@ const JaakStamps = class {
|
|
|
1818
1916
|
isCapturing: false
|
|
1819
1917
|
};
|
|
1820
1918
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1821
|
-
return (index.h("div", { key: '
|
|
1919
|
+
return (index.h("div", { key: '3026fb421368ee82a1d2cefd8f9355ebf3194146', class: "detector-container" }, index.h("div", { key: '62b1230c3268412f4ff301d480d666065037d9b3', class: "video-container" }, index.h("video", { key: '170a0e7235522dec33b914b6d2841b36503c781f', 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: '915af3649a7a551a46f1cb97af0a45916666cc12', 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: {
|
|
1822
1920
|
position: 'absolute',
|
|
1823
1921
|
left: `${box.x}px`,
|
|
1824
1922
|
top: `${box.y}px`,
|
|
@@ -1827,9 +1925,9 @@ const JaakStamps = class {
|
|
|
1827
1925
|
border: '2px solid #32406C',
|
|
1828
1926
|
pointerEvents: 'none',
|
|
1829
1927
|
boxSizing: 'border-box'
|
|
1830
|
-
} })))), this.isMaskReady && (index.h("div", { key: '
|
|
1928
|
+
} })))), this.isMaskReady && (index.h("div", { key: 'b40cf136e5b9a120aa7243c12005383196428b34', class: "overlay-mask" }, index.h("div", { key: 'e82324a37682c94f03576e89d880c3b17520b5e5', class: "card-outline" }, index.h("div", { key: 'b1fd7df2a36300f2e6eb96474b449be74abd1ed4', class: "side side-top" }), index.h("div", { key: 'a6b3eb63fb35b22d0d53c8fd5aa88b2e299ae8c4', class: "side side-right" }), index.h("div", { key: '2d43e9281ad4fe05e314627aa249f56a7b0de9f1', class: "side side-bottom" }), index.h("div", { key: '231440a2dccc0f8ede35971ef092523d7be444b9', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'a923e81e11ead323b9bcf76bd6b0fea421bc1517', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '4b68212634cbc4916cd16c4e00ca4e91b924de6e', class: "back-capture-section" }, index.h("div", { key: 'f2ec22b132defe1e4026ad38977c52974c5cdb21', class: "back-capture-buttons" }, !this.useDocumentDetector && (index.h("button", { key: 'ae8c0e452fbf1bb69b6683ae775f1c1a4b7d22d9', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), index.h("button", { key: 'f1ec52715a584da161885bcbae0db7a38632905c', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1831
1929
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
1832
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (index.h("div", { key: '
|
|
1930
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'a831afce8ac493fe9716fc5608c1be5373cac9f4', class: "camera-controls" }, index.h("button", { key: '9a56a494d6357eac86211af857b74db4509aad64', 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: 'b08699bf97018b3318771aa9c19a5848fae6e09f', class: "camera-selector-dropdown" }, index.h("div", { key: '24d010fde00991a80f92803511ae31ab23d2d4ee', class: "camera-selector-header" }, index.h("span", { key: 'ea0a21c5989cc28dff59bab870cfbb39b401b466' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '2cbd997565bfaeceb799a95fcb8e26681bab22ef', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'c00abb13cb7299ff325b85d3fcd44dcad7839e53', 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: 'e369c9ae07f0638b490d67bb01ad2a3a7f9b56a1', class: "device-info" }, index.h("small", { key: '04d4a8c24a1a4487939f87e8af240962705fd685' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'f955e8e5b6917d01477fd171e3c16a44a3d0a916', class: "manual-capture-section" }, index.h("button", { key: 'cb122ec84fec7afae8185e561f4da197a210b0dd', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (index.h("div", { key: '98d15bcd0afab9daa3bc64096535db74f795723b', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '86132c96fc3ff3296c04c32c03ef624511d175fd', class: "flip-animation" }, index.h("div", { key: '05efba20dc5a4890a6de5650e67b5144853fa9d6', class: "id-card-icon" }), index.h("div", { key: 'a2e307b126a1c65681733e68bb737ed21e9cb119', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '38e1e4f5c3c8a5002fc45c8490e3134e31b4652a', class: "success-animation" }, index.h("div", { key: '2f6faebde51e90059106e64ef50af51d9395c0cf', class: "check-icon" }), index.h("div", { key: '63385f75ab3933966f58bb954c13f4cacb4d5c21', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '0853a33e794b26c727336fb13c9430d99b992d7f', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: 'e0d258b6897ebaa281473ee6633244a89d9b3cc2', class: "status-spinner" })), index.h("div", { key: 'e54b82a5a895734bdaeb95fc9bfb631fd4680c87', class: "status-content" }, index.h("div", { key: '57e712d858574138a9ffdefa298adc99765788f3', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'b2e1240eb71ed506157353fe5e24d62c3a0da8ab', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '3f3acb1ffc8e2c1d64cc8dd0e76bf64772a92def', class: "performance-monitor" }, index.h("div", { key: '83c1640404aad9552ad082a78327acf9facd79ad', class: "performance-expanded" }, index.h("div", { key: '18ee837b0fa64bae25285183cbf79733f7654153', class: "metrics-row" }, index.h("div", { key: 'b7d6b1c15b7e57e67d886c1bbc279573e795a3a3', class: "metric-compact" }, index.h("span", { key: '2b02e2323aadc064231ab8735a5810acc7bdd871', class: "metric-label" }, "FPS"), index.h("span", { key: 'd0cbe3e212f216834127456574eef6e61b4f3d6c', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '974538f93bbe0abb25eadba4a73d9a28f9c9ae0a', class: "metric-compact" }, index.h("span", { key: 'e04bc2180ede406b787f63f92eb6086d4e372c77', class: "metric-label" }, "MEM"), index.h("span", { key: 'c7911542d7a39a334b0a2f0f258256d11b297fa4', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '92bbce4d85240a84d53afce554792ae51597e16f', class: "metrics-row" }, index.h("div", { key: '7b78afc87a85a229b582d870e85d3015ca54b941', class: "metric-compact" }, index.h("span", { key: '7579b2b804c8a869894c0ed5a89c71f19d493c69', class: "metric-label" }, "INF"), index.h("span", { key: '029733bca2504d1d3c1fef77519e0df90b28d5d1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: 'f7fe505877d8e9214c2c819e714e3039c95c0ec7', class: "metric-compact" }, index.h("span", { key: 'ecef2280e78aecb7b811f9fa14c7fdbda8c4d065', class: "metric-label" }, "FRAME"), index.h("span", { key: 'c6cadf43725c125f371d3d849e8b09953a3747fc', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '832e8ee75377791a17279a3c7eee12fc45f21820', class: "metrics-row" }, index.h("div", { key: 'abb2dc9ce8a52374bc04e4b36560885427303fb6', class: "metric-compact" }, index.h("span", { key: '8f8c9cfab162a01807d07f44278f4f5889a1b206', class: "metric-label" }, "DET"), index.h("span", { key: '7359af4c236091e99b1f653ba20d8e3bd3acaf1b', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '7dd17d6d47289a11087a33c6104269899b72e46e', class: "metric-compact" }, index.h("span", { key: '0b1317becd3af6804e1fb1965593882886c51b15', class: "metric-label" }, "RATE"), index.h("span", { key: '9e94ba4257aa707e6c751852db48a17fb7ec8bae', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '77beb6f47153c43fddbc643fe00d1777ecfab295', class: "watermark" }, index.h("img", { key: 'e6aec0055f8ffb058dc2249fa1182dae06638a31', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
1833
1931
|
}
|
|
1834
1932
|
// Utility methods
|
|
1835
1933
|
updateDetectionBoxes(boxes) {
|
|
@@ -1944,6 +2042,115 @@ const JaakStamps = class {
|
|
|
1944
2042
|
}
|
|
1945
2043
|
}
|
|
1946
2044
|
}
|
|
2045
|
+
async takeManualScreenshot() {
|
|
2046
|
+
if (!this.videoRef)
|
|
2047
|
+
return;
|
|
2048
|
+
// When using manual capture, use mask coordinates for cropping
|
|
2049
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
2050
|
+
}
|
|
2051
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
2052
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
2053
|
+
return;
|
|
2054
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
2055
|
+
this.triggerCaptureAnimation();
|
|
2056
|
+
// Use pooled canvas for optimization
|
|
2057
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
2058
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
2059
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
2060
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
2061
|
+
// Calculate mask coordinates for cropping
|
|
2062
|
+
const container = this.detectionContainer.parentElement;
|
|
2063
|
+
const containerRect = container.getBoundingClientRect();
|
|
2064
|
+
// Get mask dimensions from CSS properties
|
|
2065
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
2066
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
2067
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
2068
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
2069
|
+
// Convert percentages to actual video coordinates
|
|
2070
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
2071
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
2072
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
2073
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
2074
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
2075
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
2076
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
2077
|
+
displayedVideoWidth = containerRect.width;
|
|
2078
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
2079
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
displayedVideoHeight = containerRect.height;
|
|
2083
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
2084
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
2085
|
+
}
|
|
2086
|
+
// Calculate mask coordinates in video space
|
|
2087
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
2088
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
2089
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
2090
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
2091
|
+
// Convert to video coordinates
|
|
2092
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
2093
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
2094
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
2095
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
2096
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
2097
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
2098
|
+
// Calculate crop coordinates
|
|
2099
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
2100
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
2101
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
2102
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
2103
|
+
// Use pooled canvas for cropped version
|
|
2104
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
2105
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
2106
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
2107
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
2108
|
+
const captureState = this.stateManager.getCaptureState();
|
|
2109
|
+
if (captureState.step === 'front') {
|
|
2110
|
+
this.stateManager.setCapturedImages({
|
|
2111
|
+
front: {
|
|
2112
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2113
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
// Check if document classification is enabled (independent of detector)
|
|
2117
|
+
if (this.useDocumentClassification) {
|
|
2118
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2119
|
+
if (classification && classification.class === 'passport') {
|
|
2120
|
+
this.completeProcess(true);
|
|
2121
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2122
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
this.stateManager.updateCaptureState({
|
|
2127
|
+
step: 'back',
|
|
2128
|
+
isDetectionPaused: true,
|
|
2129
|
+
showFlipAnimation: true
|
|
2130
|
+
});
|
|
2131
|
+
this.triggerRerender();
|
|
2132
|
+
setTimeout(() => {
|
|
2133
|
+
this.stateManager.updateCaptureState({
|
|
2134
|
+
showFlipAnimation: false,
|
|
2135
|
+
isDetectionPaused: false
|
|
2136
|
+
});
|
|
2137
|
+
this.triggerRerender();
|
|
2138
|
+
this.startBackDocumentTimer();
|
|
2139
|
+
}, 3000);
|
|
2140
|
+
}
|
|
2141
|
+
else if (captureState.step === 'back') {
|
|
2142
|
+
this.stateManager.setCapturedImages({
|
|
2143
|
+
back: {
|
|
2144
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2145
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
this.completeProcess(false);
|
|
2149
|
+
}
|
|
2150
|
+
// Return canvases to pool after use
|
|
2151
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2152
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2153
|
+
}
|
|
1947
2154
|
async takeScreenshot() {
|
|
1948
2155
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
1949
2156
|
return;
|