@srsergio/taptapp-ar 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # @srsergio/taptapp-ar
2
2
 
3
- 🚀 **TapTapp AR** is a high-performance Augmented Reality (AR) toolkit specifically designed for **Astro** and **Node.js** environments. It provides a seamless way to integrate image tracking, video overlays, and an offline compiler for image targets.
3
+ 🚀 **TapTapp AR** is a high-performance Augmented Reality (AR) compiler toolkit for **Node.js** and **Browser** environments. It provides an ultra-fast offline compiler for image targets.
4
4
 
5
- Built on top of **MindAR** and **A-Frame**, this package features a **pure JavaScript offline compiler** that requires **no TensorFlow** for backend compilation, while still supporting TensorFlow.js for real-time tracking in the browser.
5
+ Built with performance in mind, this package features a **pure JavaScript offline compiler** that requires **no TensorFlow** for compilation, generating high-quality `.mind` files in record time.
6
6
 
7
7
  ---
8
8
 
9
9
  ## 🌟 Key Features
10
10
 
11
- - 🚀 **Astro Native**: Optimized components for Astro's Islands architecture.
12
11
  - 🖼️ **Ultra-Fast Offline Compiler**: Pure JavaScript compiler that generates `.mind` target files in **~1.3s per image**.
13
12
  - ⚡ **Zero TensorFlow for Compilation**: The offline compiler uses optimized pure JS algorithms - no TensorFlow installation required.
14
13
  - 🧵 **Multi-threaded Engine**: Truly parallel processing using Node.js `worker_threads` for bulk image compilation.
15
14
  - 🚀 **Serverless Ready**: Lightweight compiler with minimal dependencies, perfect for Vercel, AWS Lambda, and Netlify.
15
+ - 📦 **Protocol V3 (Columnar Binary)**: Industry-leading performance with zero-copy loading and 80%+ smaller files.
16
16
 
17
17
  ---
18
18
 
@@ -26,34 +26,6 @@ npm install @srsergio/taptapp-ar
26
26
 
27
27
  > **Note:** TensorFlow is **NOT required** for the offline compiler. It only uses pure JavaScript.
28
28
 
29
- For real-time AR tracking in the browser, TensorFlow.js is loaded automatically via CDN.
30
-
31
- ---
32
-
33
- ## 🚀 Astro Integration Guide
34
-
35
- The easiest way to display AR content is using the `ARVideoTrigger` component.
36
-
37
- ### Usage
38
-
39
- ```astro
40
- ---
41
- import ARVideoTrigger from '@srsergio/taptapp-ar/astro/ARVideoTrigger.astro';
42
-
43
- const config = {
44
- cardId: 'unique-id',
45
- targetImageSrc: 'https://cdn.example.com/target.jpg',
46
- targetMindSrc: 'https://cdn.example.com/targets.mind',
47
- videoSrc: 'https://cdn.example.com/overlay.mp4',
48
- videoWidth: 1280,
49
- videoHeight: 720,
50
- scale: 1.2,
51
- };
52
- ---
53
-
54
- <ARVideoTrigger config={config} />
55
- ```
56
-
57
29
  ---
58
30
 
59
31
  ## 🖼️ High-Performance Compiler (Protocol V3)
@@ -83,7 +55,7 @@ TaptApp AR features the industry's most advanced **pure JavaScript** offline com
83
55
  Optimized for server-side compilation with multi-core parallelism:
84
56
 
85
57
  ```javascript
86
- import { OfflineCompiler } from '@srsergio/taptapp-ar/compiler/offline-compiler.js';
58
+ import { OfflineCompiler } from '@srsergio/taptapp-ar';
87
59
 
88
60
  const compiler = new OfflineCompiler();
89
61
 
@@ -100,22 +72,12 @@ const binaryBuffer = compiler.exportData(); // Yields a much smaller .mind file
100
72
  ### 🌐 Frontend (Zero-Latency Loading)
101
73
 
102
74
  ```javascript
103
- import { OfflineCompiler } from '@srsergio/taptapp-ar/compiler/offline-compiler.js';
75
+ import { OfflineCompiler } from '@srsergio/taptapp-ar';
104
76
 
105
77
  const compiler = new OfflineCompiler();
