@simprints/simface-sdk 0.10.1 → 0.12.1

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,6 +1,6 @@
1
1
  # SimFace SDK
2
2
 
3
- SimFace SDK provides facial recognition for web-based KYC workflows. It is a drop-in Web Component and JavaScript API that handles camera capture, face quality validation, and communication with the SimFace backend.
3
+ SimFace SDK provides facial recognition for web-based KYC workflows. It exposes one primary JavaScript API for enrollment and verification, plus a lower-level Web Component for advanced UI control.
4
4
 
5
5
  Works in: all modern browsers, WhatsApp in-app browser, and mobile WebViews.
6
6
 
@@ -8,7 +8,20 @@ This repository is the public frontend SDK and demo repo for SimFace. For fronte
8
8
 
9
9
  The backend API, infrastructure, and TensorFlow Lite runtime live in the separate private backend repository.
10
10
 
11
- The capture flow is planned explicitly as: auto camera -> manual camera -> media picker. Hosts can keep the default popup experience or opt into embedded capture with the same fallback policy.
11
+ The capture flow is planned explicitly as: auto camera -> manual camera -> media picker. The primary API supports two UI modes:
12
+ - popup capture: the SDK opens and manages its own modal capture flow
13
+ - embedded capture: the SDK runs capture inside a host-provided `simface-capture` element
14
+
15
+ ## Face Quality Checks
16
+
17
+ The SDK automatically performs these checks on captured images before submission:
18
+
19
+ 1. Face presence - at least one face must be detected.
20
+ 2. Single face - only one face should be in the frame.
21
+ 3. Face size - face must not be too close or too far.
22
+ 4. Centering - face must be approximately centered in the frame.
23
+
24
+ If a check fails, the user is prompted with specific guidance and asked to retake the photo.
12
25
 
13
26
  ## Quick Start
14
27
 
@@ -75,70 +88,138 @@ if (result.match) {
75
88
  }
