@rexai/pulse-react 1.0.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.
@@ -0,0 +1,436 @@
1
+ /**
2
+ * PulseAI React SDK - Component Styles
3
+ * Import this file in your app: import '@pulseai/react/styles.css'
4
+ */
5
+
6
+ /* ================================================
7
+ CSS Variables / Theme
8
+ ================================================ */
9
+ .pulse-scanner,
10
+ .vitals-card {
11
+ --pulse-primary: #0ea5e9;
12
+ --pulse-primary-dark: #0284c7;
13
+ --pulse-success: #10b981;
14
+ --pulse-warning: #f59e0b;
15
+ --pulse-error: #ef4444;
16
+ --pulse-text: #1e293b;
17
+ --pulse-text-secondary: #64748b;
18
+ --pulse-bg: #ffffff;
19
+ --pulse-bg-secondary: #f8fafc;
20
+ --pulse-border: #e2e8f0;
21
+ --pulse-radius: 12px;
22
+ --pulse-radius-lg: 16px;
23
+ --pulse-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
24
+ }
25
+
26
+ .pulse-scanner--dark,
27
+ .vitals-card--dark {
28
+ --pulse-primary: #38bdf8;
29
+ --pulse-primary-dark: #0ea5e9;
30
+ --pulse-text: #f1f5f9;
31
+ --pulse-text-secondary: #94a3b8;
32
+ --pulse-bg: #1e293b;
33
+ --pulse-bg-secondary: #0f172a;
34
+ --pulse-border: #334155;
35
+ }
36
+
37
+ /* ================================================
38
+ PulseScanner Component
39
+ ================================================ */
40
+ .pulse-scanner {
41
+ font-family: var(--pulse-font);
42
+ background: var(--pulse-bg);
43
+ border-radius: var(--pulse-radius-lg);
44
+ overflow: hidden;
45
+ border: 1px solid var(--pulse-border);
46
+ }
47
+
48
+ /* Preview Container */
49
+ .pulse-scanner__preview {
50
+ position: relative;
51
+ width: 100%;
52
+ aspect-ratio: 4 / 3;
53
+ background: #000;
54
+ overflow: hidden;
55
+ }
56
+
57
+ .pulse-scanner__video {
58
+ width: 100%;
59
+ height: 100%;
60
+ object-fit: cover;
61
+ transform: scaleX(-1); /* Mirror for selfie view */
62
+ }
63
+
64
+ /* Face Mesh Canvas Overlay */
65
+ .pulse-scanner__mesh-canvas {
66
+ position: absolute;
67
+ top: 0;
68
+ left: 0;
69
+ width: 100%;
70
+ height: 100%;
71
+ pointer-events: none;
72
+ transform: scaleX(-1); /* Mirror to match video */
73
+ }
74
+
75
+ /* Overlay */
76
+ .pulse-scanner__overlay {
77
+ position: absolute;
78
+ inset: 0;
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ justify-content: center;
83
+ pointer-events: none;
84
+ }
85
+
86
+ /* Face Guide */
87
+ .pulse-scanner__face-guide {
88
+ width: 200px;
89
+ height: 260px;
90
+ border: 3px dashed rgba(255, 255, 255, 0.5);
91
+ border-radius: 50% 50% 45% 45%;
92
+ transition: all 0.3s ease;
93
+ }
94
+
95
+ .pulse-scanner__face-guide--detected {
96
+ border-color: var(--pulse-success);
97
+ border-style: solid;
98
+ box-shadow: 0 0 20px rgba(16, 185, 129, 0.3);
99
+ }
100
+
101
+ /* Quality Message */
102
+ .pulse-scanner__quality {
103
+ position: absolute;
104
+ bottom: 20px;
105
+ left: 50%;
106
+ transform: translateX(-50%);
107
+ background: rgba(0, 0, 0, 0.7);
108
+ color: white;
109
+ padding: 8px 16px;
110
+ border-radius: 20px;
111
+ font-size: 14px;
112
+ font-weight: 500;
113
+ backdrop-filter: blur(4px);
114
+ }
115
+
116
+ .pulse-scanner__quality--good {
117
+ background: rgba(16, 185, 129, 0.9);
118
+ }
119
+
120
+ /* Countdown */
121
+ .pulse-scanner__countdown {
122
+ position: absolute;
123
+ inset: 0;
124
+ display: flex;
125
+ flex-direction: column;
126
+ align-items: center;
127
+ justify-content: center;
128
+ background: rgba(0, 0, 0, 0.6);
129
+ backdrop-filter: blur(4px);
130
+ }
131
+
132
+ .pulse-scanner__countdown-ring {
133
+ position: relative;
134
+ width: 100px;
135
+ height: 100px;
136
+ }
137
+
138
+ .pulse-scanner__countdown-ring svg {
139
+ width: 100%;
140
+ height: 100%;
141
+ color: var(--pulse-primary);
142
+ }
143
+
144
+ .pulse-scanner__countdown-ring circle {
145
+ transition: stroke-dasharray 1s linear;
146
+ }
147
+
148
+ .pulse-scanner__countdown-number {
149
+ position: absolute;
150
+ inset: 0;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ font-size: 36px;
155
+ font-weight: 700;
156
+ color: white;
157
+ }
158
+
159
+ .pulse-scanner__countdown-label {
160
+ margin-top: 8px;
161
+ color: rgba(255, 255, 255, 0.8);
162
+ font-size: 14px;
163
+ }
164
+
165
+ /* Processing Overlay */
166
+ .pulse-scanner__processing {
167
+ position: absolute;
168
+ inset: 0;
169
+ display: flex;
170
+ flex-direction: column;
171
+ align-items: center;
172
+ justify-content: center;
173
+ background: rgba(255, 255, 255, 0.95);
174
+ backdrop-filter: blur(8px);
175
+ }
176
+
177
+ .pulse-scanner--dark .pulse-scanner__processing {
178
+ background: rgba(15, 23, 42, 0.95);
179
+ }
180
+
181
+ .pulse-scanner__spinner {
182
+ width: 48px;
183
+ height: 48px;
184
+ border: 4px solid var(--pulse-border);
185
+ border-top-color: var(--pulse-primary);
186
+ border-radius: 50%;
187
+ animation: pulse-spin 1s linear infinite;
188
+ }
189
+
190
+ @keyframes pulse-spin {
191
+ to { transform: rotate(360deg); }
192
+ }
193
+
194
+ .pulse-scanner__processing-text {
195
+ margin-top: 16px;
196
+ color: var(--pulse-text);
197
+ font-size: 16px;
198
+ font-weight: 500;
199
+ }
200
+
201
+ /* Controls */
202
+ .pulse-scanner__controls {
203
+ padding: 16px;
204
+ display: flex;
205
+ justify-content: center;
206
+ background: var(--pulse-bg-secondary);
207
+ border-top: 1px solid var(--pulse-border);
208
+ }
209
+
210
+ .pulse-scanner__btn {
211
+ padding: 12px 32px;
212
+ font-size: 16px;
213
+ font-weight: 600;
214
+ border-radius: var(--pulse-radius);
215
+ border: none;
216
+ cursor: pointer;
217
+ transition: all 0.2s ease;
218
+ font-family: var(--pulse-font);
219
+ }
220
+
221
+ .pulse-scanner__btn--primary {
222
+ background: var(--pulse-primary);
223
+ color: white;
224
+ }
225
+
226
+ .pulse-scanner__btn--primary:hover:not(:disabled) {
227
+ background: var(--pulse-primary-dark);
228
+ transform: translateY(-1px);
229
+ }
230
+
231
+ .pulse-scanner__btn--secondary {
232
+ background: var(--pulse-bg);
233
+ color: var(--pulse-text);
234
+ border: 1px solid var(--pulse-border);
235
+ }
236
+
237
+ .pulse-scanner__btn--secondary:hover:not(:disabled) {
238
+ background: var(--pulse-bg-secondary);
239
+ }
240
+
241
+ .pulse-scanner__btn:disabled {
242
+ opacity: 0.6;
243
+ cursor: not-allowed;
244
+ }
245
+
246
+ /* Error */
247
+ .pulse-scanner__error {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 8px;
251
+ padding: 12px 16px;
252
+ background: rgba(239, 68, 68, 0.1);
253
+ border-top: 1px solid rgba(239, 68, 68, 0.2);
254
+ color: var(--pulse-error);
255
+ font-size: 14px;
256
+ }
257
+
258
+ .pulse-scanner__error-icon {
259
+ width: 20px;
260
+ height: 20px;
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ background: var(--pulse-error);
265
+ color: white;
266
+ border-radius: 50%;
267
+ font-size: 12px;
268
+ font-weight: 700;
269
+ flex-shrink: 0;
270
+ }
271
+
272
+ /* ================================================
273
+ VitalsCard Component
274
+ ================================================ */
275
+ .vitals-card {
276
+ font-family: var(--pulse-font);
277
+ background: var(--pulse-bg);
278
+ border-radius: var(--pulse-radius-lg);
279
+ padding: 24px;
280
+ border: 1px solid var(--pulse-border);
281
+ }
282
+
283
+ .vitals-card--loading,
284
+ .vitals-card--error {
285
+ display: flex;
286
+ flex-direction: column;
287
+ align-items: center;
288
+ justify-content: center;
289
+ min-height: 200px;
290
+ gap: 16px;
291
+ }
292
+
293
+ .vitals-card__spinner {
294
+ width: 32px;
295
+ height: 32px;
296
+ border: 3px solid var(--pulse-border);
297
+ border-top-color: var(--pulse-primary);
298
+ border-radius: 50%;
299
+ animation: pulse-spin 1s linear infinite;
300
+ }
301
+
302
+ .vitals-card__loading-text {
303
+ color: var(--pulse-text-secondary);
304
+ font-size: 14px;
305
+ }
306
+
307
+ .vitals-card__error-icon {
308
+ width: 40px;
309
+ height: 40px;
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ background: rgba(239, 68, 68, 0.1);
314
+ color: var(--pulse-error);
315
+ border-radius: 50%;
316
+ font-size: 20px;
317
+ font-weight: 700;
318
+ }
319
+
320
+ .vitals-card__error-text {
321
+ color: var(--pulse-error);
322
+ font-size: 14px;
323
+ text-align: center;
324
+ }
325
+
326
+ /* Grid Layout */
327
+ .vitals-card__grid {
328
+ display: grid;
329
+ grid-template-columns: repeat(2, 1fr);
330
+ gap: 20px;
331
+ }
332
+
333
+ @media (min-width: 480px) {
334
+ .vitals-card__grid {
335
+ grid-template-columns: repeat(4, 1fr);
336
+ }
337
+ }
338
+
339
+ .vitals-card--compact .vitals-card__grid {
340
+ gap: 12px;
341
+ }
342
+
343
+ /* Vital Item */
344
+ .vitals-card__item {
345
+ display: flex;
346
+ flex-direction: column;
347
+ gap: 4px;
348
+ }
349
+
350
+ .vitals-card__label {
351
+ font-size: 12px;
352
+ font-weight: 500;
353
+ color: var(--pulse-text-secondary);
354
+ text-transform: uppercase;
355
+ letter-spacing: 0.5px;
356
+ }
357
+
358
+ .vitals-card__value {
359
+ font-size: 28px;
360
+ font-weight: 700;
361
+ color: var(--pulse-text);
362
+ line-height: 1.2;
363
+ }
364
+
365
+ .vitals-card--compact .vitals-card__value {
366
+ font-size: 22px;
367
+ }
368
+
369
+ .vitals-card__unit {
370
+ font-size: 14px;
371
+ font-weight: 500;
372
+ color: var(--pulse-text-secondary);
373
+ margin-left: 4px;
374
+ }
375
+
376
+ .vitals-card__status {
377
+ font-size: 12px;
378
+ font-weight: 600;
379
+ padding: 2px 8px;
380
+ border-radius: 10px;
381
+ width: fit-content;
382
+ }
383
+
384
+ .vitals-card__status--green {
385
+ background: rgba(16, 185, 129, 0.1);
386
+ color: var(--pulse-success);
387
+ }
388
+
389
+ .vitals-card__status--blue {
390
+ background: rgba(14, 165, 233, 0.1);
391
+ color: var(--pulse-primary);
392
+ }
393
+
394
+ .vitals-card__status--orange {
395
+ background: rgba(245, 158, 11, 0.1);
396
+ color: var(--pulse-warning);
397
+ }
398
+
399
+ .vitals-card__status--red {
400
+ background: rgba(239, 68, 68, 0.1);
401
+ color: var(--pulse-error);
402
+ }
403
+
404
+ /* Extended Metrics */
405
+ .vitals-card__extended {
406
+ display: grid;
407
+ grid-template-columns: repeat(2, 1fr);
408
+ gap: 16px;
409
+ margin-top: 20px;
410
+ padding-top: 20px;
411
+ border-top: 1px solid var(--pulse-border);
412
+ }
413
+
414
+ @media (min-width: 480px) {
415
+ .vitals-card__extended {
416
+ grid-template-columns: repeat(4, 1fr);
417
+ }
418
+ }
419
+
420
+ .vitals-card__item--small .vitals-card__value {
421
+ font-size: 20px;
422
+ }
423
+
424
+ .vitals-card__item--small .vitals-card__unit {
425
+ font-size: 12px;
426
+ }
427
+
428
+ /* Minimal Theme */
429
+ .pulse-scanner--minimal .pulse-scanner__preview {
430
+ border-radius: var(--pulse-radius);
431
+ }
432
+
433
+ .pulse-scanner--minimal .pulse-scanner__controls {
434
+ background: transparent;
435
+ border: none;
436
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@rexai/pulse-react",
3
+ "version": "1.0.0",
4
+ "description": "React SDK for PulseAI rPPG health analysis - measure heart rate, HRV, SpO2 from any camera",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./styles.css": "./dist/styles.css"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom && cp src/styles/scanner.css dist/styles.css",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom --watch",
23
+ "lint": "eslint src/",
24
+ "clean": "rm -rf dist",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=17.0.0",
29
+ "react-dom": ">=17.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@mediapipe/tasks-vision": "^0.10.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.2.0",
36
+ "@types/react-dom": "^18.2.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.0.0",
39
+ "react": "^18.2.0",
40
+ "react-dom": "^18.2.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/kingkong9128/rppg"
45
+ },
46
+ "keywords": [
47
+ "rppg",
48
+ "remote-photoplethysmography",
49
+ "health",
50
+ "vitals",
51
+ "heart-rate",
52
+ "hrv",
53
+ "spo2",
54
+ "mediapipe",
55
+ "react",
56
+ "telehealth",
57
+ "telemedicine"
58
+ ],
59
+ "author": "PulseAI",
60
+ "license": "MIT",
61
+ "bugs": {
62
+ "url": "https://github.com/pulseai/react-sdk/issues"
63
+ },
64
+ "homepage": "https://pulseai.com/developers"
65
+ }