@jaak.ai/stamps 2.0.0-beta.4 → 2.0.0-beta.6

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 (29) hide show
  1. package/README.md +660 -510
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +241 -41
  4. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  5. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  6. package/dist/cjs/loader.cjs.js +1 -1
  7. package/dist/collection/components/my-component/my-component.css +54 -24
  8. package/dist/collection/components/my-component/my-component.js +91 -5
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/CameraService.js +158 -21
  11. package/dist/collection/services/CameraService.js.map +1 -1
  12. package/dist/collection/services/DetectionService.js +34 -16
  13. package/dist/collection/services/DetectionService.js.map +1 -1
  14. package/dist/components/jaak-stamps.js +244 -41
  15. package/dist/components/jaak-stamps.js.map +1 -1
  16. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  17. package/dist/esm/jaak-stamps.entry.js +241 -41
  18. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  19. package/dist/esm/loader.js +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-65990bdd.entry.js +2 -0
  23. package/dist/jaak-stamps-webcomponent/p-65990bdd.entry.js.map +1 -0
  24. package/dist/types/components/my-component/my-component.d.ts +6 -0
  25. package/dist/types/components.d.ts +18 -2
  26. package/dist/types/services/CameraService.d.ts +1 -0
  27. package/package.json +2 -2
  28. package/dist/jaak-stamps-webcomponent/p-10ee2dab.entry.js +0 -2
  29. package/dist/jaak-stamps-webcomponent/p-10ee2dab.entry.js.map +0 -1
@@ -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"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"currentStatus":[32],"performanceData":[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],"setTorchEnabled":[64],"focusAtPoint":[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"],"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],"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],"setTorchEnabled":[64],"focusAtPoint":[64]}]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -201,6 +201,7 @@ class CameraService {
201
201
  deviceType = 'desktop';
202
202
  preferredCameraFacing = null;
203
203
  preferredCamera = 'auto';
204
+ isManuallySelected = false;
204
205
  constructor(logger, eventBus, preferredCamera = 'auto') {
205
206
  this.logger = logger;
206
207
  this.eventBus = eventBus;
@@ -247,6 +248,11 @@ class CameraService {
247
248
  }))
248
249
  });
249
250
  this.setInitialCameraPreference();
251
+ this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
252
+ selectedCameraId: this.selectedCameraId,
253
+ preferredCameraFacing: this.preferredCameraFacing,
254
+ preferredCamera: this.preferredCamera
255
+ });
250
256
  this.eventBus.emit('camera-changed', this.selectedCameraId);
251
257
  return this.availableCameras;
252
258
  }
@@ -272,6 +278,7 @@ class CameraService {
272
278
  }
273
279
  this.selectedCameraId = cameraId;
274
280
  this.updatePreferredFacing(camera);
281
+ this.isManuallySelected = true; // Mark as manually selected
275
282
  this.savePreference();
276
283
  this.eventBus.emit('camera-changed', cameraId);
277
284
  }
@@ -279,12 +286,31 @@ class CameraService {
279
286
  return this.preferredCameraFacing;
280
287
  }
281
288
  async setupCamera(constraints) {
282
- const finalConstraints = constraints || await this.getMaxResolution();
283
- const stream = await navigator.mediaDevices.getUserMedia({
284
- video: finalConstraints,
285
- audio: false
286
- });
287
- return stream;
289
+ try {
290
+ const finalConstraints = constraints || await this.getMaxResolution();
291
+ const stream = await navigator.mediaDevices.getUserMedia({
292
+ video: finalConstraints,
293
+ audio: false
294
+ });
295
+ return stream;
296
+ }
297
+ catch (error) {
298
+ this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
299
+ // Fallback to basic constraints if getMaxResolution fails
300
+ const basicConstraints = {
301
+ width: { ideal: 1280 },
302
+ height: { ideal: 720 }
303
+ };
304
+ // Only add facingMode if not on desktop to avoid OverconstrainedError
305
+ if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
306
+ basicConstraints.facingMode = this.preferredCameraFacing;
307
+ }
308
+ this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
309
+ return await navigator.mediaDevices.getUserMedia({
310
+ video: basicConstraints,
311
+ audio: false
312
+ });
313
+ }
288
314
  }