76
89
  ```
77
90
 
78
- ### 5. Customize capture behavior
91
+ ### 5. Choose the capture UI mode
79
92
 
80
- Both `enroll()` and `verify()` accept an optional third argument to control how the camera capture UI is presented. Define the options once and pass them to either call:
93
+ `enroll()` and `verify()` are the main SDK entry points. They both use the same capture workflow and backend API. The only UI difference is where the capture UI is rendered.
81
94
 
82
- ```javascript
83
- const captureOptions = {
84
- // 'popup' (default) opens a fullscreen modal dialog.
85
- // 'embedded' renders the capture component inline in a host container.
86
- presentation: 'popup',
95
+ Both functions accept:
96
+ - `workflowOptions`: optional capture behavior that applies to both popup and embedded flows
97
+ - `captureElement`: optional existing `simface-capture` element; if this argument is present, the SDK uses embedded mode
87
98
 
88
- // 'auto-preferred' (default) uses automatic face-framing capture when supported,
89
- // falling back to manual shutter. 'manual-only' always shows a manual shutter button.
90
- capturePreference: 'auto-preferred',
99
+ #### Popup capture
100
+
101
+ If you omit `captureElement`, the SDK opens its popup capture UI:
91
102
 
92
- // When true (default), falls back to the device media/file picker if camera access
93
- // is unavailable (e.g. denied permissions, or WhatsApp in-app browser).
103
+ ```javascript
104
+ const workflowOptions = {
105
+ capturePreference: 'auto-preferred',
94
106
  allowMediaPickerFallback: true,
107
+ };
108
+
109
+ const enrollResult = await enroll(config, 'unique-user-id', workflowOptions);
110
+ const verifyResult = await verify(config, 'unique-user-id', workflowOptions);
111
+ ```
112
+
113
+ #### Embedded capture
114
+
115
+ If you want capture inline in your page, create a `simface-capture` element and pass it as `captureElement`. The SDK still owns the capture lifecycle; it just renders the UI inline instead of in a popup.
95
116
 
96
- // Only used when presentation is 'embedded'. Specifies the host container element
97
- // (CSS selector string or HTMLElement). Ignored for 'popup' mode.
98
- container: '#capture-slot',
117
+ ```html
118
+ <simface-capture
119
+ embedded
120
+ capture-preference="auto-preferred"
121
+ label="Take a selfie for verification"
122
+ idle-feedback-label="Start verification to see camera guidance here."
123
+ capture-label="Snap photo"
124
+ retake-label="Take another"
125
+ confirm-label="Use this photo"
126
+ retry-label="Start over"
127
+ ></simface-capture>
128
+ ```
99
129
 
100
- // Optional UI label overrides (apply to both popup and embedded).
101
- label: 'Take a selfie for verification',
102
- confirmLabel: 'Use this photo',
130
+ ```javascript
131
+ const workflowOptions = {
132
+ capturePreference: 'auto-preferred',
133
+ allowMediaPickerFallback: true,
103
134
  };
104
135
 
105
- const enrollResult = await enroll(config, 'unique-user-id', captureOptions);
106
- const verifyResult = await verify(config, 'unique-user-id', captureOptions);
136
+ const captureElement = document.querySelector('simface-capture');
137
+
138
+ const enrollResult = await enroll(config, 'unique-user-id', workflowOptions, captureElement);
139
+ const verifyResult = await verify(config, 'unique-user-id', workflowOptions, captureElement);
107
140
  ```
108
141
 
109
- | Option | Type | Default | Notes |
142
+ | workflowOptions | Type | Default | Notes |
110
143
  |--------|------|---------|-------|
111
- | `presentation` | `'popup' \| 'embedded'` | `'popup'` | Popup opens a fullscreen modal; embedded renders inline |
112
144
  | `capturePreference` | `'auto-preferred' \| 'manual-only'` | `'auto-preferred'` | Controls auto vs manual shutter |
113
145
  | `allowMediaPickerFallback` | `boolean` | `true` | Falls back to file picker if camera is unavailable |
114
- | `container` | `HTMLElement \| string` | — | Required for embedded via top-level helpers; ignored for popup |
115
- | `label` | `string` | `'Capturing Face'` | Instructional text shown during capture |
116
- | `confirmLabel` | `string` | `'Accept'` | Confirm button label in preview state |
117
146
 
118
- ## Try the local demo
147
+ ## API Reference
119
148
 
120
- Build the SDK at the repository root, then run the demo:
149
+ ### Primary SDK API
121
150
 
122
- ```bash
123
- npm install
124
- npm run build
151
+ The main integration surface is:
152
+ - `enroll(config, clientId, workflowOptions?, captureElement?)`
153
+ - `verify(config, clientId, workflowOptions?, captureElement?)`
125
154
 
126
- cd demo
127
- npm install
128
- npm run dev
129
- ```
155
+ These functions:
156
+ - run the camera capture workflow
157
+ - manage popup or embedded capture UI
158
+ - perform face quality validation
159
+ - call the backend API for enrollment or verification
130
160
 
131
- The demo runs at `http://localhost:4173` and consumes the built SDK artifact from `dist/`. To enable HTTPS (required for camera access from other devices on the local network), set `DEMO_USE_HTTPS=true` before starting the demo.
161
+ ### `enroll(config, clientId, workflowOptions?, captureElement?): Promise<EnrollResult>`
162
+
163
+ Opens the camera, captures a face image with quality validation, and enrolls the user.
164
+
165
+ Parameters:
166
+
167
+ | Parameter | Type | Description |
168
+ |-----------|------|-------------|
169
+ | `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
170
+ | `clientId` | `string` | Unique identifier for the user |
171
+ | `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
172
+ | `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
173
+
174
+ Returns: `EnrollResult`
175
+
176
+ ### `verify(config, clientId, workflowOptions?, captureElement?): Promise<VerifyResult>`
132
177
 
133
- ## Web Component
178
+ Opens the camera, captures a face image, and verifies against the enrolled face.
179
+
180
+ Parameters:
181
+
182
+ | Parameter | Type | Description |
183
+ |-----------|------|-------------|
184
+ | `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
185
+ | `clientId` | `string` | Unique identifier for the user |
186
+ | `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
187
+ | `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
188
+
189
+ Returns: `VerifyResult`
134
190
 
135
- For more control over the UI, use the `<simface-capture>` Web Component directly:
191
+ ### `SimFaceAPIClient` and the backend REST interface
192
+
193
+ `SimFaceAPIClient` is the lower-level HTTP client used internally by `enroll()` and `verify()`. Use it when you want direct control over when capture happens and when backend calls are made.
194
+
195
+ Typical cases for using `SimFaceAPIClient` directly:
196
+ - advanced UI flows driven by your own application state
197
+ - direct use of the `simface-capture` component
198
+ - custom orchestration where capture and backend submission happen in separate steps
199
+
200
+ At a high level:
201
+ - `enroll()` and `verify()` = capture UI + quality checks + backend submission
202
+ - `SimFaceAPIClient` = backend submission only
203
+
204
+ `SimFaceAPIClient` maps directly to the backend REST interface:
205
+ - `validateAPIKey()` -> `POST /api/v1/auth/validate`
206
+ - `enroll(clientId, imageBlob)` -> `POST /api/v1/enroll`
207
+ - `verify(clientId, imageBlob)` -> `POST /api/v1/verify`
208
+
209
+ ## Advanced: Direct `simface-capture` control
210
+
211
+ Use the `simface-capture` Web Component directly when you want the host application to manage capture state itself instead of letting `enroll()` or `verify()` orchestrate it. In this mode, the component is also the source of truth for embedded UI copy.
136
212
 
137
213
  ```html
138
214
  <simface-capture
139
215
  embedded
140
216
  capture-preference="auto-preferred"
141
217
  label="Take a selfie for verification"
218
+ idle-feedback-label="Start verification to see camera guidance here."
219
+ capture-label="Snap photo"
220
+ retake-label="Take another"
221
+ confirm-label="Use this photo"
222
+ retry-label="Start over"
142
223
  ></simface-capture>
143
224
 
144
225
  <script type="module">
@@ -165,16 +246,30 @@ For more control over the UI, use the `<simface-capture>` Web Component directly
165
246
  captureEl.addEventListener('simface-error', (e) => {
166
247
  console.error('Capture error:', e.detail.error);
167
248
  });
249
+
250
+ await captureEl.startCapture();
168
251
  </script>
169
252
  ```
170
253
 
254
+ In this advanced flow:
255
+ 1. the host renders the component
256
+ 2. the host starts capture with `startCapture()` or by setting `active = true`
257
+ 3. the component emits capture events
258
+ 4. the host decides what backend call to make with `SimFaceAPIClient`
259
+
260
+ This is more flexible, but it also means the host owns more of the workflow.
261
+
171
262
  ### Component Attributes
172
263
 
173
264
  | Attribute | Type | Default | Description |
174
265
  |-----------|------|---------|-------------|
175
- | `label` | String | `"Capturing Face"` | Instructional text shown on the capture button |
266
+ | `label` | String | `"Capturing Face"` | Primary instructional text shown by the component |
267
+ | `idle-feedback-label` | String | `"Start a capture to see camera guidance here."` | Idle guidance text shown in the feedback area before capture begins |
176
268
  | `embedded` | Boolean | `false` | Runs the component inline instead of delegating to the popup capture service |
269
+ | `capture-label` | String | `"Take photo"` | Manual capture button label |
270
+ | `retake-label` | String | `"Retake"` | Preview retake button label |
177
271
  | `confirm-label` | String | `"Accept"` | Confirm button label used in preview state |
272
+ | `retry-label` | String | `"Try again"` | Error-state retry button label |
178
273
  | `capture-preference` | `"auto-preferred" \| "manual-only"` | `"auto-preferred"` | Whether auto capture should be preferred or disabled |
179
274
  | `allow-media-picker-fallback` | Boolean | `true` | Whether the component may fall back to the media picker if camera capture is unavailable |
180
275
 
@@ -186,23 +281,31 @@ For more control over the UI, use the `<simface-capture>` Web Component directly
186
281
  | `simface-cancelled` | - | Fires when the user cancels the capture flow |
187
282
  | `simface-error` | `{ error: string }` | Fires on capture/detection errors |
188
283
 
189
- ## API Reference
284
+ ## Type Definitions
190
285
 
191
- ### `enroll(config, clientId, captureOptions?): Promise<EnrollResult>`
286
+ ### `SimFaceConfig`
192
287
 
193
- Opens the camera, captures a face image with quality validation, and enrolls the user.
288
+ ```typescript
289
+ interface SimFaceConfig {
290
+ apiUrl: string;
291
+ projectId: string;
292
+ apiKey: string;
293
+ }
294
+ ```
194
295
 
195
- Parameters:
296
+ ### `SimFaceWorkflowOptions`
196
297
 
197
- | Parameter | Type | Description |
198
- |-----------|------|-------------|
199
- | `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
200
- | `clientId` | `string` | Unique identifier for the user |
201
- | `captureOptions` | `SimFaceCaptureOptions` | Optional capture presentation/fallback overrides |
298
+ ```typescript
299
+ interface SimFaceWorkflowOptions {
300
+ capturePreference?: 'auto-preferred' | 'manual-only';
301
+ allowMediaPickerFallback?: boolean;
302
+ }
303
+ ```
304
+
305
+ ### `EnrollResult`
202
306
 
203
- Returns `EnrollResult`:
204
307
  ```typescript
205
- {
308
+ interface EnrollResult {
206
309
  success: boolean;
207
310
  clientId: string;
208
311
  message?: string;
@@ -210,21 +313,10 @@ Returns `EnrollResult`:
210
313
  }
211
314
  ```
212
315
 
213
- ### `verify(config, clientId, captureOptions?): Promise<VerifyResult>`
214
-
215
- Opens the camera, captures a face image, and verifies against the enrolled face.
216
-
217
- Parameters:
218
-
219
- | Parameter | Type | Description |
220
- |-----------|------|-------------|
221
- | `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
222
- | `clientId` | `string` | Unique identifier for the user |
223
- | `captureOptions` | `SimFaceCaptureOptions` | Optional capture presentation/fallback overrides |
316
+ ### `VerifyResult`
224
317
 
225
- Returns `VerifyResult`:
226
318
  ```typescript
227
- {
319
+ interface VerifyResult {
228
320
  match: boolean;
229
321
  score: number;
230
322
  threshold: number;
@@ -233,29 +325,6 @@ Returns `VerifyResult`:
233
325
  }
234
326
  ```
235
327
 
236
- ### `SimFaceConfig`
237
-
238
- ```typescript
239
- {
240
- apiUrl: string;
241
- projectId: string;
242
- apiKey: string;
243
- }
244
- ```
245
-
246
- ### `SimFaceCaptureOptions`
247
-
248
- ```typescript
249
- {
250
- presentation?: 'popup' | 'embedded';
251
- capturePreference?: 'auto-preferred' | 'manual-only';
252
- allowMediaPickerFallback?: boolean;
253
- container?: HTMLElement | string;
254
- label?: string;
255
- confirmLabel?: string;
256
- }
257
- ```
258
-
259
328
  ## Backend API Endpoints
260
329
 
261
330
  For clients integrating directly with the REST API:
@@ -283,17 +352,6 @@ Health check endpoint.
283
352
 
284
353
  Response: `{ "status": "ok" }`
285
354
 
286
- ## Face Quality Checks
287
-
288
- The SDK automatically performs these checks on captured images before submission:
289
-
290
- 1. Face presence - at least one face must be detected.
291
- 2. Single face - only one face should be in the frame.
292
- 3. Face size - face must not be too close or too far.
293
- 4. Centering - face must be approximately centered in the frame.
294
-
295
- If a check fails, the user is prompted with specific guidance and asked to retake the photo.
296
-
297
355
  ## Browser Compatibility
298
356
 
299
357
  | Browser | Camera Capture | Face Detection |
@@ -305,3 +363,18 @@ If a check fails, the user is prompted with specific guidance and asked to retak
305
363
  | Samsung Internet | Yes | Yes |
306
364
 
307
365
  > Note: In WhatsApp's in-app browser, the camera opens via the device's native camera app rather than an in-browser preview. Face quality checks run after the photo is taken.
366
+
367
+ ## Try the local demo
368
+
369
+ Build the SDK at the repository root, then run the demo:
370
+
371
+ ```bash
372
+ npm install
373
+ npm run build
374
+
375
+ cd demo
376
+ npm install
377
+ npm run dev
378
+ ```
379
+
380
+ The demo runs at `http://localhost:4173` and consumes the built SDK artifact from `dist/`. To enable HTTPS (required for camera access from other devices on the local network), set `DEMO_USE_HTTPS=true` before starting the demo.
@@ -10,9 +10,13 @@ import type { CapturePreference } from '../types/index.js';
10
10
  */
11
11
  export declare class SimFaceCapture extends LitElement {
12
12
  label: string;
13
+ idleFeedbackLabel: string;
13
14
  embedded: boolean;
14
15
  active: boolean;
15
16
  confirmLabel: string;
17
+ captureLabel: string;
18
+ retakeLabel: string;
19
+ retryLabel: string;
16
20
  capturePreference: CapturePreference;
17
21
  allowMediaPickerFallback: boolean;
18
22
  private captureState;
@@ -31,6 +35,7 @@ export declare class SimFaceCapture extends LitElement {
31
35
  private pendingActiveSync;
32
36
  static styles: import("lit").CSSResult;
33
37
  disconnectedCallback(): void;
38
+ protected willUpdate(changedProperties: Map<string, unknown>): void;
34
39
  updated(changedProperties: Map<string, unknown>): void;
35
40
  render(): import("lit-html").TemplateResult<1>;
36
41
  startCapture(): Promise<void>;
@@ -8,7 +8,7 @@ import { LitElement, html, css } from 'lit';
8
8
  import { customElement, property, query, state } from 'lit/decorators.js';
9
9
  import { assessFaceQuality } from '../services/face-detection.js';
10
10
  import { CAPTURE_GUIDE_MASK_PATH, CAPTURE_GUIDE_PATH, } from '../shared/auto-capture.js';
11
- import { buildCapturePlan, normalizeCaptureOptions, resolveCaptureCapabilities, DEFAULT_LABEL, DEFAULT_CONFIRM_LABEL, } from '../shared/capture-flow.js';
11
+ import { buildCapturePlan, normalizeCaptureOptions, resolveCaptureCapabilities, DEFAULT_CAPTURE_LABEL, DEFAULT_IDLE_FEEDBACK_LABEL, DEFAULT_LABEL, DEFAULT_CONFIRM_LABEL, DEFAULT_RETAKE_LABEL, DEFAULT_RETRY_LABEL, } from '../shared/capture-flow.js';
12
12
  import { CameraCaptureSessionController, } from '../shared/capture-session.js';
13
13
  import { CameraAccessError, blobToImage, captureFromFileInput, openUserFacingCameraStream, } from '../shared/capture-runtime.js';
14
14
  /**
@@ -23,14 +23,18 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
23
23
  constructor() {
24
24
  super(...arguments);
25
25
  this.label = DEFAULT_LABEL;
26
+ this.idleFeedbackLabel = DEFAULT_IDLE_FEEDBACK_LABEL;
26
27
  this.embedded = false;
27
28
  this.active = false;
28
29
  this.confirmLabel = DEFAULT_CONFIRM_LABEL;
30
+ this.captureLabel = DEFAULT_CAPTURE_LABEL;
31
+ this.retakeLabel = DEFAULT_RETAKE_LABEL;
32
+ this.retryLabel = DEFAULT_RETRY_LABEL;
29
33
  this.capturePreference = 'auto-preferred';
30
34
  this.allowMediaPickerFallback = true;
31
35
  this.captureState = 'idle';
32
36
  this.errorMessage = '';
33
- this.feedbackMessage = 'Start a capture to see camera guidance here.';
37
+ this.feedbackMessage = DEFAULT_IDLE_FEEDBACK_LABEL;
34
38
  this.feedbackTone = 'neutral';
35
39
  this.previewUrl = '';
36
40
  this.qualityResult = null;
@@ -46,6 +50,11 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
46
50
  super.disconnectedCallback();
47
51
  this.stopSession();
48
52
  }
53
+ willUpdate(changedProperties) {
54
+ if (changedProperties.has('idleFeedbackLabel') && this.captureState === 'idle') {
55
+ this.feedbackMessage = this.idleFeedbackLabel;
56
+ }
57
+ }
49
58
  updated(changedProperties) {
50
59
  if (!changedProperties.has('active') || this.pendingActiveSync) {
51
60
  return;
@@ -90,9 +99,8 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
90
99
  : ''}
91
100
 
92
101
  <p class="capture-copy">${this.label}</p>
93
-
94
102
  ${this.captureState === 'idle'
95
- ? html `<p class="capture-copy">Waiting for the host page to start capture.</p>`
103
+ ? ''
96
104
  : html `
97
105
  <div class="stage">
98
106
  <video
@@ -127,13 +135,13 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
127
135
  ${this.captureState === 'live'
128
136
  ? html `
129
137
  ${this.captureMode === 'manual'
130
- ? html `<button class="btn btn-primary" data-simface-action="capture" ?disabled=${!this.canTakePhoto} @click=${this.handleManualCapture}>Take photo</button>`
138
+ ? html `<button class="btn btn-primary" data-simface-action="capture" ?disabled=${!this.canTakePhoto} @click=${this.handleManualCapture}>${this.captureLabel}</button>`
131
139
  : ''}
132
140
  `
133
141
  : ''}
134
142
  ${this.captureState === 'preview'
135
143
  ? html `
136
- <button class="btn btn-retake" data-simface-action="retake" @click=${this.handleRetake}>Retake</button>
144
+ <button class="btn btn-retake" data-simface-action="retake" @click=${this.handleRetake}>${this.retakeLabel}</button>
137
145
  ${this.qualityResult?.passesQualityChecks === false
138
146
  ? ''
139
147
  : html `<button class="btn btn-confirm" data-simface-action="confirm" @click=${this.handleConfirm}>${this.confirmLabel}</button>`}
@@ -141,7 +149,7 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
141
149
  : ''}
142
150
  ${this.captureState === 'error'
143
151
  ? html `
144
- <button class="btn btn-primary" data-simface-action="retry" @click=${this.beginCapture}>Try again</button>
152
+ <button class="btn btn-primary" data-simface-action="retry" @click=${this.beginCapture}>${this.retryLabel}</button>
145
153
  `
146
154
  : ''}
147
155
  </div>
@@ -157,12 +165,15 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
157
165
  this.feedbackMessage = 'Requesting camera access...';
158
166
  this.feedbackTone = 'neutral';
159
167
  const options = normalizeCaptureOptions({
160
- presentation: 'embedded',
161
168
  capturePreference: this.capturePreference,
162
169
  allowMediaPickerFallback: this.allowMediaPickerFallback,
163
- label: this.label,
164
- confirmLabel: this.confirmLabel,
165
- });
170
+ }, this);
171
+ options.label = this.label;
172
+ options.idleFeedbackLabel = this.idleFeedbackLabel;
173
+ options.confirmLabel = this.confirmLabel;
174
+ options.captureLabel = this.captureLabel;
175
+ options.retakeLabel = this.retakeLabel;
176
+ options.retryLabel = this.retryLabel;
166
177
  const capabilities = await resolveCaptureCapabilities({
167
178
  capturePreference: options.capturePreference,
168
179
  });
@@ -328,7 +339,7 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
328
339
  this.clearPreviewUrl();
329
340
  this.captureState = 'idle';
330
341
  this.errorMessage = '';
331
- this.feedbackMessage = 'Start a capture to see camera guidance here.';
342
+ this.feedbackMessage = this.idleFeedbackLabel;
332
343
  this.feedbackTone = 'neutral';
333
344
  this.syncProgress(0);
334
345
  this.qualityResult = null;
@@ -393,7 +404,8 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
393
404
  SimFaceCapture.styles = css `
394
405
  :host {
395
406
  display: block;
396
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
407
+ font-family: 'Mulish', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
408
+ color: #212529;
397
409
  max-width: 400px;
398
410
  margin: 0 auto;
399
411
  text-align: center;
@@ -408,9 +420,9 @@ SimFaceCapture.styles = css `
408
420
  .container {
409
421
  position: relative;
410
422
  padding: 16px;
411
- border: 1px solid #e0e0e0;
423
+ border: 1px solid #E0E0E0;
412
424
  border-radius: 16px;
413
- background: #fafafa;
425
+ background: #F6F6F6;
414
426
  }
415
427
 
416
428
  .close-btn {
@@ -427,7 +439,7 @@ SimFaceCapture.styles = css `
427
439
  border: none;
428
440
  border-radius: 50%;
429
441
  background: rgba(0, 0, 0, 0.06);
430
- color: #49454f;
442
+ color: #5D5E5E;
431
443
  cursor: pointer;
432
444
  transition: background-color 0.15s;
433
445
  }
@@ -450,7 +462,7 @@ SimFaceCapture.styles = css `
450
462
 
451
463
  .capture-copy {
452
464
  margin: 0;
453
- color: #334155;
465
+ color: #5D5E5E;
454
466
  text-align: center;
455
467
  width: 100%;
456
468
  }
@@ -462,8 +474,8 @@ SimFaceCapture.styles = css `
462
474
  aspect-ratio: 3 / 4;
463
475
  border-radius: 22px;
464
476
  background:
465
- radial-gradient(circle at top, rgba(56, 189, 248, 0.16), transparent 30%),
466
- linear-gradient(180deg, #0f172a, #020617);
477
+ radial-gradient(circle at top, rgba(0, 179, 209, 0.16), transparent 30%),
478
+ linear-gradient(180deg, #1a1a1a, #0d0d0d);
467
479
  box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.2);
468
480
  }
469
481
 
@@ -495,7 +507,7 @@ SimFaceCapture.styles = css `
495
507
  }
496
508
 
497
509
  .guide-mask {
498
- fill: rgba(51, 65, 85, 0.75);
510
+ fill: rgba(42, 42, 42, 0.75);
499
511
  fill-rule: evenodd;
500
512
  }
501
513
 
@@ -509,7 +521,7 @@ SimFaceCapture.styles = css `
509
521
 
510
522
  .ring-progress {
511
523
  fill: none;
512
- stroke: #22c55e;
524
+ stroke: #00B3D1;
513
525
  stroke-width: 2.8;
514
526
  stroke-linecap: round;
515
527
  stroke-linejoin: round;
@@ -533,24 +545,25 @@ SimFaceCapture.styles = css `
533
545
  border: none;
534
546
  border-radius: 100px;
535
547
  font-size: 15px;
536
- font-weight: 600;
537
- letter-spacing: 0.02em;
548
+ font-weight: 700;
549
+ letter-spacing: 0.04em;
550
+ text-transform: var(--simface-button-text-transform, uppercase);
538
551
  cursor: pointer;
539
552
  transition: background-color 0.15s, box-shadow 0.15s;
540
553
  }
541
554
 
542
555
  .btn-primary {
543
- background: #2563eb;
556
+ background: #00B3D1;
544
557
  color: white;
545
558
  }
546
559
 
547
560
  .btn-primary:hover {
548
- background: #1d4ed8;
549
- box-shadow: 0 1px 3px rgba(37, 99, 235, 0.3);
561
+ background: #009DB8;
562
+ box-shadow: 0 1px 3px rgba(0, 179, 209, 0.3);
550
563
  }
551
564
 
552
565
  .btn-primary:disabled {
553
- background: #93c5fd;
566
+ background: #80D9E8;
554
567
  cursor: not-allowed;
555
568
  box-shadow: none;
556
569
  }
@@ -576,12 +589,12 @@ SimFaceCapture.styles = css `
576
589
  }
577
590
 
578
591
  .btn-secondary {
579
- background: #e5e7eb;
580
- color: #374151;
592
+ background: #E0E0E0;
593
+ color: #212529;
581
594
  }
582
595
 
583
596
  .btn-secondary:hover {
584
- background: #d1d5db;
597
+ background: #D0D0D0;
585
598
  }
586
599
 
587
600
  .quality-msg {
@@ -608,21 +621,21 @@ SimFaceCapture.styles = css `
608
621
  }
609
622
 
610
623
  .quality-neutral {
611
- background: #e2e8f0;
612
- color: #0f172a;
624
+ background: #E0E0E0;
625
+ color: #212529;
613
626
  }
614
627
 
615
628
  .quality-manual {
616
- background: #e0f2fe;
617
- color: #0f172a;
629
+ background: #E0F7FB;
630
+ color: #212529;
618
631
  }
619
632
 
620
633
  .spinner {
621
634
  display: inline-block;
622
635
  width: 24px;
623
636
  height: 24px;
624
- border: 3px solid #e5e7eb;
625
- border-top: 3px solid #2563eb;
637
+ border: 3px solid #E0E0E0;
638
+ border-top: 3px solid #00B3D1;
626
639
  border-radius: 50%;
627
640
  animation: spin 0.8s linear infinite;
628
641
  margin: 12px auto;
@@ -641,6 +654,9 @@ SimFaceCapture.styles = css `
641
654
  __decorate([
642
655
  property({ type: String })
643
656
  ], SimFaceCapture.prototype, "label", void 0);
657
+ __decorate([
658
+ property({ type: String, attribute: 'idle-feedback-label' })
659
+ ], SimFaceCapture.prototype, "idleFeedbackLabel", void 0);
644
660
  __decorate([
645
661
  property({ type: Boolean, reflect: true })
646
662
  ], SimFaceCapture.prototype, "embedded", void 0);
@@ -650,6 +666,15 @@ __decorate([
650
666
  __decorate([
651
667
  property({ type: String, attribute: 'confirm-label' })
652
668
  ], SimFaceCapture.prototype, "confirmLabel", void 0);
669
+ __decorate([
670
+ property({ type: String, attribute: 'capture-label' })
671
+ ], SimFaceCapture.prototype, "captureLabel", void 0);
672
+ __decorate([
673
+ property({ type: String, attribute: 'retake-label' })
674
+ ], SimFaceCapture.prototype, "retakeLabel", void 0);
675
+ __decorate([
676
+ property({ type: String, attribute: 'retry-label' })
677
+ ], SimFaceCapture.prototype, "retryLabel", void 0);
653
678
  __decorate([
654
679
  property({ type: String, attribute: 'capture-preference' })
655
680
  ], SimFaceCapture.prototype, "capturePreference", void 0);