@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
@@ -199,6 +199,7 @@ class CameraService {
199
199
  deviceType = 'desktop';
200
200
  preferredCameraFacing = null;
201
201
  preferredCamera = 'auto';
202
+ isManuallySelected = false;
202
203
  constructor(logger, eventBus, preferredCamera = 'auto') {
203
204
  this.logger = logger;
204
205
  this.eventBus = eventBus;
@@ -245,6 +246,11 @@ class CameraService {
245
246
  }))
246
247
  });
247
248
  this.setInitialCameraPreference();
249
+ this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
250
+ selectedCameraId: this.selectedCameraId,
251
+ preferredCameraFacing: this.preferredCameraFacing,
252
+ preferredCamera: this.preferredCamera
253
+ });
248
254
  this.eventBus.emit('camera-changed', this.selectedCameraId);
249
255
  return this.availableCameras;
250
256
  }
@@ -270,6 +276,7 @@ class CameraService {
270
276
  }
271
277
  this.selectedCameraId = cameraId;
272
278
  this.updatePreferredFacing(camera);
279
+ this.isManuallySelected = true; // Mark as manually selected
273
280
  this.savePreference();
274
281
  this.eventBus.emit('camera-changed', cameraId);
275
282
  }
@@ -277,12 +284,31 @@ class CameraService {
277
284
  return this.preferredCameraFacing;
278
285
  }
279
286
  async setupCamera(constraints) {
280
- const finalConstraints = constraints || await this.getMaxResolution();
281
- const stream = await navigator.mediaDevices.getUserMedia({
282
- video: finalConstraints,
283
- audio: false
284
- });
285
- return stream;
287
+ try {
288
+ const finalConstraints = constraints || await this.getMaxResolution();
289
+ const stream = await navigator.mediaDevices.getUserMedia({
290
+ video: finalConstraints,
291
+ audio: false
292
+ });
293
+ return stream;
294
+ }
295
+ catch (error) {
296
+ this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
297
+ // Fallback to basic constraints if getMaxResolution fails
298
+ const basicConstraints = {
299
+ width: { ideal: 1280 },
300
+ height: { ideal: 720 }
301
+ };
302
+ // Only add facingMode if not on desktop to avoid OverconstrainedError
303
+ if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
304
+ basicConstraints.facingMode = this.preferredCameraFacing;
305
+ }
306
+ this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
307
+ return await navigator.mediaDevices.getUserMedia({
308
+ video: basicConstraints,
309
+ audio: false
310
+ });
311
+ }
286
312
  }