289
315
  async switchCamera(cameraId) {
290
316
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
@@ -296,7 +322,8 @@ class CameraService {
296
322
  await this.setSelectedCamera(cameraId);
297
323
  this.logger.state('CAMARA_CAMBIADA', {
298
324
  label: selectedCamera.label,
299
- deviceId: selectedCamera.deviceId
325
+ deviceId: selectedCamera.deviceId,
326
+ isManuallySelected: this.isManuallySelected
300
327
  });
301
328
  }
302
329
  async flipToNextCamera() {
@@ -378,6 +405,13 @@ class CameraService {
378
405
  setInitialCameraPreference() {
379
406
  if (this.availableCameras.length === 0)
380
407
  return;
408
+ // Clear any existing localStorage preferences to ensure fresh start
409
+ localStorage.removeItem('jaak-stamps-camera-preference');
410
+ this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
411
+ preferredCamera: this.preferredCamera,
412
+ availableCamerasCount: this.availableCameras.length
413
+ });
414
+ this.isManuallySelected = false; // Reset manual selection flag
381
415
  if (this.preferredCamera === 'front') {
382
416
  this.selectFrontCamera();
383
417
  }
@@ -390,23 +424,56 @@ class CameraService {
390
424
  }
391
425
  selectFrontCamera() {
392
426
  this.preferredCameraFacing = 'user';
393
- const frontCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('front') ||
394
- camera.label.toLowerCase().includes('user') ||
395
- camera.label.toLowerCase().includes('selfie') ||
396
- !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
427
+ this.logger.state('BUSCANDO_CAMARA_FRONTAL', {
428
+ availableCameras: this.availableCameras.map(cam => ({
429
+ id: cam.deviceId,
430
+ label: cam.label
431
+ }))
432
+ });
433
+ const frontCamera = this.availableCameras.find(camera => {
434
+ const label = camera.label.toLowerCase();
435
+ return label.includes('front') ||
436
+ label.includes('user') ||
437
+ label.includes('selfie') ||
438
+ label.includes('frontal') ||
439
+ (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
440
+ });
441
+ // If not found, use the first camera (usually front on mobile)
397
442
  this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;
398
443
  this.logger.state('CAMARA_FRONTAL_SELECCIONADA', {
444
+ found: !!frontCamera,
399
445
  label: frontCamera?.label || this.availableCameras[0].label,
400
446
  deviceId: this.selectedCameraId
401
447
  });
402
448
  }
403
449
  selectBackCamera() {
404
450
  this.preferredCameraFacing = 'environment';
405
- const backCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
406
- camera.label.toLowerCase().includes('rear') ||
407
- camera.label.toLowerCase().includes('environment'));
451
+ this.logger.state('BUSCANDO_CAMARA_TRASERA', {
452
+ availableCameras: this.availableCameras.map(cam => ({
453
+ id: cam.deviceId,
454
+ label: cam.label
455
+ }))
456
+ });
457
+ // Try to find rear camera with multiple detection methods
458
+ let backCamera = this.availableCameras.find(camera => {
459
+ const label = camera.label.toLowerCase();
460
+ return label.includes('back') ||
461
+ label.includes('rear') ||
462
+ label.includes('environment') ||
463
+ label.includes('trasera') ||
464
+ label.includes('posterior');
465
+ });
466
+ // If not found by label, try to use the last camera (usually rear on mobile)
467
+ if (!backCamera && this.availableCameras.length > 1) {
468
+ backCamera = this.availableCameras[this.availableCameras.length - 1];
469
+ this.logger.state('USANDO_ULTIMA_CAMARA_COMO_TRASERA', {
470
+ label: backCamera.label,
471
+ deviceId: backCamera.deviceId
472
+ });
473
+ }
408
474
  this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;
409
475
  this.logger.state('CAMARA_TRASERA_SELECCIONADA', {
476
+ found: !!backCamera,
410
477
  label: backCamera?.label || this.availableCameras[0].label,
411
478
  deviceId: this.selectedCameraId
412
479
  });
@@ -417,8 +484,10 @@ class CameraService {
417
484
  this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
418
485
  }
419
486
  else {
420
- this.selectedCameraId = this.availableCameras[0].deviceId;
487
+ // For desktop, prefer front camera (usually built-in webcam)
488
+ this.selectFrontCamera();
421
489
  this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
490
+ type: 'front',
422
491
  label: this.availableCameras[0].label,
423
492
  deviceId: this.selectedCameraId
424
493
  });
@@ -432,15 +501,60 @@ class CameraService {
432
501
  }
433
502
  async getMaxResolution() {
434
503
  try {
504
+ // Ensure device type is detected before determining constraints
505
+ if (!this.deviceType || this.deviceType === 'desktop') {
506
+ await this.detectDeviceType();
507
+ }
435
508
  const videoConstraints = {};
436
- if (this.selectedCameraId) {
509
+ this.logger.state('CONFIGURANDO_CONSTRAINS_CAMARA', {
510
+ selectedCameraId: this.selectedCameraId,
511
+ preferredCameraFacing: this.preferredCameraFacing,
512
+ preferredCamera: this.preferredCamera,
513
+ deviceType: this.deviceType,
514
+ isManuallySelected: this.isManuallySelected
515
+ });
516
+ if (this.isManuallySelected && this.selectedCameraId) {
517
+ // When manually selected, use deviceId for exact camera selection
437
518
  videoConstraints.deviceId = { exact: this.selectedCameraId };
519
+ this.logger.state('USANDO_CAMARA_MANUAL', {
520
+ deviceId: this.selectedCameraId
521
+ });
438
522
  }
439
523
  else if (this.preferredCameraFacing) {
440
- videoConstraints.facingMode = this.preferredCameraFacing;
524
+ // Use facingMode for initial preference-based selection
525
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
526
+ const facingConstraint = this.deviceType === 'desktop'
527
+ ? { ideal: this.preferredCameraFacing }
528
+ : { exact: this.preferredCameraFacing };
529
+ videoConstraints.facingMode = facingConstraint;
530
+ this.logger.state('USANDO_FACING_CONSTRAINT', {
531
+ facingMode: this.preferredCameraFacing,
532
+ constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
533
+ });
534
+ }
535
+ else if (this.selectedCameraId) {
536
+ videoConstraints.deviceId = { exact: this.selectedCameraId };
537
+ this.logger.state('USANDO_CAMARA_SELECCIONADA', {
538
+ deviceId: this.selectedCameraId
539
+ });
441
540
  }
442
541
  else {
443
- videoConstraints.facingMode = "environment";
542
+ // Use preferredCamera setting to determine default facing mode
543
+ if (this.preferredCamera === 'front') {
544
+ videoConstraints.facingMode = "user";
545
+ }
546
+ else if (this.preferredCamera === 'back') {
547
+ videoConstraints.facingMode = "environment";
548
+ }
549
+ else {
550
+ // Auto mode: use environment for mobile/tablet, user for desktop
551
+ videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
552
+ }
553
+ this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
554
+ facingMode: videoConstraints.facingMode,
555
+ preferredCamera: this.preferredCamera,
556
+ deviceType: this.deviceType
557
+ });
444
558
  }
445
559
  const tempStream = await navigator.mediaDevices.getUserMedia({
446
560
  video: videoConstraints
@@ -472,6 +586,10 @@ class CameraService {
472
586
  }
473
587
  catch (err) {
474
588
  this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
589
+ // Ensure device type is detected before determining constraints
590
+ if (!this.deviceType || this.deviceType === 'desktop') {
591
+ await this.detectDeviceType();
592
+ }
475
593
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
476
594
  const fallbackConstraints = {
477
595
  width: { ideal: isTablet ? 1280 : 1920 },
@@ -479,14 +597,33 @@ class CameraService {
479
597
  };
480
598
  // Apply basic focus settings even for fallback
481
599
  this.applyBasicFocusSettings(fallbackConstraints);
482
- if (this.selectedCameraId) {
600
+ if (this.isManuallySelected && this.selectedCameraId) {
601
+ // When manually selected, use deviceId for exact camera selection
483
602
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
484
603
  }
485
604
  else if (this.preferredCameraFacing) {
486
- fallbackConstraints.facingMode = this.preferredCameraFacing;
605
+ // Use facingMode for initial preference-based selection
606
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
607
+ const facingConstraint = this.deviceType === 'desktop'
608
+ ? { ideal: this.preferredCameraFacing }
609
+ : { exact: this.preferredCameraFacing };
610
+ fallbackConstraints.facingMode = facingConstraint;
611
+ }
612
+ else if (this.selectedCameraId) {
613
+ fallbackConstraints.deviceId = { exact: this.selectedCameraId };
487
614
  }
488
615
  else {
489
- fallbackConstraints.facingMode = "environment";
616
+ // Use preferredCamera setting to determine default facing mode
617
+ if (this.preferredCamera === 'front') {
618
+ fallbackConstraints.facingMode = "user";
619
+ }
620
+ else if (this.preferredCamera === 'back') {
621
+ fallbackConstraints.facingMode = "environment";
622
+ }
623
+ else {
624
+ // Auto mode: use environment for mobile/tablet, user for desktop
625
+ fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
626
+ }
490
627
  }
491
628
  return fallbackConstraints;
492
629
  }
@@ -730,7 +867,7 @@ class DetectionService {
730
867
  modelLoaded = false;
731
868
  deviceStrategy;
732
869
  MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/ddmyp-v2.onnx";
733
- MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.onnx";
870
+ MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/squeezenet_new_fp32.onnx";
734
871
  MOBILENET_CLASSES_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.json";
735
872
  INPUT_SIZE = 320;
736
873
  CONFIDENCE_THRESHOLD = 0.6;
@@ -792,15 +929,27 @@ class DetectionService {
792
929
  }
793
930
  try {
794
931
  this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
795
- // Load class map
796
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
797
- if (!classResponse.ok) {
798
- throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
932
+ // Try to load class map (optional - SqueezeNet may not need it for basic classification)
933
+ try {
934
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
935
+ if (classResponse.ok) {
936
+ this.mobileNetClassMap = await classResponse.json();
937
+ this.logger.state('CLASES_MOBILENET_CARGADAS', {
938
+ classCount: Object.keys(this.mobileNetClassMap).length
939
+ });
940
+ }
941
+ }
942
+ catch (error) {
943
+ this.logger.warn('No se pudo cargar el mapa de clases de MobileNet, usando clasificación básica');
944
+ // Create a basic class map for document types
945
+ this.mobileNetClassMap = {
946
+ "0": "document",
947
+ "1": "id_card",
948
+ "2": "passport",
949
+ "3": "license",
950
+ "4": "other"
951
+ };
799
952
  }
800
- this.mobileNetClassMap = await classResponse.json();
801
- this.logger.state('CLASES_MOBILENET_CARGADAS', {
802
- classCount: Object.keys(this.mobileNetClassMap).length
803
- });
804
953
  // Load model
805
954
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
806
955
  try {
@@ -889,7 +1038,7 @@ class DetectionService {
889
1038
  try {
890
1039
  this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
891
1040
  const inputTensor = this.preprocessMobileNet(canvas);
892
- const feeds = { input: inputTensor };
1041
+ const feeds = { [this.mobileNetSession.inputNames[0]]: inputTensor };
893
1042
  const results = await this.mobileNetSession.run(feeds);
894
1043
  const output = results[Object.keys(results)[0]].data;
895
1044
  const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
@@ -899,7 +1048,8 @@ class DetectionService {
899
1048
  class: className,
900
1049
  confidence: confidence.toFixed(3),
901
1050
  classIndex: maxIdx,
902
- timestamp: Date.now()
1051
+ timestamp: Date.now(),
1052
+ results
903
1053
  });
904
1054
  return {
905
1055
  class: className,
@@ -938,7 +1088,7 @@ class DetectionService {
938
1088
  displayedVideoHeight = INPUT_SIZE / videoAspectRatio;
939
1089
  }
940
1090
  else {
941
- // Video is taller - pillarboxed in display
1091
+ // Video is taller - pillarboxed in display
942
1092
  displayedVideoHeight = INPUT_SIZE;
943
1093
  displayedVideoWidth = INPUT_SIZE * videoAspectRatio;
944
1094
  }
@@ -989,7 +1139,7 @@ class DetectionService {
989
1139
  const leftAligned = Math.abs(docLeft - maskLeft) <= tolerance;
990
1140
  return {
991
1141
  top: topAligned && leftAligned, // Esquina superior izquierda: borde top Y left alineados
992
- right: topAligned && rightAligned, // Esquina superior derecha: borde top Y right alineados
1142
+ right: topAligned && rightAligned, // Esquina superior derecha: borde top Y right alineados
993
1143
  bottom: bottomAligned && leftAligned, // Esquina inferior izquierda: borde bottom Y left alineados
994
1144
  left: bottomAligned && rightAligned // Esquina inferior derecha: borde bottom Y right alineados
995
1145
  };
@@ -1074,15 +1224,20 @@ class DetectionService {
1074
1224
  tempCanvas.width = 224;
1075
1225
  tempCanvas.height = 224;
1076
1226
  const tempCtx = tempCanvas.getContext('2d');
1227
+ // Resize image to 224x224 for SqueezeNet (using MobileNet interface)
1077
1228
  tempCtx.drawImage(canvas, 0, 0, 224, 224);
1078
1229
  const imageData = tempCtx.getImageData(0, 0, 224, 224);
1079
1230
  const data = imageData.data;
1080
1231
  const hw = 224 * 224;
1081
1232
  const arr = new Float32Array(3 * hw);
1233
+ // SqueezeNet preprocessing: normalize to [0,1] range and arrange in CHW format
1082
1234
  for (let i = 0; i < hw; i++) {
1083
- arr[i] = (data[i * 4 + 0] / 255 - 0.5) / 0.5;
1084
- arr[hw + i] = (data[i * 4 + 1] / 255 - 0.5) / 0.5;
1085
- arr[2 * hw + i] = (data[i * 4 + 2] / 255 - 0.5) / 0.5;
1235
+ // Red channel
1236
+ arr[i] = data[i * 4 + 0] / 255.0;
1237
+ // Green channel
1238
+ arr[hw + i] = data[i * 4 + 1] / 255.0;
1239
+ // Blue channel
1240
+ arr[2 * hw + i] = data[i * 4 + 2] / 255.0;
1086
1241
  }
1087
1242
  return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
1088
1243
  }
@@ -1143,7 +1298,7 @@ class ServiceContainer {
1143
1298
  }
1144
1299
  }
1145
1300
 
1146
- 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(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;z-index:20}.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-button{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;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:translateX(-50%) translateY(0)}.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:space-between;align-items:center;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:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.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}}";
1301
+ 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:space-between;align-items:center;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:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.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}}";
1147
1302
 
1148
1303
  const JaakStamps = class {
1149
1304
  constructor(hostRef) {
@@ -1154,11 +1309,13 @@ const JaakStamps = class {
1154
1309
  get el() { return index.getElement(this); }
1155
1310
  debug = false;
1156
1311
  alignmentTolerance = 15;
1157
- maskSize = 80;
1312
+ maskSize = 90;
1158
1313
  cropMargin = 20;
1159
1314
  useDocumentClassification = false;
1160
1315
  preferredCamera = 'auto';
1161
1316
  captureDelay = 1500;
1317
+ enableBackDocumentTimer = false;
1318
+ backDocumentTimerDuration = 20;
1162
1319
  captureCompleted;
1163
1320
  isReady;
1164
1321
  // State derived from services
@@ -1187,6 +1344,7 @@ const JaakStamps = class {
1187
1344
  successfulDetections: 0,
1188
1345
  detectionRate: 0
1189
1346
  };
1347
+ backDocumentTimerRemaining = 0;
1190
1348
  // Services
1191
1349
  serviceContainer;
1192
1350
  logger;
@@ -1205,6 +1363,7 @@ const JaakStamps = class {
1205
1363
  hasScreenshotTaken = false;
1206
1364
  alignmentStartTime;
1207
1365
  alignmentTimer;
1366
+ backDocumentTimer;
1208
1367
  // Performance monitoring
1209
1368
  performanceMetrics = {
1210
1369
  fps: 0,
@@ -1670,7 +1829,13 @@ const JaakStamps = class {
1670
1829
  });
1671
1830
  }
1672
1831
  // Update document detection state
1832
+ const wasDocumentDetected = this.hasDocumentDetected;
1673
1833
  this.hasDocumentDetected = detections.length > 0;
1834
+ // Restart timer if document detected during back capture
1835
+ if (!wasDocumentDetected && this.hasDocumentDetected &&
1836
+ captureState.step === 'back' && this.enableBackDocumentTimer) {
1837
+ this.startBackDocumentTimer();
1838
+ }
1674
1839
  // Adaptive frame rate
1675
1840
  if (detections.length === 0) {
1676
1841
  this.consecutiveFailures++;
@@ -1747,7 +1912,7 @@ const JaakStamps = class {
1747
1912
  availableCameras: [],
1748
1913
  selectedCameraId: null,
1749
1914
  deviceType: 'desktop'};
1750
- return (index.h("div", { key: '7af79a0ff25b51790a9477cd7b338a0584172fc2', class: "detector-container" }, index.h("div", { key: 'ebf6a3811e0e599f29e98325b975a645e3bfb5e0', class: "video-container" }, index.h("video", { key: '5208fc9574fa78b1149be5839bc26d04bf9bcdfc', 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: 'b507c786eccbd21acdd884ef50bc6ec280b9d437', 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: {
1915
+ return (index.h("div", { key: '9d4d77042218ab3d0bc4fb1ce879db09dc3e78b4', class: "detector-container" }, index.h("div", { key: '51ac8ccb1275637282b038ad8364d19f14ef61b1', class: "video-container" }, index.h("video", { key: 'a34f0481da20ec5b7b62b21aa275ec5f6dfec5d1', 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: '43b71dfe3eaf20ffa66a4139e24b1af6a2074631', 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: {
1751
1916
  position: 'absolute',
1752
1917
  left: `${box.x}px`,
1753
1918
  top: `${box.y}px`,
@@ -1756,7 +1921,9 @@ const JaakStamps = class {
1756
1921
  border: '2px solid #32406C',
1757
1922
  pointerEvents: 'none',
1758
1923
  boxSizing: 'border-box'
1759
- } })))), this.isMaskReady && (index.h("div", { key: '53066c626b23daae351ea9a80409e8882434b0dd', class: "overlay-mask" }, index.h("div", { key: '77bb4048d43e328e57a5c7e1909e0a59c8957217', class: "card-outline" }, index.h("div", { key: 'ea0f2610d903f985286856ab69f8be24fd42148e', class: "side side-top" }), index.h("div", { key: 'dc671111ef4adf33aa41f4457fdf344ede23eb80', class: "side side-right" }), index.h("div", { key: '2475ec41577dca92b425542c8861c570ed7ecd3b', class: "side side-bottom" }), index.h("div", { key: 'a06171c8eb1bd6353a6464485604560d73ac04b7', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'ecfd98f36d457fe7282b26f6a0efef5361380c76', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("button", { key: 'f3c7b66500110437e9e4a7442cb327f2fdc6d71f', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (index.h("div", { key: 'b5b2a8dfc6dbcf381c32c4caa562c27ced2e3e56', class: "camera-controls" }, index.h("button", { key: '6b40d4649dfa22c5ea8ca90cacce35cd0ca53ff3', 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: '41feec90287510b08bd1090ad15586b323007c30', class: "camera-selector-dropdown" }, index.h("div", { key: '8310ebe3db842c463ad428b973f841053315a76f', class: "camera-selector-header" }, index.h("span", { key: '6693bdfa33643dfe73798d4f25b0c2d9de7aac7c' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '8552d45e1482c570d7ff85588d4404eec7c369d7', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '5333ec0e5fb2c50a07a7c0b7d4dfb1bb4ee0df1c', 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}`), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '8adf70b0e632c56a4784dec44621688809a3ff56', class: "device-info" }, index.h("small", { key: '2ea23922386ecda4a9011b50faba5efe4b4e60bb' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (index.h("div", { key: '8e693db67fe1e6ae8f11082b8993682b6d19b11d', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '474489ed7848cdc163c492a5e19f5f13231e17b3', class: "flip-animation" }, index.h("div", { key: 'a9bd24ca21330882f001b7811077630eb1c30f50', class: "id-card-icon" }), index.h("div", { key: 'ffadb660a61e0f5e3e29bdc473d987e87dc4ad8a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'd2522ee33aa159d49f040a293775892fb1cb81b5', class: "success-animation" }, index.h("div", { key: 'e7f899fac58bc9a3d51cf2cb89ee33bb320b241f', class: "check-icon" }), index.h("div", { key: 'd5d0ee1f37dbc659ca625ad03cbce325be0cf0a6', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '425d347666032c3982a660ebee13b47670b996cb', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '4d396fa107872f9a1e526fd59dc4edf279a05792', class: "status-spinner" })), index.h("div", { key: '232e9ae24c8cf5ef8ac0e3442e45a920799c724d', class: "status-content" }, index.h("div", { key: 'fe9dc0ca6d8e8276052bac7d2d44f1c7c9730364', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '6910c7ac78fd89e6fa1165446ec6d46fb356035c', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '7282b8afaf03da53b01a23d04babd772528c3c14', class: "performance-monitor" }, index.h("div", { key: 'b582b3124fed13387cd70a02a34ab2bb541f3bfc', class: "performance-expanded" }, index.h("div", { key: '764bac2d61224b9a5f306f50934d410e1bd27787', class: "metrics-row" }, index.h("div", { key: '261563f8314a9f6069b1ac6362eb9c38936a9dc7', class: "metric-compact" }, index.h("span", { key: '4feea98eff86620c1bc90eb65d9799102423109d', class: "metric-label" }, "FPS"), index.h("span", { key: 'eb15f13b27b8aa50701ad3d11c4cf87200010b67', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '0813c35c2c33286f2276bfb5adce59b32c7bb597', class: "metric-compact" }, index.h("span", { key: '201e7bcb556e63b141e42a3ffc3419eb4284bfa8', class: "metric-label" }, "MEM"), index.h("span", { key: '4f6703cbe0128440d0f2f22f631035ab85422c64', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '6343c361c93138b9ea66e8ae8acd361b93333f8c', class: "metrics-row" }, index.h("div", { key: 'd3c81fe597004bf380fae2d32f3a8314317e7769', class: "metric-compact" }, index.h("span", { key: 'fce572af3e200a611974dceef4da02f6aecedffc', class: "metric-label" }, "INF"), index.h("span", { key: '18dd78d54dcecfb70ddf59b782f87f8c6f208b0f', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '4c31ebfd835b017fd3e41b23d6bd9b19add511aa', class: "metric-compact" }, index.h("span", { key: '66b1a380b9479a8fc1e8c2385cb72aa4158dc74e', class: "metric-label" }, "FRAME"), index.h("span", { key: '3c6e41f7f11123c524d3a9df409345929d63f352', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '3b522e2cdfe4bd9f1dd6eb0f91ee3577dc43e490', class: "metrics-row" }, index.h("div", { key: 'fa1e6f687b43aa2582b8b3426feb03a16c6493fc', class: "metric-compact" }, index.h("span", { key: 'a9355091e71a33a6a0d7cc6c11171dcda2e51b22', class: "metric-label" }, "DET"), index.h("span", { key: 'beca5a7ccf4853675cf8366bcf577ebfbd46c4f0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '4b07fc9b3d35f99bc6e77c8726192cde0476a787', class: "metric-compact" }, index.h("span", { key: 'a1674b538f45328ee0afb00e582c07e0b4ccf553', class: "metric-label" }, "RATE"), index.h("span", { key: 'fd384a5137bc3df6c939d3d698a3ef80e9087030', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: 'ebbaf15a0ad96e4da5bbf746c32d7a4abb51b973', class: "watermark" }, index.h("img", { key: '8e7d274b529d1f7020ec4a7225579792763f871a', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1924
+ } })))), this.isMaskReady && (index.h("div", { key: 'c2596f8cabb227e3ea3b082495f52b141cb1690f', class: "overlay-mask" }, index.h("div", { key: 'ba1ea39b5df1cd312a9bd9410b7225a29cfb8a29', class: "card-outline" }, index.h("div", { key: '6ce4d4d969d5f3fbcf75a4eb285f61d434c5633b', class: "side side-top" }), index.h("div", { key: '36653ecc4fcda25dfc84bc4d1f4c4c5dfb71ced7', class: "side side-right" }), index.h("div", { key: '8b108fa8fb370b229a0d1a77cb1ff044319a8f75', class: "side side-bottom" }), index.h("div", { key: 'a7aa3b45c065a50445aafd5c3ccefb57b0c9c769', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'b94b92273e12a019238e11ae57f21e140f13c3f1', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '144c3741003ec0cc148bb6ce92012d7b3cd110c8', class: "skip-section" }, index.h("div", { key: 'f0a984a6b75afa374c5d47da9d53e39f98071da0', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), index.h("button", { key: '57ac30d776e30709aab6e0a621cef889da3cc677', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1925
+ ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1926
+ : 'Saltar reverso'))), captureState.isVideoActive && (index.h("div", { key: 'bd45628707e0169317ed3dd4247e1056d7046104', class: "camera-controls" }, index.h("button", { key: 'e750fbf4d5d76d9e9596823bb2b86801ef19e485', 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: '40ada780a8ea13aeeaee9cb39bc6496f69f2679d', class: "camera-selector-dropdown" }, index.h("div", { key: '3a920bb02ba024f0359513f10544bf0528ecee6d', class: "camera-selector-header" }, index.h("span", { key: 'e5efd478a602b13821ba53cb9a7a59d1b4a71178' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '2df6cc11e162be74862029e33815927e3fa8f2f8', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '06934642dff134c9e1a6872f935a5892a9c7b835', 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}`), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '8d5ede287c2efb0ebe418bee8dc7fb310443c991', class: "device-info" }, index.h("small", { key: 'b7dd10565113694b09f01ab28ffa29262ae1d2bc' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (index.h("div", { key: '2f9e549e6c0c56d4db5835e58361fb17bba6cf90', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: 'a6ef0ed127f23e9d764896ed743ca8d8454851a6', class: "flip-animation" }, index.h("div", { key: '0b04411f88a634bb3243b00ca2d5a45fd6962dea', class: "id-card-icon" }), index.h("div", { key: '9327be58879b407cea87f455989406e58302e9d1', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'bd268578b5003e99256e3340b4a9a6fb7656be64', class: "success-animation" }, index.h("div", { key: '064e0a5b9851de14bc6829f028e7ea7161fd1633', class: "check-icon" }), index.h("div", { key: 'bdcd94a8806399e396e218a359c10b11d05f38d6', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '3d9af617376c16e44e065873919c3d90ff46110a', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: 'be7b56decfd6f12dcca46b2a9456480e3d5ac00e', class: "status-spinner" })), index.h("div", { key: '5fe9850883cb749cebaa18c3e9c13ca21325e373', class: "status-content" }, index.h("div", { key: '43fdb50691e4b98e56126af1a129f625061128bf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'b76cbfa4a0349421fcd535d3fd8bf40c89ca3aef', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: 'eddefb464af0487ab9ab2a0f76dccbb6a2092e51', class: "performance-monitor" }, index.h("div", { key: '7b7d93b56cb4cd4fbb1bdde89c6150b282fa805e', class: "performance-expanded" }, index.h("div", { key: 'f302e8b9d985fb8cad53c4c4e0d37a3bf5a63261', class: "metrics-row" }, index.h("div", { key: 'e8d8f2420ee8643a7f454d38065c75b5be598f77', class: "metric-compact" }, index.h("span", { key: '6e52eb5bb06e0df57e61774de38c31349f45e22b', class: "metric-label" }, "FPS"), index.h("span", { key: '6636f9cea20c49b49c17db86188e2f6b214c45c3', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'afa1d9dec05b14256a1b1fcec8638b8c21a56e37', class: "metric-compact" }, index.h("span", { key: '9c55e30f5d01582648bacc1fb38ca1990485a591', class: "metric-label" }, "MEM"), index.h("span", { key: 'ca263601e8715027f95ffb2d5413a643dc570cf2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '03caf19961fd01b7781484d49d8121b1e78c161f', class: "metrics-row" }, index.h("div", { key: 'bffb08f01f703411f7c8605e4f8d95b1c37b5c0f', class: "metric-compact" }, index.h("span", { key: '9b92279b3253cc669412733facb3751f4344d69f', class: "metric-label" }, "INF"), index.h("span", { key: '3234d4150e2e1479e3b552767af8123e69911d6c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '77082d2669db53f19fb50b3bbfa1ebefb0b86ff4', class: "metric-compact" }, index.h("span", { key: 'cc6ef96c72c800ba36dab2a388f1de863b2dacd5', class: "metric-label" }, "FRAME"), index.h("span", { key: 'e2bb4b7351869a76e810f8bbf20d805154495e35', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '5700d7ef839fa77686299d79445203090dc8079f', class: "metrics-row" }, index.h("div", { key: 'a2999a035fccb7f250c01b084075490678513c87', class: "metric-compact" }, index.h("span", { key: '3677d58665996fa5969507dbaff9e956b2ea54ee', class: "metric-label" }, "DET"), index.h("span", { key: 'c584312438b339c82a3bdc33f00fb985ff2a6f55', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '58866219f213bf8607083f30be2d5ca5d0515cea', class: "metric-compact" }, index.h("span", { key: 'd90b450051331347f219b6c0f197536d0dcee9b7', class: "metric-label" }, "RATE"), index.h("span", { key: 'f6e6cd7948e06014e5a0416e4e81394fa36ec61d', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: 'c1d0194f181a0082cc1aa5452de382908da61b65', class: "watermark" }, index.h("img", { key: 'e04d35f3c79a2b4a3922ab5b0f102220171fa7c0', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1760
1927
  }
1761
1928
  // Utility methods
1762
1929
  updateDetectionBoxes(boxes) {
@@ -1825,6 +1992,15 @@ const JaakStamps = class {
1825
1992
  bottomSide?.classList.toggle('aligned', currentAlignment.bottom);
1826
1993
  leftSide?.classList.toggle('aligned', currentAlignment.left);
1827
1994
  const allSidesAligned = this.detectionService.areAllSidesAligned(currentAlignment);
1995
+ // Restart back document timer if any border is aligned during back capture
1996
+ const captureState = this.stateManager.getCaptureState();
1997
+ if (captureState.step === 'back' && this.enableBackDocumentTimer && bestBox) {
1998
+ const anyBorderAligned = currentAlignment.top || currentAlignment.right ||
1999
+ currentAlignment.bottom || currentAlignment.left;
2000
+ if (anyBorderAligned) {
2001
+ this.startBackDocumentTimer();
2002
+ }
2003
+ }
1828
2004
  if (allSidesAligned && bestBox) {
1829
2005
  cardOutline?.classList.add('perfect-match');
1830
2006
  corners?.forEach(corner => corner.classList.add('perfect-match'));
@@ -1939,6 +2115,7 @@ const JaakStamps = class {
1939
2115
  showFlipAnimation: false,
1940
2116
  isDetectionPaused: false
1941
2117
  });
2118
+ this.startBackDocumentTimer();
1942
2119
  }, 3000);
1943
2120
  }
1944
2121
  else if (captureState.step === 'back') {
@@ -1989,9 +2166,32 @@ const JaakStamps = class {
1989
2166
  cancelAnimationFrame(this.animationId);
1990
2167
  this.animationId = undefined;
1991
2168
  }
2169
+ this.clearBackDocumentTimer();
1992
2170
  this.detectionBoxes = [];
1993
2171
  this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
1994
2172
  }
2173
+ startBackDocumentTimer() {
2174
+ if (!this.enableBackDocumentTimer)
2175
+ return;
2176
+ if (this.backDocumentTimer) {
2177
+ clearInterval(this.backDocumentTimer);
2178
+ }
2179
+ this.backDocumentTimerRemaining = this.backDocumentTimerDuration;
2180
+ this.backDocumentTimer = window.setInterval(() => {
2181
+ this.backDocumentTimerRemaining--;
2182
+ if (this.backDocumentTimerRemaining <= 0) {
2183
+ this.clearBackDocumentTimer();
2184
+ this.skipBackCapture();
2185
+ }
2186
+ }, 1000);
2187
+ }
2188
+ clearBackDocumentTimer() {
2189
+ if (this.backDocumentTimer) {
2190
+ clearInterval(this.backDocumentTimer);
2191
+ this.backDocumentTimer = undefined;
2192
+ }
2193
+ this.backDocumentTimerRemaining = 0;
2194
+ }
1995
2195
  toggleCameraSelector() {
1996
2196
  if (this.isSwitchingCamera)
1997
2197
  return; // Don't toggle if switching camera