@smileid/web-components 11.4.5 → 11.6.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.
Files changed (132) hide show
  1. package/dist/esm/DocumentCaptureScreens-DjSTdVP-.js +5398 -0
  2. package/dist/esm/DocumentCaptureScreens-DjSTdVP-.js.map +1 -0
  3. package/dist/esm/{Navigation-Bb7MPLE8.js → Navigation-6DH3vF4-.js} +28 -22
  4. package/dist/esm/Navigation-6DH3vF4-.js.map +1 -0
  5. package/dist/esm/{PoweredBySmileId-CxbaihMu.js → PoweredBySmileId-DoKwoPUd.js} +424 -6
  6. package/dist/esm/PoweredBySmileId-DoKwoPUd.js.map +1 -0
  7. package/dist/esm/SelfieCaptureScreens-CtX-4Tco.js +11470 -0
  8. package/dist/esm/SelfieCaptureScreens-CtX-4Tco.js.map +1 -0
  9. package/dist/esm/combobox.js +1 -1
  10. package/dist/esm/document.js +1 -1
  11. package/dist/esm/end-user-consent.js +713 -2
  12. package/dist/esm/end-user-consent.js.map +1 -1
  13. package/dist/esm/index-BqyuTk9f.js +1366 -0
  14. package/dist/esm/{index-C4RTMbgw.js.map → index-BqyuTk9f.js.map} +1 -1
  15. package/dist/esm/localisation.js +1 -1
  16. package/dist/esm/main.js +14 -14
  17. package/dist/esm/navigation.js +1 -1
  18. package/dist/esm/package-CjZI-cNQ.js +2540 -0
  19. package/dist/esm/package-CjZI-cNQ.js.map +1 -0
  20. package/dist/esm/selfie.js +1 -1
  21. package/dist/esm/smart-camera-web.js +81 -37
  22. package/dist/esm/smart-camera-web.js.map +1 -1
  23. package/dist/esm/totp-consent.js +731 -2
  24. package/dist/esm/totp-consent.js.map +1 -1
  25. package/dist/esm/validate.js +31 -0
  26. package/dist/esm/validate.js.map +1 -0
  27. package/dist/smart-camera-web.js +1513 -383
  28. package/dist/smart-camera-web.js.map +1 -1
  29. package/dist/types/main.d.ts +18 -1
  30. package/dist/types/validate.d.ts +21 -0
  31. package/lib/components/document/src/DocumentCaptureScreens.js +97 -18
  32. package/lib/components/document/src/assets/lottie.d.ts +12 -0
  33. package/lib/components/document/src/assets/svg-inline.d.ts +8 -0
  34. package/lib/components/document/src/document-auto-capture/DocumentAutoCapture.stories.js +75 -0
  35. package/lib/components/document/src/document-auto-capture/DocumentAutoCapture.tsx +1458 -0
  36. package/lib/components/document/src/document-auto-capture/README.md +73 -0
  37. package/lib/components/document/src/document-auto-capture/assets/Greenbook_Shimmer.svg +42 -0
  38. package/lib/components/document/src/document-auto-capture/assets/ID_Back_Shimmer.svg +8 -0
  39. package/lib/components/document/src/document-auto-capture/assets/ID_Front_Shimmer.svg +20 -0
  40. package/lib/components/document/src/document-auto-capture/assets/Passport-Shimmer.svg +143 -0
  41. package/lib/components/document/src/document-auto-capture/assets/shimmers.ts +21 -0
  42. package/lib/components/document/src/document-auto-capture/assets/svg-raw.d.ts +4 -0
  43. package/lib/components/document/src/document-auto-capture/components/CaptureButton.tsx +122 -0
  44. package/lib/components/document/src/document-auto-capture/components/Overlay.tsx +167 -0
  45. package/lib/components/document/src/document-auto-capture/components/TuningPanel.tsx +856 -0
  46. package/lib/components/document/src/document-auto-capture/constants/captureLayout.ts +58 -0
  47. package/lib/components/document/src/document-auto-capture/detection/cvErrorRecovery.ts +40 -0
  48. package/lib/components/document/src/document-auto-capture/detection/documentAspect.ts +20 -0
  49. package/lib/components/document/src/document-auto-capture/detection/qualityScoring.ts +35 -0
  50. package/lib/components/document/src/document-auto-capture/detection/seamRejection.ts +209 -0
  51. package/lib/components/document/src/document-auto-capture/detection/synthesisTiming.ts +10 -0
  52. package/lib/components/document/src/document-auto-capture/hooks/useCamera.ts +117 -0
  53. package/lib/components/document/src/document-auto-capture/hooks/useCardDetection.ts +3059 -0
  54. package/lib/components/document/src/document-auto-capture/index.ts +4 -0
  55. package/lib/components/document/src/document-auto-capture/theme.ts +40 -0
  56. package/lib/components/document/src/document-auto-capture/utils/debug.ts +25 -0
  57. package/lib/components/document/src/document-auto-capture/utils/opencvLoader.ts +86 -0
  58. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.tsx +327 -244
  59. package/lib/components/document/src/document-capture-review/DocumentCaptureReview.js +153 -189
  60. package/lib/components/document/src/document-capture-submission/DocumentCaptureSubmission.tsx +432 -0
  61. package/lib/components/document/src/document-capture-submission/index.js +3 -0
  62. package/lib/components/navigation/src/Navigation.js +27 -8
  63. package/lib/components/selfie/README.md +13 -0
  64. package/lib/components/selfie/src/SelfieCaptureScreens.js +56 -8
  65. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieCapture.tsx +684 -0
  66. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieConsent.tsx +71 -0
  67. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieSubmission.tsx +181 -0
  68. package/lib/components/selfie/src/enhanced-smartselfie-capture/OvalProgress.tsx +87 -0
  69. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/Icon.svg +8 -0
  70. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/accessories.svg +77 -0
  71. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/active_liveness_animation.lottie +0 -0
  72. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device.svg +12 -0
  73. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device_orientation.lottie +0 -0
  74. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/good.svg +52 -0
  75. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/id-card.svg +9 -0
  76. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/illustrations.tsx +852 -0
  77. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/instructions-img.svg +3 -0
  78. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/multiple-faces.svg +69 -0
  79. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/person.svg +6 -0
  80. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/phone.svg +8 -0
  81. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/poor-lighting.svg +53 -0
  82. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/too_dark_animation.lottie +0 -0
  83. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ActiveLivenessOverlay.tsx +226 -0
  84. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/AlertDisplay.tsx +38 -0
  85. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/BackNavigation.tsx +45 -0
  86. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CameraPreview.tsx +96 -0
  87. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureControls.tsx +97 -0
  88. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureGuidelines.tsx +374 -0
  89. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ConsentView.tsx +460 -0
  90. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/SubmissionView.tsx +426 -0
  91. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/index.ts +3 -0
  92. package/lib/components/selfie/src/enhanced-smartselfie-capture/constants.ts +23 -0
  93. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/index.ts +2 -0
  94. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useCamera.ts +238 -0
  95. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useFaceCapture.ts +1075 -0
  96. package/lib/components/selfie/src/enhanced-smartselfie-capture/index.ts +1 -0
  97. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/alertMessages.ts +20 -0
  98. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/canvas.ts +108 -0
  99. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/faceDetection.ts +545 -0
  100. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageCapture.ts +66 -0
  101. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageQuality.ts +151 -0
  102. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/index.ts +5 -0
  103. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/mediapipeManager.ts +215 -0
  104. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +24 -1
  105. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +2 -2
  106. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +15 -7
  107. package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +4 -6
  108. package/lib/components/signature-pad/package.json +1 -1
  109. package/lib/components/smart-camera-web/src/README.md +11 -0
  110. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +89 -8
  111. package/lib/components/totp-consent/src/TotpConsent.js +1 -1
  112. package/lib/domain/localisation/index.js +2 -2
  113. package/package.json +9 -5
  114. package/dist/esm/DocumentCaptureScreens-D2G0NOQr.js +0 -4147
  115. package/dist/esm/DocumentCaptureScreens-D2G0NOQr.js.map +0 -1
  116. package/dist/esm/EndUserConsent-uHfA3txP.js +0 -717
  117. package/dist/esm/EndUserConsent-uHfA3txP.js.map +0 -1
  118. package/dist/esm/Navigation-Bb7MPLE8.js.map +0 -1
  119. package/dist/esm/PoweredBySmileId-CxbaihMu.js.map +0 -1
  120. package/dist/esm/SelfieCaptureScreens-Dr7VzON7.js +0 -7651
  121. package/dist/esm/SelfieCaptureScreens-Dr7VzON7.js.map +0 -1
  122. package/dist/esm/TotpConsent-Depzg0ti.js +0 -734
  123. package/dist/esm/TotpConsent-Depzg0ti.js.map +0 -1
  124. package/dist/esm/index-C4RTMbgw.js +0 -1360
  125. package/dist/esm/package-D6YrpMcO.js +0 -565
  126. package/dist/esm/package-D6YrpMcO.js.map +0 -1
  127. package/dist/esm/styles-BTEClL7R.js +0 -419
  128. package/dist/esm/styles-BTEClL7R.js.map +0 -1
  129. /package/lib/components/document/src/assets/lottie/{taking photo of green book passport.lottie → greenbook.lottie} +0 -0
  130. /package/lib/components/document/src/assets/lottie/{taking photo of ID FLIP 2D.lottie → id-card-flip.lottie} +0 -0
  131. /package/lib/components/document/src/assets/lottie/{taking photo of ID.lottie → id-card.lottie} +0 -0
  132. /package/lib/components/document/src/assets/lottie/{taking photo of passport 2.lottie → passport.lottie} +0 -0