287
313
  async switchCamera(cameraId) {
288
314
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
@@ -294,7 +320,8 @@ class CameraService {
294
320
  await this.setSelectedCamera(cameraId);
295
321
  this.logger.state('CAMARA_CAMBIADA', {
296
322
  label: selectedCamera.label,
297
- deviceId: selectedCamera.deviceId
323
+ deviceId: selectedCamera.deviceId,
324
+ isManuallySelected: this.isManuallySelected
298
325
  });
299
326
  }
300
327
  async flipToNextCamera() {
@@ -376,6 +403,13 @@ class CameraService {
376
403
  setInitialCameraPreference() {
377
404
  if (this.availableCameras.length === 0)
378
405
  return;
406
+ // Clear any existing localStorage preferences to ensure fresh start
407
+ localStorage.removeItem('jaak-stamps-camera-preference');
408
+ this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
409
+ preferredCamera: this.preferredCamera,
410
+ availableCamerasCount: this.availableCameras.length
411
+ });
412
+ this.isManuallySelected = false; // Reset manual selection flag
379
413
  if (this.preferredCamera === 'front') {
380
414
  this.selectFrontCamera();
381
415
  }
@@ -388,23 +422,56 @@ class CameraService {
388
422
  }
389
423
  selectFrontCamera() {
390
424
  this.preferredCameraFacing = 'user';
391
- const frontCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('front') ||
392
- camera.label.toLowerCase().includes('user') ||
393
- camera.label.toLowerCase().includes('selfie') ||
394
- !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
425
+ this.logger.state('BUSCANDO_CAMARA_FRONTAL', {
426
+ availableCameras: this.availableCameras.map(cam => ({
427
+ id: cam.deviceId,
428
+ label: cam.label
429
+ }))
430
+ });
431
+ const frontCamera = this.availableCameras.find(camera => {
432
+ const label = camera.label.toLowerCase();
433
+ return label.includes('front') ||
434
+ label.includes('user') ||
435
+ label.includes('selfie') ||
436
+ label.includes('frontal') ||
437
+ (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
438
+ });
439
+ // If not found, use the first camera (usually front on mobile)
395
440
  this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;
396
441
  this.logger.state('CAMARA_FRONTAL_SELECCIONADA', {
442
+ found: !!frontCamera,
397
443
  label: frontCamera?.label || this.availableCameras[0].label,
398
444
  deviceId: this.selectedCameraId
399
445
  });
400
446
  }
401
447
  selectBackCamera() {
402
448
  this.preferredCameraFacing = 'environment';
403
- const backCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
404
- camera.label.toLowerCase().includes('rear') ||
405
- camera.label.toLowerCase().includes('environment'));
449
+ this.logger.state('BUSCANDO_CAMARA_TRASERA', {
450
+ availableCameras: this.availableCameras.map(cam => ({
451
+ id: cam.deviceId,
452
+ label: cam.label
453
+ }))
454
+ });
455
+ // Try to find rear camera with multiple detection methods
456
+ let backCamera = this.availableCameras.find(camera => {
457
+ const label = camera.label.toLowerCase();
458
+ return label.includes('back') ||
459
+ label.includes('rear') ||
460
+ label.includes('environment') ||
461
+ label.includes('trasera') ||
462
+ label.includes('posterior');
463
+ });
464
+ // If not found by label, try to use the last camera (usually rear on mobile)
465
+ if (!backCamera && this.availableCameras.length > 1) {
466
+ backCamera = this.availableCameras[this.availableCameras.length - 1];
467
+ this.logger.state('USANDO_ULTIMA_CAMARA_COMO_TRASERA', {
468
+ label: backCamera.label,
469
+ deviceId: backCamera.deviceId
470
+ });
471
+ }
406
472
  this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;
407
473
  this.logger.state('CAMARA_TRASERA_SELECCIONADA', {
474
+ found: !!backCamera,
408
475
  label: backCamera?.label || this.availableCameras[0].label,
409
476
  deviceId: this.selectedCameraId
410
477
  });
@@ -415,8 +482,10 @@ class CameraService {
415
482
  this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
416
483
  }
417
484
  else {
418
- this.selectedCameraId = this.availableCameras[0].deviceId;
485
+ // For desktop, prefer front camera (usually built-in webcam)
486
+ this.selectFrontCamera();
419
487
  this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
488
+ type: 'front',
420
489
  label: this.availableCameras[0].label,
421
490
  deviceId: this.selectedCameraId
422
491
  });
@@ -430,15 +499,60 @@ class CameraService {
430
499
  }
431
500
  async getMaxResolution() {
432
501
  try {
502
+ // Ensure device type is detected before determining constraints
503
+ if (!this.deviceType || this.deviceType === 'desktop') {
504
+ await this.detectDeviceType();
505
+ }
433
506
  const videoConstraints = {};
434
- if (this.selectedCameraId) {
507
+ this.logger.state('CONFIGURANDO_CONSTRAINS_CAMARA', {
508
+ selectedCameraId: this.selectedCameraId,
509
+ preferredCameraFacing: this.preferredCameraFacing,
510
+ preferredCamera: this.preferredCamera,
511
+ deviceType: this.deviceType,
512
+ isManuallySelected: this.isManuallySelected
513
+ });
514
+ if (this.isManuallySelected && this.selectedCameraId) {
515
+ // When manually selected, use deviceId for exact camera selection
435
516
  videoConstraints.deviceId = { exact: this.selectedCameraId };
517
+ this.logger.state('USANDO_CAMARA_MANUAL', {
518
+ deviceId: this.selectedCameraId
519
+ });
436
520
  }
437
521
  else if (this.preferredCameraFacing) {
438
- videoConstraints.facingMode = this.preferredCameraFacing;
522
+ // Use facingMode for initial preference-based selection
523
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
524
+ const facingConstraint = this.deviceType === 'desktop'
525
+ ? { ideal: this.preferredCameraFacing }
526
+ : { exact: this.preferredCameraFacing };
527
+ videoConstraints.facingMode = facingConstraint;
528
+ this.logger.state('USANDO_FACING_CONSTRAINT', {
529
+ facingMode: this.preferredCameraFacing,
530
+ constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
531
+ });
532
+ }
533
+ else if (this.selectedCameraId) {
534
+ videoConstraints.deviceId = { exact: this.selectedCameraId };
535
+ this.logger.state('USANDO_CAMARA_SELECCIONADA', {
536
+ deviceId: this.selectedCameraId
537
+ });
439
538
  }
440
539
  else {
441
- videoConstraints.facingMode = "environment";
540
+ // Use preferredCamera setting to determine default facing mode
541
+ if (this.preferredCamera === 'front') {
542
+ videoConstraints.facingMode = "user";
543
+ }
544
+ else if (this.preferredCamera === 'back') {
545
+ videoConstraints.facingMode = "environment";
546
+ }
547
+ else {
548
+ // Auto mode: use environment for mobile/tablet, user for desktop
549
+ videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
550
+ }
551
+ this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
552
+ facingMode: videoConstraints.facingMode,
553
+ preferredCamera: this.preferredCamera,
554
+ deviceType: this.deviceType
555
+ });
442
556
  }
443
557
  const tempStream = await navigator.mediaDevices.getUserMedia({
444
558
  video: videoConstraints
@@ -470,6 +584,10 @@ class CameraService {
470
584
  }
471
585
  catch (err) {
472
586
  this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
587
+ // Ensure device type is detected before determining constraints
588
+ if (!this.deviceType || this.deviceType === 'desktop') {
589
+ await this.detectDeviceType();
590
+ }
473
591
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
474
592
  const fallbackConstraints = {
475
593
  width: { ideal: isTablet ? 1280 : 1920 },
@@ -477,14 +595,33 @@ class CameraService {
477
595
  };
478
596
  // Apply basic focus settings even for fallback
479
597
  this.applyBasicFocusSettings(fallbackConstraints);
480
- if (this.selectedCameraId) {
598
+ if (this.isManuallySelected && this.selectedCameraId) {
599
+ // When manually selected, use deviceId for exact camera selection
481
600
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
482
601
  }
483
602
  else if (this.preferredCameraFacing) {
484
- fallbackConstraints.facingMode = this.preferredCameraFacing;
603
+ // Use facingMode for initial preference-based selection
604
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
605
+ const facingConstraint = this.deviceType === 'desktop'
606
+ ? { ideal: this.preferredCameraFacing }
607
+ : { exact: this.preferredCameraFacing };
608
+ fallbackConstraints.facingMode = facingConstraint;
609
+ }
610
+ else if (this.selectedCameraId) {
611
+ fallbackConstraints.deviceId = { exact: this.selectedCameraId };
485
612
  }
486
613
  else {
487
- fallbackConstraints.facingMode = "environment";
614
+ // Use preferredCamera setting to determine default facing mode
615
+ if (this.preferredCamera === 'front') {
616
+ fallbackConstraints.facingMode = "user";
617
+ }
618
+ else if (this.preferredCamera === 'back') {
619
+ fallbackConstraints.facingMode = "environment";
620
+ }
621
+ else {
622
+ // Auto mode: use environment for mobile/tablet, user for desktop
623
+ fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
624
+ }
488
625
  }
489
626
  return fallbackConstraints;
490
627
  }
@@ -728,7 +865,7 @@ class DetectionService {
728
865
  modelLoaded = false;
729
866
  deviceStrategy;
730
867
  MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/ddmyp-v2.onnx";
731
- MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.onnx";
868
+ MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/squeezenet_new_fp32.onnx";
732
869
  MOBILENET_CLASSES_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.json";
733
870
  INPUT_SIZE = 320;
734
871
  CONFIDENCE_THRESHOLD = 0.6;
@@ -790,15 +927,27 @@ class DetectionService {
790
927
  }
791
928
  try {
792
929
  this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
793
- // Load class map
794
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
795
- if (!classResponse.ok) {
796
- throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
930
+ // Try to load class map (optional - SqueezeNet may not need it for basic classification)
931
+ try {
932
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
933
+ if (classResponse.ok) {
934
+ this.mobileNetClassMap = await classResponse.json();
935
+ this.logger.state('CLASES_MOBILENET_CARGADAS', {
936
+ classCount: Object.keys(this.mobileNetClassMap).length
937
+ });
938
+ }
939
+ }
940
+ catch (error) {
941
+ this.logger.warn('No se pudo cargar el mapa de clases de MobileNet, usando clasificación básica');
942
+ // Create a basic class map for document types
943
+ this.mobileNetClassMap = {
944
+ "0": "document",
945
+ "1": "id_card",
946
+ "2": "passport",
947
+ "3": "license",
948
+ "4": "other"
949
+ };
797
950
  }
798
- this.mobileNetClassMap = await classResponse.json();
799
- this.logger.state('CLASES_MOBILENET_CARGADAS', {
800
- classCount: Object.keys(this.mobileNetClassMap).length
801
- });
802
951
  // Load model
803
952
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
804
953
  try {
@@ -887,7 +1036,7 @@ class DetectionService {
887
1036
  try {
888
1037
  this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
889
1038
  const inputTensor = this.preprocessMobileNet(canvas);
890
- const feeds = { input: inputTensor };
1039
+ const feeds = { [this.mobileNetSession.inputNames[0]]: inputTensor };
891
1040
  const results = await this.mobileNetSession.run(feeds);
892
1041
  const output = results[Object.keys(results)[0]].data;
893
1042
  const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
@@ -897,7 +1046,8 @@ class DetectionService {
897
1046
  class: className,
898
1047
  confidence: confidence.toFixed(3),
899
1048
  classIndex: maxIdx,
900
- timestamp: Date.now()
1049
+ timestamp: Date.now(),
1050
+ results
901
1051
  });
902
1052
  return {
903
1053
  class: className,
@@ -936,7 +1086,7 @@ class DetectionService {
936
1086
  displayedVideoHeight = INPUT_SIZE / videoAspectRatio;
937
1087
  }
938
1088
  else {
939
- // Video is taller - pillarboxed in display
1089
+ // Video is taller - pillarboxed in display
940
1090
  displayedVideoHeight = INPUT_SIZE;
941
1091
  displayedVideoWidth = INPUT_SIZE * videoAspectRatio;
942
1092
  }
@@ -987,7 +1137,7 @@ class DetectionService {
987
1137
  const leftAligned = Math.abs(docLeft - maskLeft) <= tolerance;
988
1138
  return {
989
1139
  top: topAligned && leftAligned, // Esquina superior izquierda: borde top Y left alineados
990
- right: topAligned && rightAligned, // Esquina superior derecha: borde top Y right alineados
1140
+ right: topAligned && rightAligned, // Esquina superior derecha: borde top Y right alineados
991
1141
  bottom: bottomAligned && leftAligned, // Esquina inferior izquierda: borde bottom Y left alineados
992
1142
  left: bottomAligned && rightAligned // Esquina inferior derecha: borde bottom Y right alineados
993
1143
  };
@@ -1072,15 +1222,20 @@ class DetectionService {
1072
1222
  tempCanvas.width = 224;
1073
1223
  tempCanvas.height = 224;
1074
1224
  const tempCtx = tempCanvas.getContext('2d');
1225
+ // Resize image to 224x224 for SqueezeNet (using MobileNet interface)
1075
1226
  tempCtx.drawImage(canvas, 0, 0, 224, 224);
1076
1227
  const imageData = tempCtx.getImageData(0, 0, 224, 224);
1077
1228
  const data = imageData.data;
1078
1229
  const hw = 224 * 224;
1079
1230
  const arr = new Float32Array(3 * hw);
1231
+ // SqueezeNet preprocessing: normalize to [0,1] range and arrange in CHW format
1080
1232
  for (let i = 0; i < hw; i++) {
1081
- arr[i] = (data[i * 4 + 0] / 255 - 0.5) / 0.5;
1082
- arr[hw + i] = (data[i * 4 + 1] / 255 - 0.5) / 0.5;
1083
- arr[2 * hw + i] = (data[i * 4 + 2] / 255 - 0.5) / 0.5;
1233
+ // Red channel
1234
+ arr[i] = data[i * 4 + 0] / 255.0;
1235
+ // Green channel
1236
+ arr[hw + i] = data[i * 4 + 1] / 255.0;
1237
+ // Blue channel
1238
+ arr[2 * hw + i] = data[i * 4 + 2] / 255.0;
1084
1239
  }
1085
1240
  return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
1086
1241
  }
@@ -1141,7 +1296,7 @@ class ServiceContainer {
1141
1296
  }
1142
1297
  }
1143
1298
 
1144
- 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}}";
1299
+ 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}}";
1145
1300
 
1146
1301
  const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
1147
1302
  constructor() {
@@ -1154,11 +1309,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1154
1309
  get el() { return 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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1747
1912
  availableCameras: [],
1748
1913
  selectedCameraId: null,
1749
1914
  deviceType: 'desktop'};
1750
- return (h("div", { key: '7af79a0ff25b51790a9477cd7b338a0584172fc2', class: "detector-container" }, h("div", { key: 'ebf6a3811e0e599f29e98325b975a645e3bfb5e0', class: "video-container" }, 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' } }), h("div", { key: 'b507c786eccbd21acdd884ef50bc6ec280b9d437', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
1915
+ return (h("div", { key: '9d4d77042218ab3d0bc4fb1ce879db09dc3e78b4', class: "detector-container" }, h("div", { key: '51ac8ccb1275637282b038ad8364d19f14ef61b1', class: "video-container" }, 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' } }), h("div", { key: '43b71dfe3eaf20ffa66a4139e24b1af6a2074631', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
1751
1916
  position: 'absolute',
1752
1917
  left: `${box.x}px`,
1753
1918
  top: `${box.y}px`,
@@ -1756,7 +1921,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1756
1921
  border: '2px solid #32406C',
1757
1922
  pointerEvents: 'none',
1758
1923
  boxSizing: 'border-box'
1759
- } })))), this.isMaskReady && (h("div", { key: '53066c626b23daae351ea9a80409e8882434b0dd', class: "overlay-mask" }, h("div", { key: '77bb4048d43e328e57a5c7e1909e0a59c8957217', class: "card-outline" }, h("div", { key: 'ea0f2610d903f985286856ab69f8be24fd42148e', class: "side side-top" }), h("div", { key: 'dc671111ef4adf33aa41f4457fdf344ede23eb80', class: "side side-right" }), h("div", { key: '2475ec41577dca92b425542c8861c570ed7ecd3b', class: "side side-bottom" }), h("div", { key: 'a06171c8eb1bd6353a6464485604560d73ac04b7', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'ecfd98f36d457fe7282b26f6a0efef5361380c76', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: 'f3c7b66500110437e9e4a7442cb327f2fdc6d71f', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: 'b5b2a8dfc6dbcf381c32c4caa562c27ced2e3e56', class: "camera-controls" }, 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 ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '41feec90287510b08bd1090ad15586b323007c30', class: "camera-selector-dropdown" }, h("div", { key: '8310ebe3db842c463ad428b973f841053315a76f', class: "camera-selector-header" }, h("span", { key: '6693bdfa33643dfe73798d4f25b0c2d9de7aac7c' }, "Seleccionar C\u00E1mara"), h("button", { key: '8552d45e1482c570d7ff85588d4404eec7c369d7', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '5333ec0e5fb2c50a07a7c0b7d4dfb1bb4ee0df1c', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8adf70b0e632c56a4784dec44621688809a3ff56', class: "device-info" }, h("small", { key: '2ea23922386ecda4a9011b50faba5efe4b4e60bb' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '8e693db67fe1e6ae8f11082b8993682b6d19b11d', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '474489ed7848cdc163c492a5e19f5f13231e17b3', class: "flip-animation" }, h("div", { key: 'a9bd24ca21330882f001b7811077630eb1c30f50', class: "id-card-icon" }), h("div", { key: 'ffadb660a61e0f5e3e29bdc473d987e87dc4ad8a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'd2522ee33aa159d49f040a293775892fb1cb81b5', class: "success-animation" }, h("div", { key: 'e7f899fac58bc9a3d51cf2cb89ee33bb320b241f', class: "check-icon" }), h("div", { key: 'd5d0ee1f37dbc659ca625ad03cbce325be0cf0a6', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '425d347666032c3982a660ebee13b47670b996cb', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '4d396fa107872f9a1e526fd59dc4edf279a05792', class: "status-spinner" })), h("div", { key: '232e9ae24c8cf5ef8ac0e3442e45a920799c724d', class: "status-content" }, h("div", { key: 'fe9dc0ca6d8e8276052bac7d2d44f1c7c9730364', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '6910c7ac78fd89e6fa1165446ec6d46fb356035c', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '7282b8afaf03da53b01a23d04babd772528c3c14', class: "performance-monitor" }, h("div", { key: 'b582b3124fed13387cd70a02a34ab2bb541f3bfc', class: "performance-expanded" }, h("div", { key: '764bac2d61224b9a5f306f50934d410e1bd27787', class: "metrics-row" }, h("div", { key: '261563f8314a9f6069b1ac6362eb9c38936a9dc7', class: "metric-compact" }, h("span", { key: '4feea98eff86620c1bc90eb65d9799102423109d', class: "metric-label" }, "FPS"), h("span", { key: 'eb15f13b27b8aa50701ad3d11c4cf87200010b67', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '0813c35c2c33286f2276bfb5adce59b32c7bb597', class: "metric-compact" }, h("span", { key: '201e7bcb556e63b141e42a3ffc3419eb4284bfa8', class: "metric-label" }, "MEM"), h("span", { key: '4f6703cbe0128440d0f2f22f631035ab85422c64', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '6343c361c93138b9ea66e8ae8acd361b93333f8c', class: "metrics-row" }, h("div", { key: 'd3c81fe597004bf380fae2d32f3a8314317e7769', class: "metric-compact" }, h("span", { key: 'fce572af3e200a611974dceef4da02f6aecedffc', class: "metric-label" }, "INF"), h("span", { key: '18dd78d54dcecfb70ddf59b782f87f8c6f208b0f', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '4c31ebfd835b017fd3e41b23d6bd9b19add511aa', class: "metric-compact" }, h("span", { key: '66b1a380b9479a8fc1e8c2385cb72aa4158dc74e', class: "metric-label" }, "FRAME"), h("span", { key: '3c6e41f7f11123c524d3a9df409345929d63f352', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '3b522e2cdfe4bd9f1dd6eb0f91ee3577dc43e490', class: "metrics-row" }, h("div", { key: 'fa1e6f687b43aa2582b8b3426feb03a16c6493fc', class: "metric-compact" }, h("span", { key: 'a9355091e71a33a6a0d7cc6c11171dcda2e51b22', class: "metric-label" }, "DET"), h("span", { key: 'beca5a7ccf4853675cf8366bcf577ebfbd46c4f0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '4b07fc9b3d35f99bc6e77c8726192cde0476a787', class: "metric-compact" }, h("span", { key: 'a1674b538f45328ee0afb00e582c07e0b4ccf553', class: "metric-label" }, "RATE"), h("span", { key: 'fd384a5137bc3df6c939d3d698a3ef80e9087030', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'ebbaf15a0ad96e4da5bbf746c32d7a4abb51b973', class: "watermark" }, h("img", { key: '8e7d274b529d1f7020ec4a7225579792763f871a', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1924
+ } })))), this.isMaskReady && (h("div", { key: 'c2596f8cabb227e3ea3b082495f52b141cb1690f', class: "overlay-mask" }, h("div", { key: 'ba1ea39b5df1cd312a9bd9410b7225a29cfb8a29', class: "card-outline" }, h("div", { key: '6ce4d4d969d5f3fbcf75a4eb285f61d434c5633b', class: "side side-top" }), h("div", { key: '36653ecc4fcda25dfc84bc4d1f4c4c5dfb71ced7', class: "side side-right" }), h("div", { key: '8b108fa8fb370b229a0d1a77cb1ff044319a8f75', class: "side side-bottom" }), h("div", { key: 'a7aa3b45c065a50445aafd5c3ccefb57b0c9c769', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'b94b92273e12a019238e11ae57f21e140f13c3f1', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '144c3741003ec0cc148bb6ce92012d7b3cd110c8', class: "skip-section" }, 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"), 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 && (h("div", { key: 'bd45628707e0169317ed3dd4247e1056d7046104', class: "camera-controls" }, 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 ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '40ada780a8ea13aeeaee9cb39bc6496f69f2679d', class: "camera-selector-dropdown" }, h("div", { key: '3a920bb02ba024f0359513f10544bf0528ecee6d', class: "camera-selector-header" }, h("span", { key: 'e5efd478a602b13821ba53cb9a7a59d1b4a71178' }, "Seleccionar C\u00E1mara"), h("button", { key: '2df6cc11e162be74862029e33815927e3fa8f2f8', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '06934642dff134c9e1a6872f935a5892a9c7b835', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8d5ede287c2efb0ebe418bee8dc7fb310443c991', class: "device-info" }, h("small", { key: 'b7dd10565113694b09f01ab28ffa29262ae1d2bc' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2f9e549e6c0c56d4db5835e58361fb17bba6cf90', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'a6ef0ed127f23e9d764896ed743ca8d8454851a6', class: "flip-animation" }, h("div", { key: '0b04411f88a634bb3243b00ca2d5a45fd6962dea', class: "id-card-icon" }), h("div", { key: '9327be58879b407cea87f455989406e58302e9d1', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'bd268578b5003e99256e3340b4a9a6fb7656be64', class: "success-animation" }, h("div", { key: '064e0a5b9851de14bc6829f028e7ea7161fd1633', class: "check-icon" }), h("div", { key: 'bdcd94a8806399e396e218a359c10b11d05f38d6', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '3d9af617376c16e44e065873919c3d90ff46110a', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'be7b56decfd6f12dcca46b2a9456480e3d5ac00e', class: "status-spinner" })), h("div", { key: '5fe9850883cb749cebaa18c3e9c13ca21325e373', class: "status-content" }, h("div", { key: '43fdb50691e4b98e56126af1a129f625061128bf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'b76cbfa4a0349421fcd535d3fd8bf40c89ca3aef', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'eddefb464af0487ab9ab2a0f76dccbb6a2092e51', class: "performance-monitor" }, h("div", { key: '7b7d93b56cb4cd4fbb1bdde89c6150b282fa805e', class: "performance-expanded" }, h("div", { key: 'f302e8b9d985fb8cad53c4c4e0d37a3bf5a63261', class: "metrics-row" }, h("div", { key: 'e8d8f2420ee8643a7f454d38065c75b5be598f77', class: "metric-compact" }, h("span", { key: '6e52eb5bb06e0df57e61774de38c31349f45e22b', class: "metric-label" }, "FPS"), h("span", { key: '6636f9cea20c49b49c17db86188e2f6b214c45c3', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'afa1d9dec05b14256a1b1fcec8638b8c21a56e37', class: "metric-compact" }, h("span", { key: '9c55e30f5d01582648bacc1fb38ca1990485a591', class: "metric-label" }, "MEM"), h("span", { key: 'ca263601e8715027f95ffb2d5413a643dc570cf2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '03caf19961fd01b7781484d49d8121b1e78c161f', class: "metrics-row" }, h("div", { key: 'bffb08f01f703411f7c8605e4f8d95b1c37b5c0f', class: "metric-compact" }, h("span", { key: '9b92279b3253cc669412733facb3751f4344d69f', class: "metric-label" }, "INF"), h("span", { key: '3234d4150e2e1479e3b552767af8123e69911d6c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '77082d2669db53f19fb50b3bbfa1ebefb0b86ff4', class: "metric-compact" }, h("span", { key: 'cc6ef96c72c800ba36dab2a388f1de863b2dacd5', class: "metric-label" }, "FRAME"), h("span", { key: 'e2bb4b7351869a76e810f8bbf20d805154495e35', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '5700d7ef839fa77686299d79445203090dc8079f', class: "metrics-row" }, h("div", { key: 'a2999a035fccb7f250c01b084075490678513c87', class: "metric-compact" }, h("span", { key: '3677d58665996fa5969507dbaff9e956b2ea54ee', class: "metric-label" }, "DET"), h("span", { key: 'c584312438b339c82a3bdc33f00fb985ff2a6f55', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '58866219f213bf8607083f30be2d5ca5d0515cea', class: "metric-compact" }, h("span", { key: 'd90b450051331347f219b6c0f197536d0dcee9b7', class: "metric-label" }, "RATE"), h("span", { key: 'f6e6cd7948e06014e5a0416e4e81394fa36ec61d', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'c1d0194f181a0082cc1aa5452de382908da61b65', class: "watermark" }, 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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
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
@@ -2137,6 +2337,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2137
2337
  "useDocumentClassification": [4, "use-document-classification"],
2138
2338
  "preferredCamera": [1, "preferred-camera"],
2139
2339
  "captureDelay": [2, "capture-delay"],
2340
+ "enableBackDocumentTimer": [4, "enable-back-document-timer"],
2341
+ "backDocumentTimerDuration": [2, "back-document-timer-duration"],
2140
2342
  "detectionBoxes": [32],
2141
2343
  "sideAlignment": [32],
2142
2344
  "isMaskReady": [32],
@@ -2146,6 +2348,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2146
2348
  "hasDocumentDetected": [32],
2147
2349
  "currentStatus": [32],
2148
2350
  "performanceData": [32],
2351
+ "backDocumentTimerRemaining": [32],
2149
2352
  "getCapturedImages": [64],
2150
2353
  "isProcessCompleted": [64],
2151
2354
  "startCapture": [64],