106
78
  // Loading 127KB instead of 800KB saves bandwidth and CPU parsing time
107
79
  compiler.importData(binaryBuffer);
108
80
  ```
109
- ---
110
-
111
- ## ❓ Troubleshooting
112
-
113
- | Issue | Solution |
114
- | :--- | :--- |
115
- | **Camera not starting** | Ensure your site is served via `HTTPS`. Browsers block camera access on insecure origins. |
116
- | **Video not playing** | iOS Safari requires `muted` and `playsinline` attributes for autoplaying videos. Our components handle this by default. |
117
- | **CORS errors** | Ensure that `targetImageSrc`, `targetMindSrc`, and `videoSrc` have CORS headers enabled (`Access-Control-Allow-Origin: *`). |
118
- | **Memory Outage on Serverless** | Reduce the resolution of your target images. High-res images increase memory pressure during compilation. |
119
81
 
120
82
  ---
121
83
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.7",
4
- "description": "AR Visualizer and Compiler for Astro and React",
3
+ "version": "1.0.8",
4
+ "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/srsergiolazaro/taptapp-ar.git"
@@ -19,7 +19,6 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "import": "./dist/index.js"
21
21
  },
22
- "./astro/*": "./src/astro/*",
23
22
  "./compiler/*": "./src/compiler/*"
24
23
  },
25
24
  "files": [
@@ -34,7 +33,6 @@
34
33
  },
35
34
  "peerDependencies": {
36
35
  "aframe": ">=1.5.0",
37
- "astro": ">=4.0.0",
38
36
  "react": ">=18.0.0",
39
37
  "react-dom": ">=18.0.0",
40
38
  "three": ">=0.160.0"
@@ -48,7 +46,6 @@
48
46
  "@types/react": "^18.3.3",
49
47
  "@types/react-dom": "^18.3.0",
50
48
  "@types/three": "^0.170.0",
51
- "astro": "5.16.4",
52
49
  "jimp": "^1.6.0",
53
50
  "typescript": "^5.4.5",
54
51
  "vitest": "^4.0.16"
@@ -1,59 +0,0 @@
1
- ---
2
- import type { PropsConfig } from "../react/types";
3
- interface Props {
4
- config: PropsConfig;
5
- }
6
-
7
- const { config } = Astro.props;
8
- ---
9
-
10
- <a-scene
11
- mindar-image={`imageTargetSrc: ${config.targetMindSrc};uiLoading: #loading-overlay;uiScanning: #scanning-overlay;uiError: #error-overlay`}
12
- color-space="sRGB"
13
- renderer="colorManagement: true, physicallyCorrectLights"
14
- vr-mode-ui="enabled: false"
15
- device-orientation-permission-ui="enabled: false"
16
- >
17
- <a-assets>
18
- <img id="target-image" crossorigin="anonymous" src={config.targetImageSrc} />
19
- <video
20
- id="ar-video"
21
- src={config.videoSrc}
22
- preload="auto"
23
- loop
24
- autoplay
25
- playsinline
26
- webkit-playsinline
27
- muted
28
- crossorigin="anonymous"
29
- class="absolute opacity-0"
30
- width="1"
31
- height="1"></video></a-assets
32
- >
33
- <a-camera position="0 0 0" look-controls="enabled: false"></a-camera>
34
-
35
- <a-entity mindar-image-target="targetIndex: 0">
36
- <a-video
37
- id="main-video"
38
- src="#ar-video"
39
- position="0 0 0"
40
- opacity="1"
41
- width={(config.videoWidth / config.videoHeight) * config.scale}
42
- height={1 * config.scale}
43
- rotation="0 0 0"
44
- visible="true"
45
- play-on-click
46
- autoplay></a-video>
47
- </a-entity>
48
- </a-scene>
49
-
50
- <style>
51
- video {
52
- width: 100% !important;
53
- height: 100% !important;
54
- object-fit: cover !important;
55
- position: absolute !important;
56
- top: 0 !important;
57
- left: 0 !important;
58
- }
59
- </style>
@@ -1,73 +0,0 @@
1
- ---
2
- import ARStyles from "./styles/ARStyles.astro";
3
- import LoadingOverlay from "./overlays/LoadingOverlay.astro";
4
- import ScanningOverlay from "./overlays/ScanningOverlay.astro";
5
- import ErrorOverlay from "./overlays/ErrorOverlay.astro";
6
- import ARScene from "./ARScene.astro";
7
- import ARScripts from "./scripts/ARScripts.astro";
8
- import type { PropsConfig } from "../react/types";
9
- interface Props {
10
- config: PropsConfig;
11
- }
12
-
13
- const {config} = Astro.props ;
14
- ---
15
-
16
- <!doctype html>
17
- <html lang="es-PE">
18
- <head>
19
- <meta charset="UTF-8" />
20
- <meta name="viewport" content="width=device-width, initial-scale=1" />
21
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
22
- <meta name="robots" content="index, follow" />
23
- <link rel="canonical" href="https://go.taptapp.xyz" />
24
-
25
- <meta name="apple-mobile-web-app-capable" content="yes" />
26
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any" />
27
- <meta name="theme-color" content="#3367D6" />
28
- <meta
29
- name="description"
30
- content="Crea impacto duradero y genera confianza en tus clientes con la tarjeta digital TapTapp. Presenta tu información de manera clara y atractiva para conectar con el futuro."
31
- />
32
- <meta name="keywords" content="tarjeta digital, identidad, futuro, confianza, clientes" />
33
- <meta name="author" content="@srsergiolazaro" />
34
-
35
- <meta property="og:title" content="TapTapp AR Experience" />
36
- <meta
37
- property="og:description"
38
- content="Descubre nuestra experiencia de realidad aumentada y da vida a tu tarjeta digital TapTapp."
39
- />
40
- <meta property="og:image" content="https://go.taptapp.xyz/Imgsocial.jpg" />
41
- <meta property="og:url" content="https://go.taptapp.xyz" />
42
- <link rel="manifest" href="/manifest.webmanifest" crossorigin="anonymous" />
43
-
44
- <title>TapTapp AR Experience</title>
45
- <script is:inline src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
46
- <script is:inline src="https://r2-worker.sergiolazaromondargo.workers.dev/taptapp-ar.prod.js"
47
- ></script>
48
- <link
49
- href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
50
- rel="stylesheet"
51
- />
52
- <ARStyles />
53
- <ARScripts />
54
- <style>
55
- body {
56
- font-family: "Montserrat", sans-serif;
57
- margin: 0;
58
- overflow: hidden;
59
- }
60
- @view-transition {
61
- navigation: auto;
62
- }
63
- </style>
64
- </head>
65
- <body>
66
- <!-- Custom UI Overlays -->
67
- <LoadingOverlay />
68
- <ScanningOverlay targetImage={config.targetImageSrc} />
69
- <ErrorOverlay />
70
-
71
- <ARScene config={config} />
72
- </body>
73
- </html>
@@ -1,40 +0,0 @@
1
- <div id="error-overlay" class="custom-overlay hidden bg-gradient-to-br from-red-700 to-purple-700">
2
- <div class="mb-5 text-5xl">⚠️</div>
3
- <div class="font-semibold tracking-wide text-white/95 text-xl">No se pudo iniciar</div>
4
- <div class="mt-3 max-w-[80%] text-center font-light text-white/75 text-sm">
5
- Verifique los permisos de cámara o intente con otro dispositivo
6
- </div>
7
- <button
8
- onclick="location.reload()"
9
- class="mt-6 rounded-lg bg-blue-500 px-6 py-2.5 font-semibold text-white transition-all hover:bg-blue-600 hover:shadow-lg"
10
- >
11
- Reintentar
12
- </button>
13
- </div>
14
-
15
- <style>
16
- #error-overlay {
17
- background: linear-gradient(135deg, #c0392b, #8e44ad);
18
- }
19
-
20
- .error-icon {
21
- font-size: 50px;
22
- margin-bottom: 20px;
23
- }
24
-
25
- .retry-button {
26
- margin-top: 20px;
27
- padding: 10px 20px;
28
- background-color: #3498db;
29
- color: white;
30
- border: none;
31
- border-radius: 5px;
32
- font-weight: 600;
33
- cursor: pointer;
34
- transition: background-color 0.3s ease;
35
- }
36
-
37
- .retry-button:hover {
38
- background-color: #2980b9;
39
- }
40
- </style>
@@ -1,28 +0,0 @@
1
- <div id="loading-overlay" class="custom-overlay bg-gradient-to-br from-gray-800 to-gray-900">
2
- <div class="loader"></div>
3
- <div class="mt-5 font-semibold tracking-wide text-white/95 text-xl">Preparando...</div>
4
- <div class="mt-3 max-w-[80%] text-center font-light text-white/75 text-sm">
5
- Un momento mientras optimizamos su experiencia
6
- </div>
7
- </div>
8
-
9
- <style>
10
- .loader {
11
- width: 60px;
12
- height: 60px;
13
- border: 5px solid rgba(255, 255, 255, 0.2);
14
- border-top: 5px solid #3498db;
15
- border-radius: 50%;
16
- animation: spin 1s linear infinite;
17
- margin-bottom: 20px;
18
- }
19
-
20
- @keyframes spin {
21
- 0% {
22
- transform: rotate(0deg);
23
- }
24
- 100% {
25
- transform: rotate(360deg);
26
- }
27
- }
28
- </style>
@@ -1,119 +0,0 @@
1
- ---
2
- interface Props {
3
- targetImage?: string;
4
- }
5
-
6
- const { targetImage } = Astro.props;
7
- ---
8
-
9
- <div
10
- id="scanning-overlay"
11
- class="custom-overlay relative flex flex-col items-center justify-center"
12
- >
13
- <div class="scan-region relative mb-8 h-[300px] w-[300px] overflow-hidden">
14
- <!-- Esquinas del recuadro de escaneo -->
15
- <div class="corner top-left"></div>
16
- <div class="corner top-right"></div>
17
- <div class="corner bottom-left"></div>
18
- <div class="corner bottom-right"></div>
19
-
20
- <div class="absolute inset-0 p-6">
21
- <img id="target-preview" src={targetImage} class="h-full w-full object-contain opacity-90" />
22
- </div>
23
-
24
- <div class="scan-animation"></div>
25
- </div>
26
-
27
- <div class="font-semibold tracking-wide text-white/95 text-2xl">Escaneando...</div>
28
- <div class="mt-3 max-w-[80%] text-center font-light text-white/75 text-sm">
29
- Centre el sticker dentro del marco
30
- </div>
31
- </div>
32
-
33
- <style>
34
- .scan-region img {
35
- width: 100%;
36
- height: 100%;
37
- object-fit: contain;
38
- opacity: 0.7;
39
- }
40
-
41
- .scan-animation {
42
- position: absolute;
43
- top: 0;
44
- left: 0;
45
- width: 100%;
46
- height: 2px;
47
- background: linear-gradient(
48
- 90deg,
49
- rgba(52, 152, 219, 0),
50
- rgba(52, 152, 219, 0.8),
51
- rgba(155, 89, 182, 0.8),
52
- rgba(52, 152, 219, 0)
53
- );
54
- box-shadow:
55
- 0 0 10px rgba(52, 152, 219, 0.7),
56
- 0 0 20px rgba(155, 89, 182, 0.4);
57
- animation: scan 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
58
- }
59
-
60
- @keyframes scan {
61
- 0% {
62
- transform: translateY(0);
63
- opacity: 0.8;
64
- }
65
- 15% {
66
- opacity: 1;
67
- }
68
- 50% {
69
- transform: translateY(298px);
70
- opacity: 1;
71
- }
72
- 85% {
73
- opacity: 0.8;
74
- }
75
- 100% {
76
- transform: translateY(0);
77
- opacity: 0.8;
78
- }
79
- }
80
-
81
- /* Estilos para las esquinas */
82
- .corner {
83
- position: absolute;
84
- width: 24px;
85
- height: 24px;
86
- border-color: white;
87
- border-style: solid;
88
- opacity: 0.7;
89
- z-index: 1;
90
- }
91
-
92
- .top-left {
93
- top: 6px;
94
- left: 6px;
95
- border-width: 2px 0 0 2px;
96
- border-top-left-radius: 6px;
97
- }
98
-
99
- .top-right {
100
- top: 6px;
101
- right: 6px;
102
- border-width: 2px 2px 0 0;
103
- border-top-right-radius: 6px;
104
- }
105
-
106
- .bottom-left {
107
- bottom: 6px;
108
- left: 6px;
109
- border-width: 0 0 2px 2px;
110
- border-bottom-left-radius: 6px;
111
- }
112
-
113
- .bottom-right {
114
- bottom: 6px;
115
- right: 6px;
116
- border-width: 0 2px 2px 0;
117
- border-bottom-right-radius: 6px;
118
- }
119
- </style>
@@ -1,118 +0,0 @@
1
- <script is:inline>
2
- // Lógica mejorada para reproducción de video
3
- let started = false;
4
- let playing = false;
5
-
6
- window.addEventListener("click", async function () {
7
- try {
8
- let video = document.querySelector("#ar-video");
9
- let avideo = document.querySelector("a-video");
10
-
11
- if (!video || !avideo) {
12
- console.error("Elementos de video no encontrados");
13
- return;
14
- }
15
-
16
- if (!started) {
17
- await video.play();
18
- avideo.setAttribute("opacity", 1);
19
- started = true;
20
- playing = true;
21
- } else {
22
- if (playing) {
23
- video.pause();
24
- playing = false;
25
- } else {
26
- await video.play();
27
- playing = true;
28
- }
29
- }
30
- } catch (error) {
31
- console.error("Error en la reproducción del video:", error);
32
- }
33
- });
34
- </script>
35
-
36
- <script is:inline>
37
- document.addEventListener("DOMContentLoaded", function () {
38
- const scene = document.querySelector("a-scene");
39
- const errorOverlay = document.getElementById("error-overlay");
40
- const scanningOverlay = document.getElementById("scanning-overlay");
41
- const videoEl = document.querySelector("#ar-video");
42
- const mainVideo = document.querySelector("#main-video");
43
- const targetImage = document.querySelector("#target-image");
44
-
45
- if (!scene || !errorOverlay || !scanningOverlay || !videoEl || !mainVideo || !targetImage) {
46
- console.error("Elementos críticos no encontrados");
47
- if (errorOverlay) errorOverlay.classList.remove("hidden");
48
- return;
49
- }
50
-
51
- // Verificación de recursos
52
- targetImage.addEventListener("error", () => {
53
- console.error("Error al cargar la imagen objetivo");
54
- errorOverlay.classList.remove("hidden");
55
- });
56
-
57
- videoEl.addEventListener("error", () => {
58
- console.error("Error al cargar el video");
59
- errorOverlay.classList.remove("hidden");
60
- });
61
-
62
- // Gestión mejorada de errores AR
63
- scene.addEventListener("arError", (event) => {
64
- console.error("Error AR:", event);
65
- errorOverlay.classList.remove("hidden");
66
- });
67
-
68
- // Verificar si MindAR está disponible
69
- if (!window.MINDAR || !window.MINDAR.IMAGE) {
70
- console.error("MindAR no está disponible");
71
- errorOverlay.classList.remove("hidden");
72
- return;
73
- }
74
-
75
- // Manejo de eventos de target
76
- const targetEntity = document.querySelector("a-entity[mindar-image-target]");
77
-
78
- targetEntity.addEventListener("targetFound", () => {
79
- console.log("¡Objetivo encontrado!");
80
- scanningOverlay.classList.add("hidden");
81
-
82
- // Hacer visible el video
83
- mainVideo.setAttribute("opacity", "1");
84
-
85
- // Intentar reproducir
86
- videoEl.play().catch((err) => {
87
- console.error("Error reproduciendo video:", err);
88
- });
89
- });
90
-
91
- targetEntity.addEventListener("targetLost", () => {
92
- console.log("Objetivo perdido");
93
- scanningOverlay.classList.remove("hidden");
94
- mainVideo.setAttribute("opacity", "0");
95
- });
96
-
97
- // Función para aplicar estilos adaptativos
98
- function applyVideoStyles() {
99
- // Estilos del elemento video HTML
100
- if (videoEl) {
101
- videoEl.style.width = "100%";
102
- videoEl.style.height = "100%";
103
- videoEl.style.objectFit = "contain";
104
- }
105
- }
106
-
107
- // Aplicar estilos al inicio y cuando cambie la orientación
108
- applyVideoStyles();
109
- window.addEventListener("resize", applyVideoStyles);
110
-
111
- // Manejar toques para dispositivos móviles
112
- document.addEventListener("touchstart", () => {
113
- if (videoEl.paused && mainVideo.getAttribute("opacity") === "1") {
114
- videoEl.play().catch((e) => console.log("Error en touchstart:", e));
115
- }
116
- });
117
- });
118
- </script>
@@ -1,147 +0,0 @@
1
- <style is:global>
2
- /* Estilos globales básicos de A-Frame */
3
- .a-enter-vr,
4
- .a-enter-vr-button {
5
- display: none !important;
6
- }
7
-
8
- body,
9
- a-scene {
10
- background-color: transparent !important;
11
- font-family: "Montserrat", sans-serif;
12
- }
13
-
14
- .a-canvas {
15
- background-color: transparent !important;
16
- width: 100% !important;
17
- height: 100% !important;
18
- position: absolute !important;
19
- top: 0 !important;
20
- left: 0 !important;
21
- right: 0 !important;
22
- bottom: 0 !important;
23
- }
24
-
25
- /* Clase base compartida para overlays */
26
- .custom-overlay {
27
- position: fixed;
28
- top: 0;
29
- left: 0;
30
- width: 100%;
31
- height: 100%;
32
- display: flex;
33
- flex-direction: column;
34
- justify-content: center;
35
- align-items: center;
36
- z-index: 999;
37
- color: white;
38
- text-align: center;
39
- transition: opacity 0.5s ease;
40
- }
41
-
42
- .custom-overlay.hidden {
43
- display: none;
44
- }
45
-
46
- /* Texto compartido */
47
- .status-text {
48
- font-size: 18px;
49
- font-weight: 600;
50
- margin-top: 20px;
51
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
52
- }
53
-
54
- .instruction-text {
55
- font-size: 14px;
56
- max-width: 80%;
57
- margin-top: 10px;
58
- opacity: 0.8;
59
- }
60
-
61
- /* Estilos para las pantallas de UI personalizadas */
62
- .custom-overlay {
63
- position: fixed;
64
- top: 0;
65
- left: 0;
66
- width: 100%;
67
- height: 100%;
68
- display: flex;
69
- flex-direction: column;
70
- justify-content: center;
71
- align-items: center;
72
- z-index: 999;
73
- background-color: rgba(0, 0, 0, 0);
74
- color: white;
75
- text-align: center;
76
- transition: opacity 0.5s ease;
77
- }
78
-
79
- .custom-overlay.hidden {
80
- display: none;
81
- }
82
-
83
- /* Pantalla de carga */
84
- #loading-overlay {
85
- background: linear-gradient(135deg, #2c3e50, #1a1a2e);
86
- }
87
-
88
- .loader {
89
- width: 60px;
90
- height: 60px;
91
- border: 5px solid rgba(255, 255, 255, 0.2);
92
- border-top: 5px solid #3498db;
93
- border-radius: 50%;
94
- animation: spin 1s linear infinite;
95
- margin-bottom: 20px;
96
- }
97
-
98
- @keyframes spin {
99
- 0% {
100
- transform: rotate(0deg);
101
- }
102
- 100% {
103
- transform: rotate(360deg);
104
- }
105
- }
106
-
107
- /* Pantalla de error */
108
- #error-overlay {
109
- background: linear-gradient(135deg, #c0392b, #8e44ad);
110
- }
111
-
112
- .error-icon {
113
- font-size: 50px;
114
- margin-bottom: 20px;
115
- }
116
-
117
- /* Pantalla de escaneo */
118
- #scanning-overlay {
119
- background-color: rgba(0, 0, 0, 0);
120
- }
121
-
122
- /* Botón para reintentar */
123
- .retry-button {
124
- margin-top: 20px;
125
- padding: 10px 20px;
126
- background-color: #3498db;
127
- color: white;
128
- border: none;
129
- border-radius: 5px;
130
- font-weight: 600;
131
- cursor: pointer;
132
- transition: background-color 0.3s ease;
133
- }
134
-
135
- .retry-button:hover {
136
- background-color: #2980b9;
137
- }
138
-
139
- video {
140
- width: 100% !important;
141
- height: 100% !important;
142
- object-fit: cover !important;
143
- position: absolute !important;
144
- top: 0 !important;
145
- left: 0 !important;
146
- }
147
- </style>