@@ -0,0 +1,856 @@
1
+ import { useState } from 'preact/hooks';
2
+ import type { FunctionComponent } from 'preact';
3
+
4
+ interface TuningSettings {
5
+ deviceType: string;
6
+ cropToCard: boolean;
7
+ cropPadding: number;
8
+ useDynamicBorder: boolean;
9
+ edgeDensityThreshold: number;
10
+ gridCellRatio: number;
11
+ blurThreshold: number;
12
+ glareThreshold: number;
13
+ stabilityThreshold: number;
14
+ cropToContour?: boolean;
15
+ previewCropPadding?: number;
16
+ minFillPercent?: number;
17
+ maxFillPercent?: number;
18
+ autoCannySigma?: number;
19
+ chromaEdgeFusion?: boolean;
20
+ chromaCannyLow?: number;
21
+ chromaCannyHigh?: number;
22
+ mobileRegionFallback?: boolean;
23
+ idAspectTolerance?: number;
24
+ bookDocAspectTolerance?: number;
25
+ minFillRatio?: number;
26
+ chromaContentGate?: boolean;
27
+ minChromaContent?: number;
28
+ seamRejectEnabled?: boolean;
29
+ houghThreshold?: number;
30
+ houghMinLengthRatio?: number;
31
+ houghMaxLineGap?: number;
32
+ seamMaxHoughLines?: number;
33
+ lowClutterEdgeDensity?: number;
34
+ cannyHighMinLowClutter?: number;
35
+ captureGridMinCells?: number;
36
+ chromaMaskFallback?: boolean;
37
+ chromaMaskThreshold?: number;
38
+ chromaMaskMinFrac?: number;
39
+ chromaMaskMaxFrac?: number;
40
+ gateDecayEnabled?: boolean;
41
+ docFillEmaAlpha?: number;
42
+ fillHysteresis?: number;
43
+ targetProcessingFps?: number;
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ interface TuningDebugInfo {
48
+ rejectReason?: string;
49
+ procFps?: number | string;
50
+ edgeDensity?: number | string;
51
+ texture?: number | string;
52
+ quadrants?: string;
53
+ blur?: number;
54
+ glare?: number | string;
55
+ docFill?: number | string;
56
+ canny?: string;
57
+ contourSource?: string;
58
+ aspect?: number | string;
59
+ chroma?: number | string;
60
+ quality?: number | string;
61
+ houghLines?: number | string;
62
+ seamRejected?: boolean | string;
63
+ seamClutter?: boolean | string;
64
+ chromaMaskFrac?: number | string;
65
+ chromaMaskArea?: number | string;
66
+ chromaMaskFill?: number | string;
67
+ chromaMaskAspect?: number | string;
68
+ chromaMaskWall?: number | string;
69
+ cvError?: string;
70
+ cvErrors?: number | string;
71
+ cvRecovery?: string;
72
+ chromaError?: string;
73
+ chromaStatus?: string;
74
+ [key: string]: unknown;
75
+ }
76
+
77
+ interface TuningPanelProps {
78
+ settings: TuningSettings;
79
+ updateSetting: (key: string, value: unknown) => void;
80
+ debugInfo?: TuningDebugInfo | null;
81
+ }
82
+
83
+ export const TuningPanel: FunctionComponent<TuningPanelProps> = ({
84
+ settings,
85
+ updateSetting,
86
+ debugInfo,
87
+ }) => {
88
+ const [isOpen, setIsOpen] = useState(false);
89
+
90
+ // Default is minimized for UX, expand for dev
91
+
92
+ if (!isOpen) {
93
+ return (
94
+ <div style={{ position: 'absolute', bottom: 10, right: 10, zIndex: 99 }}>
95
+ <button
96
+ onClick={() => setIsOpen(true)}
97
+ style={{
98
+ background: '#333',
99
+ color: '#fff',
100
+ border: '1px solid #555',
101
+ padding: '8px 12px',
102
+ borderRadius: '4px',
103
+ }}
104
+ >
105
+ ⚙️ Settings
106
+ </button>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div
113
+ style={{
114
+ position: 'absolute',
115
+ bottom: 0,
116
+ left: 0,
117
+ right: 0,
118
+ backgroundColor: 'rgba(20,20,20,0.95)',
119
+ padding: '20px',
120
+ color: 'white',
121
+ display: 'flex',
122
+ flexDirection: 'column',
123
+ gap: '12px',
124
+ borderTop: '1px solid #444',
125
+ maxHeight: '40vh',
126
+ overflowY: 'auto',
127
+ zIndex: 100,
128
+ }}
129
+ >
130
+ <div
131
+ style={{
132
+ display: 'flex',
133
+ justifyContent: 'space-between',
134
+ alignItems: 'center',
135
+ }}
136
+ >
137
+ <h4 style={{ margin: 0, color: '#aaa' }}>
138
+ Live Tuning
139
+ <span
140
+ style={{
141
+ marginLeft: '10px',
142
+ fontSize: '0.7rem',
143
+ color: '#0f0',
144
+ border: '1px solid #0f0',
145
+ padding: '2px 6px',
146
+ borderRadius: '4px',
147
+ }}
148
+ >
149
+ {settings.deviceType}
150
+ </span>
151
+ </h4>
152
+ <button
153
+ onClick={() => setIsOpen(false)}
154
+ style={{
155
+ background: 'transparent',
156
+ border: 'none',
157
+ color: '#888',
158
+ fontSize: '1.2rem',
159
+ }}
160
+ >
161
+ ×
162
+ </button>
163
+ </div>
164
+
165
+ {/* Live Metrics */}
166
+ <div
167
+ style={{
168
+ display: 'grid',
169
+ gridTemplateColumns: '1fr 1fr',
170
+ gap: '10px',
171
+ fontSize: '0.8rem',
172
+ color: '#888',
173
+ }}
174
+ >
175
+ <div style={{ gridColumn: '1 / -1' }}>
176
+ Blocking check:{' '}
177
+ <span
178
+ style={{
179
+ color: debugInfo?.rejectReason?.startsWith('✓') ? '#0f0' : '#fd5',
180
+ fontWeight: 'bold',
181
+ }}
182
+ >
183
+ {debugInfo?.rejectReason ?? '—'}
184
+ </span>
185
+ </div>
186
+ <div>
187
+ Proc FPS:{' '}
188
+ <span style={{ color: '#fff' }}>{debugInfo?.procFps ?? '—'}</span>
189
+ </div>
190
+ <div>
191
+ Edge Density:{' '}
192
+ <span style={{ color: '#fff' }}>
193
+ {debugInfo?.edgeDensity ?? '0'}%
194
+ </span>
195
+ </div>
196
+ <div>
197
+ Texture:{' '}
198
+ <span style={{ color: '#fff' }}>{debugInfo?.texture ?? '0'}</span>
199
+ </div>
200
+ <div>
201
+ Doc Fill:{' '}
202
+ <span style={{ color: '#fff' }}>{debugInfo?.docFill ?? '-'}%</span>
203
+ </div>
204
+ <div>
205
+ Blur Variance:{' '}
206
+ <span style={{ color: '#fff' }}>{debugInfo?.blur || 0}</span>
207
+ </div>
208
+ <div>
209
+ Glare %:{' '}
210
+ <span style={{ color: '#fff' }}>{debugInfo?.glare || 0}%</span>
211
+ </div>
212
+ <div>
213
+ Canny (lo/hi):{' '}
214
+ <span style={{ color: '#fff' }}>{debugInfo?.canny ?? '—'}</span>
215
+ </div>
216
+ <div>
217
+ Edge Src:{' '}
218
+ <span style={{ color: '#fff' }}>
219
+ {debugInfo?.contourSource ?? '—'}
220
+ </span>
221
+ </div>
222
+ <div>
223
+ Aspect:{' '}
224
+ <span style={{ color: '#fff' }}>{debugInfo?.aspect ?? '—'}</span>
225
+ </div>
226
+ <div>
227
+ Chroma:{' '}
228
+ <span style={{ color: '#fff' }}>{debugInfo?.chroma ?? '—'}</span>
229
+ </div>
230
+ <div>
231
+ Chroma Status:{' '}
232
+ <span style={{ color: '#fff' }}>
233
+ {debugInfo?.chromaStatus ?? '—'}
234
+ </span>
235
+ </div>
236
+ <div>
237
+ Quality:{' '}
238
+ <span style={{ color: '#0f0' }}>{debugInfo?.quality ?? '—'}</span>
239
+ </div>
240
+ <div>
241
+ Hough Lines:{' '}
242
+ <span style={{ color: '#fff' }}>{debugInfo?.houghLines ?? '—'}</span>
243
+ </div>
244
+ <div>
245
+ Seam Rejected:{' '}
246
+ <span style={{ color: debugInfo?.seamRejected ? '#f55' : '#fff' }}>
247
+ {debugInfo?.seamRejected == null
248
+ ? '—'
249
+ : String(debugInfo.seamRejected)}
250
+ </span>
251
+ </div>
252
+ <div>
253
+ Seam Clutter:{' '}
254
+ <span style={{ color: debugInfo?.seamClutter ? '#fb5' : '#fff' }}>
255
+ {debugInfo?.seamClutter == null
256
+ ? '—'
257
+ : String(debugInfo.seamClutter)}
258
+ </span>
259
+ </div>
260
+ <div style={{ gridColumn: '1 / -1' }}>
261
+ Chroma-Mask:{' '}
262
+ <span style={{ color: '#fff', fontSize: '0.7rem' }}>
263
+ {debugInfo?.chromaMaskFill == null
264
+ ? '—'
265
+ : `cover ${debugInfo.chromaMaskFrac}% · area ${debugInfo.chromaMaskArea}% · fill ${debugInfo.chromaMaskFill} · aspect ${debugInfo.chromaMaskAspect} · wall ${debugInfo.chromaMaskWall}`}
266
+ </span>
267
+ </div>
268
+ <div style={{ gridColumn: '1 / -1' }}>
269
+ Grid 3×3:{' '}
270
+ <span style={{ color: '#fff', fontSize: '0.7rem' }}>
271
+ {debugInfo?.quadrants ?? '-'}
272
+ </span>
273
+ </div>
274
+ <div style={{ gridColumn: '1 / -1' }}>
275
+ CV Errors:{' '}
276
+ <span style={{ color: '#fbb', fontSize: '0.7rem' }}>
277
+ {debugInfo?.cvErrors ?? 0}
278
+ {debugInfo?.cvRecovery ? ` (${debugInfo.cvRecovery})` : ''}
279
+ </span>
280
+ </div>
281
+ {debugInfo?.cvError && (
282
+ <div style={{ gridColumn: '1 / -1' }}>
283
+ CV Error:{' '}
284
+ <span
285
+ style={{
286
+ color: '#f88',
287
+ fontSize: '0.7rem',
288
+ wordBreak: 'break-word',
289
+ }}
290
+ >
291
+ {debugInfo.cvError}
292
+ </span>
293
+ </div>
294
+ )}
295
+ {debugInfo?.chromaError && (
296
+ <div style={{ gridColumn: '1 / -1' }}>
297
+ Chroma Error:{' '}
298
+ <span
299
+ style={{
300
+ color: '#f88',
301
+ fontSize: '0.7rem',
302
+ wordBreak: 'break-word',
303
+ }}
304
+ >
305
+ {debugInfo.chromaError}
306
+ </span>
307
+ </div>
308
+ )}
309
+ </div>
310
+
311
+ <hr style={{ borderColor: '#333', margin: '5px 0' }} />
312
+
313
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
314
+ Crop to Card
315
+ <input
316
+ type="checkbox"
317
+ checked={settings.cropToCard}
318
+ onInput={(e) => updateSetting('cropToCard', e.target.checked)}
319
+ />
320
+ </label>
321
+
322
+ {settings.cropToCard && (
323
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
324
+ <span>Crop Padding: {settings.cropPadding}%</span>
325
+ <input
326
+ type="range"
327
+ min="0"
328
+ max="50"
329
+ step="1"
330
+ value={settings.cropPadding}
331
+ onInput={(e) =>
332
+ updateSetting('cropPadding', Number(e.target.value))
333
+ }
334
+ />
335
+ </label>
336
+ )}
337
+
338
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
339
+ Dynamic Border Detection
340
+ <input
341
+ type="checkbox"
342
+ checked={settings.useDynamicBorder}
343
+ onInput={(e) => updateSetting('useDynamicBorder', e.target.checked)}
344
+ />
345
+ </label>
346
+
347
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
348
+ <span>
349
+ Doc Presence (Edge %): {settings.edgeDensityThreshold}{' '}
350
+ <em>(Lower = easier to pass)</em>
351
+ </span>
352
+ <input
353
+ type="range"
354
+ min="1"
355
+ max="15"
356
+ step="0.1"
357
+ value={settings.edgeDensityThreshold}
358
+ onInput={(e) =>
359
+ updateSetting('edgeDensityThreshold', Number(e.target.value))
360
+ }
361
+ />
362
+ </label>
363
+
364
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
365
+ <span>
366
+ Grid Cell Ratio: {settings.gridCellRatio}{' '}
367
+ <em>(Higher = stricter occlusion check)</em>
368
+ </span>
369
+ <input
370
+ type="range"
371
+ min="0.1"
372
+ max="1.0"
373
+ step="0.05"
374
+ value={settings.gridCellRatio}
375
+ onInput={(e) =>
376
+ updateSetting('gridCellRatio', Number(e.target.value))
377
+ }
378
+ />
379
+ </label>
380
+
381
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
382
+ <span>
383
+ Edge Sensitivity (σ): {settings.autoCannySigma}{' '}
384
+ <em>(Lower = detect fainter borders on plain backgrounds)</em>
385
+ </span>
386
+ <input
387
+ type="range"
388
+ min="0"
389
+ max="3"
390
+ step="0.05"
391
+ value={settings.autoCannySigma}
392
+ onInput={(e) =>
393
+ updateSetting('autoCannySigma', Number(e.target.value))
394
+ }
395
+ />
396
+ </label>
397
+
398
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
399
+ <span>
400
+ ID Aspect Tol: {settings.idAspectTolerance}{' '}
401
+ <em>(Lower = stricter; rejects screens/odd rectangles)</em>
402
+ </span>
403
+ <input
404
+ type="range"
405
+ min="0.05"
406
+ max="0.35"
407
+ step="0.01"
408
+ value={settings.idAspectTolerance as number}
409
+ onInput={(e) =>
410
+ updateSetting('idAspectTolerance', Number(e.target.value))
411
+ }
412
+ />
413
+ </label>
414
+
415
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
416
+ <span>
417
+ Book-Doc Aspect Tol: {settings.bookDocAspectTolerance}{' '}
418
+ <em>(Passport/greenbook; lower rejects ID cards/screens)</em>
419
+ </span>
420
+ <input
421
+ type="range"
422
+ min="0.05"
423
+ max="0.35"
424
+ step="0.01"
425
+ value={settings.bookDocAspectTolerance as number}
426
+ onInput={(e) =>
427
+ updateSetting('bookDocAspectTolerance', Number(e.target.value))
428
+ }
429
+ />
430
+ </label>
431
+
432
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
433
+ <span>
434
+ Min Fill Ratio: {settings.minFillRatio}{' '}
435
+ <em>(Higher = stricter rectangularity)</em>
436
+ </span>
437
+ <input
438
+ type="range"
439
+ min="0.5"
440
+ max="1"
441
+ step="0.01"
442
+ value={settings.minFillRatio as number}
443
+ onInput={(e) => updateSetting('minFillRatio', Number(e.target.value))}
444
+ />
445
+ </label>
446
+
447
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
448
+ Chroma Edge Fusion
449
+ <input
450
+ type="checkbox"
451
+ checked={Boolean(settings.chromaEdgeFusion)}
452
+ onInput={(e) => updateSetting('chromaEdgeFusion', e.target.checked)}
453
+ />
454
+ </label>
455
+
456
+ {Boolean(settings.chromaEdgeFusion) && (
457
+ <>
458
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
459
+ <span>Chroma Canny Low: {settings.chromaCannyLow}</span>
460
+ <input
461
+ type="range"
462
+ min="0"
463
+ max="100"
464
+ step="1"
465
+ value={settings.chromaCannyLow as number}
466
+ onInput={(e) =>
467
+ updateSetting('chromaCannyLow', Number(e.target.value))
468
+ }
469
+ />
470
+ </label>
471
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
472
+ <span>Chroma Canny High: {settings.chromaCannyHigh}</span>
473
+ <input
474
+ type="range"
475
+ min="0"
476
+ max="150"
477
+ step="1"
478
+ value={settings.chromaCannyHigh as number}
479
+ onInput={(e) =>
480
+ updateSetting('chromaCannyHigh', Number(e.target.value))
481
+ }
482
+ />
483
+ </label>
484
+ </>
485
+ )}
486
+
487
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
488
+ Mobile Region Fallback
489
+ <input
490
+ type="checkbox"
491
+ checked={Boolean(settings.mobileRegionFallback)}
492
+ onInput={(e) =>
493
+ updateSetting('mobileRegionFallback', e.target.checked)
494
+ }
495
+ />
496
+ </label>
497
+
498
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
499
+ Chroma Content Gate
500
+ <input
501
+ type="checkbox"
502
+ checked={Boolean(settings.chromaContentGate)}
503
+ onInput={(e) => updateSetting('chromaContentGate', e.target.checked)}
504
+ />
505
+ </label>
506
+
507
+ {Boolean(settings.chromaContentGate) && (
508
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
509
+ <span>
510
+ Min Chroma Content: {settings.minChromaContent}{' '}
511
+ <em>(Higher = reject more monochrome objects, e.g. keyboards)</em>
512
+ </span>
513
+ <input
514
+ type="range"
515
+ min="0"
516
+ max="50"
517
+ step="1"
518
+ value={settings.minChromaContent as number}
519
+ onInput={(e) =>
520
+ updateSetting('minChromaContent', Number(e.target.value))
521
+ }
522
+ />
523
+ </label>
524
+ )}
525
+
526
+ <label style={{ display: 'flex', justifyContent: 'space-between' }}>
527
+ Seam Rejection{' '}
528
+ <em>(reject quads framed by straight lines, e.g. parquet)</em>
529
+ <input
530
+ type="checkbox"
531
+ checked={Boolean(settings.seamRejectEnabled)}
532
+ onInput={(e) => updateSetting('seamRejectEnabled', e.target.checked)}
533
+ />
534
+ </label>
535
+
536
+ {Boolean(settings.seamRejectEnabled) && (
537
+ <>
538
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
539
+ <span>
540
+ Hough Threshold: {settings.houghThreshold}{' '}
541
+ <em>(Higher = only stronger straight lines count)</em>
542
+ </span>
543
+ <input
544
+ type="range"
545
+ min="0"
546
+ max="150"
547
+ step="1"
548
+ value={settings.houghThreshold as number}
549
+ onInput={(e) =>
550
+ updateSetting('houghThreshold', Number(e.target.value))
551
+ }
552
+ />
553
+ </label>
554
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
555
+ <span>
556
+ Hough Min Length Ratio: {settings.houghMinLengthRatio}{' '}
557
+ <em>(min line length as fraction of ROI short side)</em>
558
+ </span>
559
+ <input
560
+ type="range"
561
+ min="0.1"
562
+ max="0.9"
563
+ step="0.05"
564
+ value={settings.houghMinLengthRatio as number}
565
+ onInput={(e) =>
566
+ updateSetting('houghMinLengthRatio', Number(e.target.value))
567
+ }
568
+ />
569
+ </label>
570
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
571
+ <span>Hough Max Line Gap: {settings.houghMaxLineGap}</span>
572
+ <input
573
+ type="range"
574
+ min="0"
575
+ max="30"
576
+ step="1"
577
+ value={settings.houghMaxLineGap as number}
578
+ onInput={(e) =>
579
+ updateSetting('houghMaxLineGap', Number(e.target.value))
580
+ }
581
+ />
582
+ </label>
583
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
584
+ <span>
585
+ Seam Max Hough Lines: {settings.seamMaxHoughLines}{' '}
586
+ <em>(above this the scene is too cluttered — skip seam gate)</em>
587
+ </span>
588
+ <input
589
+ type="range"
590
+ min="10"
591
+ max="300"
592
+ step="5"
593
+ value={settings.seamMaxHoughLines as number}
594
+ onInput={(e) =>
595
+ updateSetting('seamMaxHoughLines', Number(e.target.value))
596
+ }
597
+ />
598
+ </label>
599
+ </>
600
+ )}
601
+
602
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
603
+ <span>
604
+ Low-Clutter Edge Density %: {settings.lowClutterEdgeDensity}{' '}
605
+ <em>(below this, drop the Canny floor to catch faint borders)</em>
606
+ </span>
607
+ <input
608
+ type="range"
609
+ min="0"
610
+ max="10"
611
+ step="0.5"
612
+ value={settings.lowClutterEdgeDensity as number}
613
+ onInput={(e) =>
614
+ updateSetting('lowClutterEdgeDensity', Number(e.target.value))
615
+ }
616
+ />
617
+ </label>
618
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
619
+ <span>
620
+ Canny High-Min (low clutter): {settings.cannyHighMinLowClutter}
621
+ </span>
622
+ <input
623
+ type="range"
624
+ min="20"
625
+ max="60"
626
+ step="1"
627
+ value={settings.cannyHighMinLowClutter as number}
628
+ onInput={(e) =>
629
+ updateSetting('cannyHighMinLowClutter', Number(e.target.value))
630
+ }
631
+ />
632
+ </label>
633
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
634
+ <span>
635
+ Capture Grid Min Cells: {settings.captureGridMinCells}{' '}
636
+ <em>(of 9; early-out only — distance is owned by Doc Fill %)</em>
637
+ </span>
638
+ <input
639
+ type="range"
640
+ min="1"
641
+ max="9"
642
+ step="1"
643
+ value={settings.captureGridMinCells as number}
644
+ onInput={(e) =>
645
+ updateSetting('captureGridMinCells', Number(e.target.value))
646
+ }
647
+ />
648
+ </label>
649
+
650
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
651
+ <span>
652
+ Processing FPS: {settings.targetProcessingFps}{' '}
653
+ <em>(throttle heavy CV; 60 = every frame)</em>
654
+ </span>
655
+ <input
656
+ type="range"
657
+ min="10"
658
+ max="60"
659
+ step="5"
660
+ value={settings.targetProcessingFps as number}
661
+ onInput={(e) =>
662
+ updateSetting('targetProcessingFps', Number(e.target.value))
663
+ }
664
+ />
665
+ </label>
666
+
667
+ <label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
668
+ <input
669
+ type="checkbox"
670
+ checked={settings.gateDecayEnabled === true}
671
+ onInput={(e) =>
672
+ updateSetting(
673
+ 'gateDecayEnabled',
674
+ (e.target as HTMLInputElement).checked,
675
+ )
676
+ }
677
+ />
678
+ <span>
679
+ Gate Decay (anti-flicker){' '}
680
+ <em>(soften progress on transient gate misses)</em>
681
+ </span>
682
+ </label>
683
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
684
+ <span>
685
+ Fill EMA α: {settings.docFillEmaAlpha}{' '}
686
+ <em>(lower = smoother distance; 1 = off)</em>
687
+ </span>
688
+ <input
689
+ type="range"
690
+ min="0.05"
691
+ max="1"
692
+ step="0.05"
693
+ value={settings.docFillEmaAlpha as number}
694
+ onInput={(e) =>
695
+ updateSetting('docFillEmaAlpha', Number(e.target.value))
696
+ }
697
+ />
698
+ </label>
699
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
700
+ <span>
701
+ Fill Hysteresis (±%): {settings.fillHysteresis}{' '}
702
+ <em>(deadband around the fill thresholds; 0 = off)</em>
703
+ </span>
704
+ <input
705
+ type="range"
706
+ min="0"
707
+ max="10"
708
+ step="1"
709
+ value={settings.fillHysteresis as number}
710
+ onInput={(e) =>
711
+ updateSetting('fillHysteresis', Number(e.target.value))
712
+ }
713
+ />
714
+ </label>
715
+
716
+ <label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
717
+ <input
718
+ type="checkbox"
719
+ checked={settings.chromaMaskFallback === true}
720
+ onInput={(e) =>
721
+ updateSetting(
722
+ 'chromaMaskFallback',
723
+ (e.target as HTMLInputElement).checked,
724
+ )
725
+ }
726
+ />
727
+ <span>
728
+ Chroma-Mask Fallback{' '}
729
+ <em>(colored card on neutral bg; may false-capture a rug)</em>
730
+ </span>
731
+ </label>
732
+ {settings.chromaMaskFallback === true && (
733
+ <>
734
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
735
+ <span>
736
+ Chroma-Mask Threshold: {settings.chromaMaskThreshold}{' '}
737
+ <em>(|a|+|b| cutoff to isolate colored pixels)</em>
738
+ </span>
739
+ <input
740
+ type="range"
741
+ min="5"
742
+ max="60"
743
+ step="1"
744
+ value={settings.chromaMaskThreshold as number}
745
+ onInput={(e) =>
746
+ updateSetting('chromaMaskThreshold', Number(e.target.value))
747
+ }
748
+ />
749
+ </label>
750
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
751
+ <span>
752
+ Chroma-Mask Min Area: {settings.chromaMaskMinFrac}{' '}
753
+ <em>(blob must cover at least this fraction of ROI)</em>
754
+ </span>
755
+ <input
756
+ type="range"
757
+ min="0.02"
758
+ max="0.3"
759
+ step="0.01"
760
+ value={settings.chromaMaskMinFrac as number}
761
+ onInput={(e) =>
762
+ updateSetting('chromaMaskMinFrac', Number(e.target.value))
763
+ }
764
+ />
765
+ </label>
766
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
767
+ <span>
768
+ Chroma-Mask Max Coverage: {settings.chromaMaskMaxFrac}{' '}
769
+ <em>
770
+ (above this the colored region IS the background — reject)
771
+ </em>
772
+ </span>
773
+ <input
774
+ type="range"
775
+ min="0.3"
776
+ max="0.95"
777
+ step="0.05"
778
+ value={settings.chromaMaskMaxFrac as number}
779
+ onInput={(e) =>
780
+ updateSetting('chromaMaskMaxFrac', Number(e.target.value))
781
+ }
782
+ />
783
+ </label>
784
+ </>
785
+ )}
786
+
787
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
788
+ <span>Sharpness Threshold: {settings.blurThreshold}</span>
789
+ <input
790
+ type="range"
791
+ min="10"
792
+ max="300"
793
+ value={settings.blurThreshold}
794
+ onInput={(e) =>
795
+ updateSetting('blurThreshold', Number(e.target.value))
796
+ }
797
+ />
798
+ </label>
799
+
800
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
801
+ <span>Glare Limit (%): {settings.glareThreshold}</span>
802
+ <input
803
+ type="range"
804
+ min="0.5"
805
+ max="20"
806
+ step="0.1"
807
+ value={settings.glareThreshold}
808
+ onInput={(e) =>
809
+ updateSetting('glareThreshold', Number(e.target.value))
810
+ }
811
+ />
812
+ </label>
813
+
814
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
815
+ <span>Stability Frames: {settings.stabilityThreshold}</span>
816
+ <input
817
+ type="range"
818
+ min="2"
819
+ max="30"
820
+ value={settings.stabilityThreshold}
821
+ onInput={(e) =>
822
+ updateSetting('stabilityThreshold', Number(e.target.value))
823
+ }
824
+ />
825
+ </label>
826
+
827
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
828
+ <span>Min Fill (%): {settings.minFillPercent}</span>
829
+ <input
830
+ type="range"
831
+ min="20"
832
+ max="95"
833
+ step="1"
834
+ value={settings.minFillPercent}
835
+ onInput={(e) =>
836
+ updateSetting('minFillPercent', Number(e.target.value))
837
+ }
838
+ />
839
+ </label>
840
+
841
+ <label style={{ display: 'flex', flexDirection: 'column' }}>
842
+ <span>Max Fill (%): {settings.maxFillPercent}</span>
843
+ <input
844
+ type="range"
845
+ min="50"
846
+ max="100"
847
+ step="1"
848
+ value={settings.maxFillPercent}
849
+ onInput={(e) =>
850
+ updateSetting('maxFillPercent', Number(e.target.value))
851
+ }
852
+ />
853
+ </label>
854
+ </div>
855
+ );
856
+ };