@beltec/face-gate 0.0.0-stage → 0.1.0
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 +217 -2
- package/dist/index.cjs +648 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +254 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +616 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +177 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +223 -0
- package/dist/server.d.ts +223 -0
- package/dist/server.js +147 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +372 -0
- package/package.json +59 -4
package/dist/styles.css
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/* @beltec/face-gate — estilos do <FaceGate>. Importe uma vez: import "@beltec/face-gate/styles.css".
|
|
2
|
+
Personalize pelas variáveis abaixo (no .fg-root ou num ancestral). Tudo com prefixo .fg- para não
|
|
3
|
+
colidir com o CSS do seu sistema. */
|
|
4
|
+
.fg-root {
|
|
5
|
+
--fg-accent: #c9a43e;
|
|
6
|
+
--fg-accent-text: #1d2125;
|
|
7
|
+
--fg-surface: #1d2125;
|
|
8
|
+
--fg-on-surface: #ffffff;
|
|
9
|
+
--fg-muted: #9aa1a8;
|
|
10
|
+
--fg-ok: #4ade80;
|
|
11
|
+
--fg-warn: #f87171;
|
|
12
|
+
--fg-radius: 12px;
|
|
13
|
+
--fg-font: inherit;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 12px;
|
|
17
|
+
font-family: var(--fg-font);
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.fg-stage {
|
|
22
|
+
position: relative;
|
|
23
|
+
aspect-ratio: 4 / 3;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
border-radius: var(--fg-radius);
|
|
26
|
+
background: var(--fg-surface);
|
|
27
|
+
color: var(--fg-on-surface);
|
|
28
|
+
}
|
|
29
|
+
.fg-video {
|
|
30
|
+
position: absolute;
|
|
31
|
+
inset: 0;
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
object-fit: cover;
|
|
35
|
+
}
|
|
36
|
+
.fg-video[hidden] {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Estado inicial */
|
|
41
|
+
.fg-idle {
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 0;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
gap: 6px;
|
|
49
|
+
padding: 24px;
|
|
50
|
+
text-align: center;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
}
|
|
53
|
+
.fg-idle span {
|
|
54
|
+
color: var(--fg-muted);
|
|
55
|
+
}
|
|
56
|
+
.fg-idle-emblem {
|
|
57
|
+
height: 56px;
|
|
58
|
+
width: auto;
|
|
59
|
+
margin-bottom: 6px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Marca d'água: mesma posição em que a abertura termina */
|
|
63
|
+
.fg-watermark {
|
|
64
|
+
position: absolute;
|
|
65
|
+
top: 3.5%;
|
|
66
|
+
right: 3%;
|
|
67
|
+
z-index: 10;
|
|
68
|
+
height: 10%;
|
|
69
|
+
width: auto;
|
|
70
|
+
opacity: 0.55;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Abertura: o escudo se desenha (faixa, linha, pontos, lente) e vai para o canto da marca d'água */
|
|
75
|
+
.fg-intro {
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset: 0;
|
|
78
|
+
z-index: 20;
|
|
79
|
+
display: grid;
|
|
80
|
+
place-items: center;
|
|
81
|
+
background: radial-gradient(60% 55% at 50% 46%, #2c3136 0%, #22262a 55%, #181b1e 100%);
|
|
82
|
+
animation: fg-intro-out 0.3s ease-in 3.1s forwards;
|
|
83
|
+
}
|
|
84
|
+
.fg-intro-emblem {
|
|
85
|
+
height: 42%;
|
|
86
|
+
width: auto;
|
|
87
|
+
animation: fg-intro-carry 0.65s cubic-bezier(0.65, 0, 0.35, 1) 2.45s forwards;
|
|
88
|
+
}
|
|
89
|
+
/* Escudo centralizado com 42% da altura do palco (4:3) → marca d'água (altura 10%, topo 3,5%, direita 3%):
|
|
90
|
+
escala 10/42; deslocamento em % da própria caixa (largura = 0,36·H, altura = 0,42·H). */
|
|
91
|
+
@keyframes fg-intro-carry {
|
|
92
|
+
to {
|
|
93
|
+
transform: translate(162.2%, -98.8%) scale(0.238);
|
|
94
|
+
opacity: 0.55;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
@keyframes fg-intro-out {
|
|
98
|
+
to {
|
|
99
|
+
opacity: 0;
|
|
100
|
+
visibility: hidden;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
.fg-emblem-animated .fg-emblem-shield {
|
|
104
|
+
animation: fg-fade 0.5s ease-out 0.1s both;
|
|
105
|
+
}
|
|
106
|
+
.fg-emblem-animated .fg-emblem-band {
|
|
107
|
+
stroke-dasharray: 1;
|
|
108
|
+
animation: fg-draw 0.65s cubic-bezier(0.16, 1, 0.3, 1) 0.75s both;
|
|
109
|
+
}
|
|
110
|
+
.fg-emblem-animated .fg-emblem-line {
|
|
111
|
+
stroke-dasharray: 1;
|
|
112
|
+
animation: fg-draw 0.55s ease-out 1.1s both;
|
|
113
|
+
}
|
|
114
|
+
.fg-emblem-animated .fg-emblem-dot {
|
|
115
|
+
transform-box: fill-box;
|
|
116
|
+
transform-origin: center;
|
|
117
|
+
animation: fg-pop 0.35s cubic-bezier(0.34, 1.8, 0.64, 1) 1.35s both;
|
|
118
|
+
}
|
|
119
|
+
.fg-emblem-animated .fg-emblem-dot-2 {
|
|
120
|
+
animation-delay: 1.5s;
|
|
121
|
+
}
|
|
122
|
+
.fg-emblem-animated .fg-emblem-lens {
|
|
123
|
+
transform-box: fill-box;
|
|
124
|
+
transform-origin: center;
|
|
125
|
+
animation: fg-pop 0.45s cubic-bezier(0.34, 1.6, 0.64, 1) 1.6s both;
|
|
126
|
+
}
|
|
127
|
+
@keyframes fg-fade {
|
|
128
|
+
from {
|
|
129
|
+
opacity: 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
@keyframes fg-draw {
|
|
133
|
+
from {
|
|
134
|
+
stroke-dashoffset: 1;
|
|
135
|
+
}
|
|
136
|
+
to {
|
|
137
|
+
stroke-dashoffset: 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
@keyframes fg-pop {
|
|
141
|
+
from {
|
|
142
|
+
transform: scale(0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Procurando rosto: guia oval tracejada pulsando, com varredura */
|
|
147
|
+
.fg-search {
|
|
148
|
+
position: absolute;
|
|
149
|
+
top: 16%;
|
|
150
|
+
left: 30%;
|
|
151
|
+
z-index: 5;
|
|
152
|
+
width: 40%;
|
|
153
|
+
height: 68%;
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
border: 2px dashed rgb(255 255 255 / 0.55);
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
pointer-events: none;
|
|
158
|
+
animation: fg-pulse 1.3s ease-in-out infinite alternate;
|
|
159
|
+
}
|
|
160
|
+
@keyframes fg-pulse {
|
|
161
|
+
from {
|
|
162
|
+
opacity: 0.35;
|
|
163
|
+
}
|
|
164
|
+
to {
|
|
165
|
+
opacity: 0.9;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
.fg-search-sweep,
|
|
169
|
+
.fg-track-line,
|
|
170
|
+
.fg-scan-line {
|
|
171
|
+
position: absolute;
|
|
172
|
+
inset: 0;
|
|
173
|
+
animation: fg-sweep 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
|
|
174
|
+
}
|
|
175
|
+
.fg-search-sweep {
|
|
176
|
+
background: linear-gradient(to bottom, transparent 82%, rgb(255 255 255 / 0.25) 89%, rgb(255 255 255 / 0.6) 90%, transparent 96%);
|
|
177
|
+
}
|
|
178
|
+
@keyframes fg-sweep {
|
|
179
|
+
from {
|
|
180
|
+
transform: translateY(-90%);
|
|
181
|
+
}
|
|
182
|
+
to {
|
|
183
|
+
transform: translateY(10%);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Mira nos cantos: ao vivo (segue o rosto) e no scanner da foto capturada */
|
|
188
|
+
.fg-track,
|
|
189
|
+
.fg-scan {
|
|
190
|
+
--fg-c: var(--fg-accent);
|
|
191
|
+
position: absolute;
|
|
192
|
+
z-index: 5;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
border-radius: 6px;
|
|
195
|
+
pointer-events: none;
|
|
196
|
+
background:
|
|
197
|
+
linear-gradient(var(--fg-c), var(--fg-c)) top left / 22% 3px no-repeat,
|
|
198
|
+
linear-gradient(var(--fg-c), var(--fg-c)) top left / 3px 22% no-repeat,
|
|
199
|
+
linear-gradient(var(--fg-c), var(--fg-c)) top right / 22% 3px no-repeat,
|
|
200
|
+
linear-gradient(var(--fg-c), var(--fg-c)) top right / 3px 22% no-repeat,
|
|
201
|
+
linear-gradient(var(--fg-c), var(--fg-c)) bottom left / 22% 3px no-repeat,
|
|
202
|
+
linear-gradient(var(--fg-c), var(--fg-c)) bottom left / 3px 22% no-repeat,
|
|
203
|
+
linear-gradient(var(--fg-c), var(--fg-c)) bottom right / 22% 3px no-repeat,
|
|
204
|
+
linear-gradient(var(--fg-c), var(--fg-c)) bottom right / 3px 22% no-repeat;
|
|
205
|
+
}
|
|
206
|
+
.fg-track {
|
|
207
|
+
transition: left 0.15s ease-out, top 0.15s ease-out, width 0.15s ease-out, height 0.15s ease-out;
|
|
208
|
+
animation: fg-lock 0.35s cubic-bezier(0.2, 0.9, 0.3, 1) both;
|
|
209
|
+
}
|
|
210
|
+
.fg-track[data-state="warn"] {
|
|
211
|
+
--fg-c: rgb(255 255 255 / 0.85);
|
|
212
|
+
}
|
|
213
|
+
@keyframes fg-lock {
|
|
214
|
+
from {
|
|
215
|
+
opacity: 0;
|
|
216
|
+
transform: scale(1.18);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
.fg-track-line {
|
|
220
|
+
opacity: 0.55;
|
|
221
|
+
animation-duration: 1.8s;
|
|
222
|
+
background: linear-gradient(to bottom, transparent 84%, var(--fg-c) 90%, transparent 96%);
|
|
223
|
+
}
|
|
224
|
+
.fg-track-hold {
|
|
225
|
+
position: absolute;
|
|
226
|
+
right: 10%;
|
|
227
|
+
bottom: 7px;
|
|
228
|
+
left: 10%;
|
|
229
|
+
height: 3px;
|
|
230
|
+
border-radius: 2px;
|
|
231
|
+
background: var(--fg-accent);
|
|
232
|
+
transform-origin: left center;
|
|
233
|
+
transition: transform 0.15s linear;
|
|
234
|
+
}
|
|
235
|
+
.fg-scan {
|
|
236
|
+
box-shadow: 0 0 0 9999px rgb(0 0 0 / 0.35);
|
|
237
|
+
}
|
|
238
|
+
.fg-scan-line {
|
|
239
|
+
animation-duration: 1.4s;
|
|
240
|
+
background: linear-gradient(
|
|
241
|
+
to bottom,
|
|
242
|
+
transparent 76%,
|
|
243
|
+
color-mix(in oklab, var(--fg-accent) 28%, transparent) 86%,
|
|
244
|
+
color-mix(in oklab, var(--fg-accent) 60%, white) 90%,
|
|
245
|
+
color-mix(in oklab, var(--fg-accent) 28%, transparent) 94%,
|
|
246
|
+
transparent 100%
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Resultado */
|
|
251
|
+
.fg-result {
|
|
252
|
+
position: absolute;
|
|
253
|
+
inset: 0;
|
|
254
|
+
display: grid;
|
|
255
|
+
place-items: center;
|
|
256
|
+
padding: 12px 12px 52px;
|
|
257
|
+
background:
|
|
258
|
+
repeating-conic-gradient(rgb(255 255 255 / 0.06) 0% 25%, transparent 0% 50%) 50% / 16px 16px,
|
|
259
|
+
var(--fg-surface);
|
|
260
|
+
}
|
|
261
|
+
.fg-result-img {
|
|
262
|
+
max-width: 100%;
|
|
263
|
+
max-height: 100%;
|
|
264
|
+
border-radius: 8px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Selo "Imagem melhorada" sobre o resultado (e o aviso quando o rosto foi restaurado com IA) */
|
|
268
|
+
.fg-badge {
|
|
269
|
+
position: absolute;
|
|
270
|
+
top: 12px;
|
|
271
|
+
left: 12px;
|
|
272
|
+
z-index: 25;
|
|
273
|
+
display: flex;
|
|
274
|
+
max-width: calc(100% - 24px);
|
|
275
|
+
flex-direction: column;
|
|
276
|
+
gap: 2px;
|
|
277
|
+
padding: 4px 10px;
|
|
278
|
+
border: 1px solid color-mix(in oklab, var(--fg-accent) 55%, transparent);
|
|
279
|
+
border-radius: 999px;
|
|
280
|
+
background: rgb(0 0 0 / 0.7);
|
|
281
|
+
color: #fff;
|
|
282
|
+
font-size: 12px;
|
|
283
|
+
font-weight: 600;
|
|
284
|
+
}
|
|
285
|
+
.fg-badge[data-ai] {
|
|
286
|
+
border-radius: 10px;
|
|
287
|
+
}
|
|
288
|
+
.fg-badge small {
|
|
289
|
+
color: rgb(255 255 255 / 0.8);
|
|
290
|
+
font-size: 11px;
|
|
291
|
+
font-weight: 400;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Faixa de status */
|
|
295
|
+
.fg-status {
|
|
296
|
+
position: absolute;
|
|
297
|
+
right: 12px;
|
|
298
|
+
bottom: 12px;
|
|
299
|
+
left: 12px;
|
|
300
|
+
z-index: 30;
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
gap: 8px;
|
|
304
|
+
padding: 6px 12px;
|
|
305
|
+
border-radius: 8px;
|
|
306
|
+
background: rgb(0 0 0 / 0.75);
|
|
307
|
+
color: #fff;
|
|
308
|
+
font-size: 14px;
|
|
309
|
+
}
|
|
310
|
+
.fg-status span:last-child {
|
|
311
|
+
overflow: hidden;
|
|
312
|
+
white-space: nowrap;
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
}
|
|
315
|
+
.fg-dot {
|
|
316
|
+
flex-shrink: 0;
|
|
317
|
+
width: 8px;
|
|
318
|
+
height: 8px;
|
|
319
|
+
border-radius: 50%;
|
|
320
|
+
background: rgb(255 255 255 / 0.6);
|
|
321
|
+
}
|
|
322
|
+
.fg-dot-ok {
|
|
323
|
+
background: var(--fg-ok);
|
|
324
|
+
}
|
|
325
|
+
.fg-dot-warn {
|
|
326
|
+
background: var(--fg-warn);
|
|
327
|
+
}
|
|
328
|
+
.fg-dot-busy {
|
|
329
|
+
background: var(--fg-accent);
|
|
330
|
+
animation: fg-pulse 0.8s ease-in-out infinite alternate;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Botão */
|
|
334
|
+
.fg-actions {
|
|
335
|
+
display: flex;
|
|
336
|
+
}
|
|
337
|
+
.fg-button {
|
|
338
|
+
min-height: 40px;
|
|
339
|
+
padding: 0 18px;
|
|
340
|
+
border: 1px solid var(--fg-accent);
|
|
341
|
+
border-radius: 8px;
|
|
342
|
+
background: var(--fg-accent);
|
|
343
|
+
color: var(--fg-accent-text);
|
|
344
|
+
font: inherit;
|
|
345
|
+
font-weight: 600;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
}
|
|
348
|
+
.fg-button-outline {
|
|
349
|
+
background: transparent;
|
|
350
|
+
color: inherit;
|
|
351
|
+
}
|
|
352
|
+
.fg-button:disabled {
|
|
353
|
+
opacity: 0.6;
|
|
354
|
+
cursor: progress;
|
|
355
|
+
}
|
|
356
|
+
.fg-button:focus-visible {
|
|
357
|
+
outline: 2px solid var(--fg-accent);
|
|
358
|
+
outline-offset: 2px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@media (prefers-reduced-motion: reduce) {
|
|
362
|
+
.fg-root *,
|
|
363
|
+
.fg-root *::before,
|
|
364
|
+
.fg-root *::after {
|
|
365
|
+
animation: none !important;
|
|
366
|
+
transition: none !important;
|
|
367
|
+
}
|
|
368
|
+
.fg-search-sweep,
|
|
369
|
+
.fg-track-line {
|
|
370
|
+
display: none;
|
|
371
|
+
}
|
|
372
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beltec/face-gate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Face Gate, uma solução BelTec: validação de fotos de rosto para controle de acesso — componente React de webcam em tempo real e helper de servidor (Node/Next).",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "BelTec · grupo Belfort",
|
|
7
|
+
"homepage": "https://face-gate.beltec.ai",
|
|
8
|
+
"keywords": ["face-gate", "beltec", "webcam", "face", "validation", "react", "nextjs", "controle de acesso"],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"sideEffects": ["*.css"],
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./server": {
|
|
21
|
+
"types": "./dist/server.d.ts",
|
|
22
|
+
"import": "./dist/server.js",
|
|
23
|
+
"require": "./dist/server.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./styles.css": "./dist/styles.css",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": ["dist", "README.md"],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.17"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup && node -e \"require('node:fs').copyFileSync('src/react/styles.css','dist/styles.css')\"",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=18",
|
|
41
|
+
"react-dom": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"react": { "optional": true },
|
|
45
|
+
"react-dom": { "optional": true }
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.14.0",
|
|
49
|
+
"@types/react": "^19.0.0",
|
|
50
|
+
"@types/react-dom": "^19.0.0",
|
|
51
|
+
"jsdom": "^25.0.1",
|
|
52
|
+
"react": "^19.0.0",
|
|
53
|
+
"react-dom": "^19.0.0",
|
|
54
|
+
"tsup": "^8.3.5",
|
|
55
|
+
"typescript": "^5.6.3",
|
|
56
|
+
"vitest": "^2.1.8"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|