@incodetech/prizma-core-screens 0.2.4 → 0.2.12

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 (243) hide show
  1. package/dist/BiometricConsentScreen.d.ts +95 -0
  2. package/dist/BiometricConsentScreen.js +91 -0
  3. package/dist/BiometricConsentStrings.d.ts +84 -0
  4. package/dist/BiometricConsentStrings.js +31 -0
  5. package/dist/CaptureScreen.d.ts +13 -0
  6. package/dist/CaptureScreen.js +2 -2
  7. package/dist/CurpEnterScreen.d.ts +7 -0
  8. package/dist/CurpEnterScreen.js +17 -3
  9. package/dist/CurpGenerateScreen.d.ts +8 -0
  10. package/dist/CurpGenerateScreen.js +28 -13
  11. package/dist/CurpLayout.css +8 -34
  12. package/dist/CurpStrings.d.ts +16 -2
  13. package/dist/DemoScreensLayout.css +16 -0
  14. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  15. package/dist/DemoScreensRedirectScreen.js +3 -2
  16. package/dist/DemoScreensStrings.d.ts +5 -0
  17. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  18. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  19. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  20. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  21. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  22. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  23. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  24. package/dist/DigitalIdUploadStrings.js +19 -0
  25. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  26. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  27. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  28. package/dist/EkybAddressDetailsScreen.d.ts +7 -0
  29. package/dist/EkybAddressDetailsScreen.js +25 -7
  30. package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
  31. package/dist/EkybBusinessDetailsScreen.js +23 -6
  32. package/dist/EkybCountryScreen.d.ts +13 -5
  33. package/dist/EkybCountryScreen.js +41 -10
  34. package/dist/EkybOwnersScreen.d.ts +8 -0
  35. package/dist/EkybOwnersScreen.js +26 -3
  36. package/dist/EkybStrings.d.ts +32 -10
  37. package/dist/EkycCountryCatalog.d.ts +4 -1
  38. package/dist/EkycCountryCatalog.js +5 -3
  39. package/dist/EkycCountryFormScreen.d.ts +12 -3
  40. package/dist/EkycCountryFormScreen.js +28 -10
  41. package/dist/EkycDetailsScreen.d.ts +7 -0
  42. package/dist/EkycDetailsScreen.js +21 -4
  43. package/dist/EkycStrings.d.ts +14 -2
  44. package/dist/ElectronicSignatureLayout.css +6 -0
  45. package/dist/EmailInputStrings.d.ts +11 -1
  46. package/dist/EmailInputVerifyScreen.d.ts +7 -0
  47. package/dist/EmailInputVerifyScreen.js +17 -3
  48. package/dist/ErrorScreensScreen.d.ts +5 -2
  49. package/dist/ErrorScreensScreen.js +15 -4
  50. package/dist/ErrorScreensStrings.d.ts +17 -2
  51. package/dist/ErrorScreensStrings.js +18 -10
  52. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  53. package/dist/FaceCaptureCapturingScreen.js +62 -2
  54. package/dist/FaceCaptureLayout.css +69 -4
  55. package/dist/FaceCaptureStrings.d.ts +23 -0
  56. package/dist/FaceMatchComparisonScreen.d.ts +3 -3
  57. package/dist/FaceMatchLayout.css +21 -12
  58. package/dist/FormScreen.css +30 -0
  59. package/dist/FormScreen.d.ts +28 -0
  60. package/dist/FormScreen.js +3 -2
  61. package/dist/FormsLongFormScreen.d.ts +8 -0
  62. package/dist/FormsLongFormScreen.js +34 -13
  63. package/dist/FormsStrings.d.ts +11 -1
  64. package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
  65. package/dist/GeolocationBrowserStepsScreen.js +7 -3
  66. package/dist/GeolocationStrings.d.ts +3 -0
  67. package/dist/IdCaptureLayout.css +205 -2
  68. package/dist/IdCaptureStageScreen.d.ts +47 -0
  69. package/dist/IdCaptureStageScreen.js +39 -3
  70. package/dist/IdCaptureStrings.d.ts +19 -0
  71. package/dist/ListScreen.d.ts +21 -0
  72. package/dist/ListScreen.js +7 -2
  73. package/dist/LoaderScreen.d.ts +7 -5
  74. package/dist/LoaderScreen.js +7 -5
  75. package/dist/LoadersStrings.d.ts +18 -5
  76. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  77. package/dist/OcrReviewErrorScreen.js +24 -0
  78. package/dist/OcrReviewLayout.css +24 -0
  79. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  80. package/dist/OcrReviewLoadingScreen.js +19 -0
  81. package/dist/OcrReviewScreen.d.ts +76 -0
  82. package/dist/OcrReviewScreen.js +65 -0
  83. package/dist/OcrReviewStrings.d.ts +47 -0
  84. package/dist/OcrReviewStrings.js +17 -0
  85. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  86. package/dist/OcrReviewSuccessScreen.js +24 -0
  87. package/dist/PermissionsAllowScreen.d.ts +8 -11
  88. package/dist/PermissionsAllowScreen.js +8 -11
  89. package/dist/PermissionsAskScreen.d.ts +41 -0
  90. package/dist/PermissionsAskScreen.js +43 -0
  91. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  92. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  93. package/dist/PermissionsLayout.css +47 -0
  94. package/dist/PermissionsStrings.d.ts +20 -9
  95. package/dist/PermissionsStrings.js +10 -4
  96. package/dist/PhoneInputLayout.css +10 -9
  97. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  98. package/dist/PhoneInputOtpScreen.js +26 -19
  99. package/dist/PhoneInputStrings.d.ts +22 -4
  100. package/dist/PhoneInputVerifyScreen.d.ts +8 -0
  101. package/dist/PhoneInputVerifyScreen.js +36 -7
  102. package/dist/QualifiedSignatureAcceptScreen.js +6 -1
  103. package/dist/QualifiedSignatureDocumentScreen.d.ts +17 -10
  104. package/dist/QualifiedSignatureDocumentScreen.js +22 -17
  105. package/dist/QualifiedSignatureLayout.css +76 -0
  106. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  107. package/dist/QualifiedSignatureResultScreen.js +17 -18
  108. package/dist/QualifiedSignatureStrings.d.ts +19 -2
  109. package/dist/ResultScreen.d.ts +8 -0
  110. package/dist/ResultScreen.js +14 -3
  111. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  112. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  113. package/dist/ReusabilityLayout.css +241 -0
  114. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  115. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  116. package/dist/ReusabilityStrings.d.ts +85 -0
  117. package/dist/ReusabilityStrings.js +37 -0
  118. package/dist/ReviewScreen.d.ts +38 -12
  119. package/dist/ReviewScreen.js +41 -8
  120. package/dist/ScreenLayout.css +382 -6
  121. package/dist/ScreenNav.css +8 -0
  122. package/dist/ScreenNav.d.ts +15 -2
  123. package/dist/ScreenNav.js +10 -3
  124. package/dist/VideoSelfieErrorScreen.js +1 -1
  125. package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
  126. package/dist/VideoSelfieIdCaptureScreen.js +45 -14
  127. package/dist/VideoSelfieLayout.css +467 -34
  128. package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
  129. package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
  130. package/dist/VideoSelfieSpeakScreen.js +19 -2
  131. package/dist/VideoSelfieStrings.d.ts +82 -13
  132. package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
  133. package/dist/VideoSelfieTurnHeadScreen.js +66 -0
  134. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  135. package/dist/VideoSelfieTutorialScreen.js +22 -5
  136. package/dist/VideoSelfieWaveScreen.d.ts +53 -0
  137. package/dist/VideoSelfieWaveScreen.js +62 -0
  138. package/dist/ViewfinderScreen.css +36 -0
  139. package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
  140. package/dist/WatchlistBusinessFormScreen.js +27 -13
  141. package/dist/WatchlistBusinessStrings.d.ts +21 -7
  142. package/dist/flows.d.ts +8 -0
  143. package/dist/flows.js +17 -0
  144. package/dist/index.d.ts +19 -1
  145. package/dist/index.js +14 -0
  146. package/dist/styles.css +3 -0
  147. package/dist/types.d.ts +3 -1
  148. package/package.json +79 -3
  149. package/samples/video-selfie-camera-wide.jpg +0 -0
  150. package/samples/video-selfie-room-wide.jpg +0 -0
  151. package/samples/video-selfie-room.png +0 -0
  152. package/src/BiometricConsentScreen.tsx +192 -0
  153. package/src/BiometricConsentStrings.ts +119 -0
  154. package/src/CaptureScreen.tsx +17 -0
  155. package/src/CurpEnterScreen.tsx +21 -4
  156. package/src/CurpGenerateScreen.tsx +46 -22
  157. package/src/CurpLayout.css +8 -34
  158. package/src/CurpStrings.ts +22 -2
  159. package/src/DemoScreensLayout.css +16 -0
  160. package/src/DemoScreensRedirectScreen.tsx +14 -6
  161. package/src/DemoScreensStrings.ts +5 -0
  162. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  163. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  164. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  165. package/src/DigitalIdUploadStrings.ts +88 -0
  166. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  167. package/src/DocumentCaptureIllustration.tsx +1 -1
  168. package/src/EkybAddressDetailsScreen.tsx +32 -4
  169. package/src/EkybBusinessDetailsScreen.tsx +23 -5
  170. package/src/EkybCountryScreen.tsx +45 -7
  171. package/src/EkybOwnersScreen.tsx +34 -3
  172. package/src/EkybStrings.ts +34 -10
  173. package/src/EkycCountryCatalog.ts +9 -4
  174. package/src/EkycCountryFormScreen.tsx +51 -12
  175. package/src/EkycDetailsScreen.tsx +22 -1
  176. package/src/EkycStrings.ts +14 -2
  177. package/src/ElectronicSignatureLayout.css +6 -0
  178. package/src/EmailInputStrings.ts +12 -1
  179. package/src/EmailInputVerifyScreen.tsx +21 -4
  180. package/src/ErrorScreensScreen.tsx +15 -4
  181. package/src/ErrorScreensStrings.ts +37 -12
  182. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  183. package/src/FaceCaptureLayout.css +69 -4
  184. package/src/FaceCaptureStrings.ts +24 -0
  185. package/src/FaceMatchComparisonScreen.tsx +3 -3
  186. package/src/FaceMatchLayout.css +21 -12
  187. package/src/FormScreen.css +30 -0
  188. package/src/FormScreen.tsx +34 -0
  189. package/src/FormsLongFormScreen.tsx +41 -13
  190. package/src/FormsStrings.ts +12 -1
  191. package/src/GeolocationBrowserStepsScreen.tsx +6 -3
  192. package/src/GeolocationStrings.ts +3 -0
  193. package/src/IdCaptureLayout.css +205 -2
  194. package/src/IdCaptureStageScreen.tsx +75 -0
  195. package/src/IdCaptureStrings.ts +19 -0
  196. package/src/ListScreen.tsx +58 -10
  197. package/src/LoaderScreen.tsx +7 -5
  198. package/src/LoadersStrings.ts +18 -5
  199. package/src/OcrReviewErrorScreen.tsx +53 -0
  200. package/src/OcrReviewLayout.css +24 -0
  201. package/src/OcrReviewLoadingScreen.tsx +48 -0
  202. package/src/OcrReviewScreen.tsx +190 -0
  203. package/src/OcrReviewStrings.ts +69 -0
  204. package/src/OcrReviewSuccessScreen.tsx +52 -0
  205. package/src/PermissionsAllowScreen.tsx +9 -10
  206. package/src/PermissionsAskScreen.tsx +105 -0
  207. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  208. package/src/PermissionsLayout.css +47 -0
  209. package/src/PermissionsStrings.ts +42 -12
  210. package/src/PhoneInputLayout.css +10 -9
  211. package/src/PhoneInputOtpScreen.tsx +23 -3
  212. package/src/PhoneInputStrings.ts +23 -4
  213. package/src/PhoneInputVerifyScreen.tsx +41 -5
  214. package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
  215. package/src/QualifiedSignatureDocumentScreen.tsx +31 -19
  216. package/src/QualifiedSignatureLayout.css +76 -0
  217. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  218. package/src/QualifiedSignatureStrings.ts +19 -2
  219. package/src/ResultScreen.tsx +33 -2
  220. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  221. package/src/ReusabilityLayout.css +241 -0
  222. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  223. package/src/ReusabilityStrings.ts +127 -0
  224. package/src/ReviewScreen.tsx +96 -27
  225. package/src/ScreenLayout.css +382 -6
  226. package/src/ScreenNav.css +8 -0
  227. package/src/ScreenNav.tsx +35 -5
  228. package/src/VideoSelfieErrorScreen.tsx +1 -1
  229. package/src/VideoSelfieIdCaptureScreen.tsx +125 -31
  230. package/src/VideoSelfieLayout.css +467 -34
  231. package/src/VideoSelfieSelfieCaptureScreen.tsx +110 -57
  232. package/src/VideoSelfieSpeakScreen.tsx +34 -15
  233. package/src/VideoSelfieStrings.ts +89 -14
  234. package/src/VideoSelfieTurnHeadScreen.tsx +162 -0
  235. package/src/VideoSelfieTutorialScreen.tsx +45 -23
  236. package/src/VideoSelfieWaveScreen.tsx +143 -0
  237. package/src/ViewfinderScreen.css +36 -0
  238. package/src/WatchlistBusinessFormScreen.tsx +35 -16
  239. package/src/WatchlistBusinessStrings.ts +21 -7
  240. package/src/flows.ts +25 -0
  241. package/src/index.ts +88 -3
  242. package/src/styles.css +3 -0
  243. package/src/types.ts +3 -1
@@ -1,15 +1,15 @@
1
1
  import React, { forwardRef, type ReactNode } from 'react';
2
2
  import {
3
3
  IconBankCard,
4
- IconIdFront,
5
4
  IconMessages,
6
5
  IconMicrophone,
7
6
  IconNumberedList,
8
- IconPassport,
7
+ IconPassportCapture,
9
8
  IconUser,
9
+ IconUserInformation,
10
10
  IconWavingHand,
11
11
  } from '@incodetech/prizma-core-icons';
12
- import { PrizmaListItem } from '@incodetech/prizma-core-web/list';
12
+ import { PrizmaList } from '@incodetech/prizma-core-web/list';
13
13
  import { PrizmaListScreen, type PrizmaListScreenProps } from './ListScreen';
14
14
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
15
15
  import type {
@@ -18,6 +18,7 @@ import type {
18
18
  VideoSelfieTutorialRowKind,
19
19
  } from './VideoSelfieStrings';
20
20
  import './ScreenLayout.css';
21
+ import './VideoSelfieLayout.css';
21
22
 
22
23
  /**
23
24
  * Journey beat → canonical bank glyph. ONE map, in one place — the same seven
@@ -27,9 +28,13 @@ import './ScreenLayout.css';
27
28
  */
28
29
  const ROW_ICONS: Record<VideoSelfieTutorialRowKind, ReactNode> = {
29
30
  recording: <IconMicrophone size="m" />,
30
- idFront: <IconIdFront size="m" />,
31
+ // `user-information` and `passport-capture`, NOT `id-front` and `passport`:
32
+ // the tutorial promises the steps the Stepper then counts off, and the
33
+ // Stepper's own sequence uses these two. Corrected 2026-09-30 — the tutorial
34
+ // was drawing a different ID glyph from the one the journey shows.
35
+ idFront: <IconUserInformation size="m" />,
31
36
  idBack: <IconBankCard size="m" />,
32
- passport: <IconPassport size="m" />,
37
+ passport: <IconPassportCapture size="m" />,
33
38
  frontCamera: <IconUser size="m" />,
34
39
  waveHand: <IconWavingHand size="m" />,
35
40
  questions: <IconNumberedList size="m" />,
@@ -74,6 +79,14 @@ export interface VideoSelfieTutorialScreenProps
74
79
  * NOT archetype A: A centres one illustration in the flexible middle; here the
75
80
  * middle is a seven-row list that must span the column and start at the top —
76
81
  * the distinction `ScreenLayout.css` draws between `__flex` and `__list`.
82
+ *
83
+ * The one class it carries, `video-selfie-tutorial`, exists so the module's
84
+ * stylesheet can reach this screen on a SHORT panel: seven rows fit the 844
85
+ * reference to the pixel (measured 497 of 497), and on a 640 or 693 one they
86
+ * paint straight through the CTA. `VideoSelfieLayout.css` lets them pan there,
87
+ * the same concession the Android settings walk-through already makes with
88
+ * `scrollList` — and `scrollList` itself is not used here because its
89
+ * Spacing/16 margin would take the reference below the 497 the rows need.
77
90
  */
78
91
  export const VideoSelfieTutorialScreen = forwardRef<
79
92
  HTMLDivElement,
@@ -86,12 +99,14 @@ export const VideoSelfieTutorialScreen = forwardRef<
86
99
  screenId = `video-selfie-tutorial-${documentType}`,
87
100
  overrides,
88
101
  onAction,
102
+ className,
89
103
  ...shell
90
104
  },
91
105
  ref,
92
106
  ) => (
93
107
  <PrizmaListScreen
94
108
  ref={ref}
109
+ className={['video-selfie-tutorial', className].filter(Boolean).join(' ')}
95
110
  title={strings.tutorial.title}
96
111
  subtitle={strings.tutorial.subtitle}
97
112
  ctaLabel={strings.tutorial.ctaLabel}
@@ -102,25 +117,32 @@ export const VideoSelfieTutorialScreen = forwardRef<
102
117
  onAction={onAction}
103
118
  {...shell}
104
119
  >
105
- {/* Core's LIST-WITH-ICON rows. `1.0`/`1.2` draw the steps as plain
106
- icon + label rows with NO card surface behind them, so
107
- `PrizmaCardListCommonIssues` was wrong — it gave every row a tinted
108
- card the design does not have (owner-reported 2026-08-19).
120
+ {/* Core's `PrizmaList`, `icons-chain` — the stepper-style list: a 40
121
+ squircle per step with a 2×8 connector running down to the next one,
122
+ which is exactly what `1.0` and `1.2` draw (`List` → `List item` →
123
+ `Icon / List item` → `stepIcon` + `bottomConnector`).
109
124
 
110
- The rows are Core's `PrizmaListItem` (`icon-m`), composed directly
111
- rather than through `PrizmaList`: the wrapper's icon variants cap at
112
- SIX slots and silently drop the rest, and this tutorial draws SEVEN
113
- steps. The <ul> carries only the variant's own Spacing/12 rhythm —
114
- no new value. CORE REQUEST: let `PrizmaList`'s icon variants take a
115
- seventh row. DEVIATIONS #VS-8.
116
- */}
117
- <ul className="video-selfie-tutorial-list">
118
- {strings.tutorial.rows[documentType].map((row) => (
119
- <li key={row.kind}>
120
- <PrizmaListItem variant="icon-m" label={row.label} icon={ROW_ICONS[row.kind]} />
121
- </li>
122
- ))}
123
- </ul>
125
+ It used to be a hand-rolled <ul> of bare `PrizmaListItem`s, for two
126
+ reasons that are both gone: the icon LIST variants capped at six
127
+ slots and this tutorial draws seven, and no list variant paired with
128
+ the `icon-connector` ROW variant at all, so a stepper list could
129
+ only be numbered. `icons-chain` was added to Core on 2026-09-30 with
130
+ seven slots, and closes both (#VS-8, #VS-16). The list declares no
131
+ layout of its own here — the rhythm, the squircle and the connector
132
+ are the variant's. */}
133
+ <PrizmaList
134
+ variant="icons-chain"
135
+ showItem3
136
+ showItem4
137
+ showItem5
138
+ showItem6
139
+ showItem7
140
+ items={strings.tutorial.rows[documentType].map((row) => ({
141
+ variant: 'icon-connector' as const,
142
+ label: row.label,
143
+ icon: ROW_ICONS[row.kind],
144
+ }))}
145
+ />
124
146
  </PrizmaListScreen>
125
147
  ),
126
148
  );
@@ -0,0 +1,143 @@
1
+ import React, { forwardRef, type ReactNode } from 'react';
2
+ import { PrizmaModalContent } from '@incodetech/prizma-core-web/modal';
3
+ import { PrizmaSelfieCapture } from '@incodetech/prizma-core-web/selfie-capture';
4
+ import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
5
+ import { PrizmaScreenShell, type PrizmaScreenShellProps } from './ScreenShell';
6
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
7
+ import type { VideoSelfieDocumentType, VideoSelfieStrings } from './VideoSelfieStrings';
8
+ import './ScreenLayout.css';
9
+ import './ViewfinderScreen.css';
10
+ import './VideoSelfieLayout.css';
11
+
12
+ /** The three beats the `👋 Wave` band draws. */
13
+ export type VideoSelfieWaveStage = 'waiting' | 'ready' | 'success';
14
+
15
+ export interface VideoSelfieWaveScreenProps
16
+ extends Omit<PrizmaScreenShellProps, 'dark' | 'title' | 'children'> {
17
+ /** Module copy — this screen reads `selfieCapture.wave`. */
18
+ strings: VideoSelfieStrings;
19
+ /** Which beat to render. Default `waiting`. */
20
+ stage?: VideoSelfieWaveStage;
21
+ /** Which document the journey collects — the Stepper's own `documentType`. */
22
+ documentType?: VideoSelfieDocumentType;
23
+ /** Live camera feed (<video>) or sample still (<img>), full-bleed behind
24
+ * the ring. */
25
+ camera?: ReactNode;
26
+ screenId?: string;
27
+ overrides?: PrizmaScreenOverrides;
28
+ onAction?: PrizmaScreenActionHandler;
29
+ }
30
+
31
+ /**
32
+ * Video Selfie — the hand-wave beat of the liveness sequence (Archetype D,
33
+ * GUIDELINES.md §6-D; always dark). NEW 2026-09-30, from the `👋 Wave` band of
34
+ * the full-flow frames; like the head turns, it had no screen at all.
35
+ *
36
+ * The composition is the liveness ring with the spoken steps' card under it:
37
+ * the feed runs full-bleed, the ring frames the face, and Core's Modal asks
38
+ * for the gesture with a CTA that only lights up once the stage has seen it
39
+ * (the frames' "5 second delay" note between the two). The verdict beat is the
40
+ * ring's own positive treatment plus the module's "Looking good!" — the SAME
41
+ * copy and the same mark `8.2` uses, because it is the same answer.
42
+ *
43
+ * The scrim/panel positioning is the recipe Document Capture's method sheet
44
+ * established (DEVIATIONS #DC-30): Core's safe presenter portals to
45
+ * `document.body`, which would escape the 390×844 frame and the host's
46
+ * `transform: scale()`, so the screen owns the panel and Core owns the card.
47
+ * The card is pinned back to LIGHT inside the dark shell for the reason
48
+ * `VideoSelfieSpeakScreen` documents (#VS-10).
49
+ *
50
+ * The hand that sweeps across the face is Core's, not this screen's:
51
+ * `PrizmaSelfieCapture` grew a `hint="wave"` axis on 2026-09-30 (owner: "we
52
+ * need the hand wave"), the face counterpart of `PrizmaIdCapture`'s animated
53
+ * hand hint, so the art and the choreography live where every other capture
54
+ * gesture already lives. The screen only says WHEN: the hand runs while the
55
+ * stage is waiting and stops the moment the wave has landed, which is the
56
+ * frames' own "5 second delay · Remove animation (Accessibility)" note.
57
+ * DEVIATIONS #VS-21.
58
+ */
59
+ export const VideoSelfieWaveScreen = forwardRef<HTMLDivElement, VideoSelfieWaveScreenProps>(
60
+ (
61
+ {
62
+ strings,
63
+ stage = 'waiting',
64
+ documentType = 'id',
65
+ camera,
66
+ screenId = `video-selfie-wave-${stage}`,
67
+ overrides,
68
+ onAction,
69
+ className,
70
+ ...shell
71
+ },
72
+ ref,
73
+ ) => {
74
+ const slice = strings.selfieCapture.wave;
75
+ const seen = stage === 'success';
76
+
77
+ return (
78
+ <PrizmaScreenShell
79
+ ref={ref}
80
+ dark
81
+ /* The camera owns the whole window, never a column of it — see the
82
+ contract note in `VideoSelfieLayout.css`. */
83
+ data-pcs-overlay="window"
84
+ className={className}
85
+ {...shell}
86
+ >
87
+ <div className="prizma-viewfinder__surface">{camera}</div>
88
+ <div className="video-selfie-stepper">
89
+ <PrizmaStepper
90
+ steps={7}
91
+ currentState={5}
92
+ documentType={documentType}
93
+ completedValueText={strings.stepperCompletedLabel}
94
+ />
95
+ </div>
96
+ <div
97
+ className={[
98
+ 'prizma-viewfinder__content',
99
+ overrides?.align === 'left' ? 'prizma-viewfinder__content--left' : '',
100
+ ]
101
+ .filter(Boolean)
102
+ .join(' ')}
103
+ >
104
+ <div className="video-selfie-band video-selfie-band--ring">
105
+ <PrizmaSelfieCapture
106
+ mode="overlay"
107
+ scrim
108
+ state={seen ? 'success' : 'filled'}
109
+ /* The hand demonstrates the gesture only while the stage is
110
+ still waiting for it. On `ready` the wave has landed and the
111
+ CTA lights up instead — the frames drop the animation at
112
+ exactly that beat. */
113
+ hint={stage === 'waiting' ? 'wave' : undefined}
114
+ showFeedback={false}
115
+ statusLabels={{ success: strings.selfieCapture.successLabel }}
116
+ />
117
+ </div>
118
+ </div>
119
+ {/* The card is gone on the verdict beat — the ring and the mark are the
120
+ whole answer there, exactly as the band's last frame draws it. */}
121
+ {seen ? null : (
122
+ <div data-prizma-theme="light" className="video-selfie-sheet-panel">
123
+ <PrizmaModalContent
124
+ title={slice.instruction}
125
+ showCloseIcon={false}
126
+ buttonGroupProps={{
127
+ orientation: 'vertical-v1',
128
+ showPrimary: true,
129
+ showSecondary: false,
130
+ primaryLabel: slice.doneLabel,
131
+ primaryDisabled: stage !== 'ready',
132
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
133
+ }}
134
+ />
135
+ </div>
136
+ )}
137
+ </PrizmaScreenShell>
138
+ );
139
+ },
140
+ );
141
+ VideoSelfieWaveScreen.displayName = 'VideoSelfieWaveScreen';
142
+
143
+ export default VideoSelfieWaveScreen;
@@ -13,6 +13,42 @@
13
13
  object-fit: cover;
14
14
  }
15
15
 
16
+ /* ── A feed has an ASPECT, and the plate has to match it ─────────────────────
17
+ * `object-fit: cover` is right for a live feed and wrong for a still: a phone
18
+ * plate is 390 x 844, and covering a 1440 x 820 browser window scales it 3.7x,
19
+ * so the subject fills the window and her face lands nowhere near the capture
20
+ * ring (Camila, 2026-10-09: "the image is now too zoomed in because full screen
21
+ * is not good for this image ratio"). A real desktop webcam does not hand the
22
+ * page a portrait frame; it hands it a landscape one.
23
+ *
24
+ * So the HOST may supply two plates and the viewfinder picks the one whose
25
+ * shape fits the shell it is actually being drawn at:
26
+ *
27
+ * <div className="prizma-viewfinder__surface">
28
+ * <img data-pcs-plate="tall" src={portrait} alt="" />
29
+ * <img data-pcs-plate="wide" src={landscape} alt="" />
30
+ * </div>
31
+ *
32
+ * Supply one and nothing changes — an untagged `<img>` or `<video>` is shown at
33
+ * every shape, which is every existing caller. A CONTAINER query, not a media
34
+ * one, for the reason `ScreenLayout.css` gives on the capture notice: the
35
+ * browser viewport is not the screen, and in Core Lab a 390 shell sits inside a
36
+ * 1400px window. The line is square: anything wider than it is tall takes the
37
+ * landscape plate. */
38
+ .prizma-viewfinder__surface > [data-pcs-plate='wide'] {
39
+ display: none;
40
+ }
41
+
42
+ @container pcs-screen (min-aspect-ratio: 1 / 1) {
43
+ .prizma-viewfinder__surface > [data-pcs-plate='tall'] {
44
+ display: none;
45
+ }
46
+
47
+ .prizma-viewfinder__surface > [data-pcs-plate='wide'] {
48
+ display: block;
49
+ }
50
+ }
51
+
16
52
  .prizma-viewfinder__content {
17
53
  position: relative;
18
54
  z-index: 1;
@@ -41,8 +41,9 @@ export interface WatchlistBusinessFormScreenProps
41
41
  * (GUIDELINES.md §6-E, `PrizmaFormScreen`).
42
42
  *
43
43
  * ONE screen for both purple form sections (`watchlist.default` and
44
- * `watchlist.filled`): measured against the 390 frames they share an identical
45
- * skeleton — nav → Title (H2, no subtitle) → Spacing/32 → Country dropdown,
44
+ * `watchlist.filled`), plus the `emptyError` state the GUIDELINES §6-E
45
+ * empty-submit rule adds (Elena 2026-09-30, see note 3): measured against the 390 frames they share an identical
46
+ * skeleton — nav → Title (H2 + the "fields marked with *" H5) → Spacing/32 → Country dropdown,
46
47
  * Business Name and Tax ID at Spacing/16 → primary CTA → tag — and differ ONLY
47
48
  * in field content and whether the CTA is live.
48
49
  *
@@ -57,11 +58,14 @@ export interface WatchlistBusinessFormScreenProps
57
58
  * and Core's `readOnly`, the other way to make a field inert, paints the
58
59
  * DISABLED grey surface on states Figma draws white (DEVIATIONS #PI-6).
59
60
  * Neither section greys anything, so `disabled` is set nowhere.
60
- * 3. The CTA state comes from the SECTION, not from the values: Figma greys
61
- * "Continue" on `default` and lights it on `filled` — and `filled` leaves Tax
62
- * ID empty, which is how we know the tax identifier does not gate the lookup
63
- * (DEVIATIONS #WB-2). Deriving `ctaDisabled` from the values would have
64
- * disagreed with the design.
61
+ * 3. The CTA state comes from the SECTION, not from the values. Under the
62
+ * GUIDELINES §6-E empty-submit rule (Elena 2026-09-30) "Continue" is live
63
+ * on the resting `default` form, so the user can press it, and greys ONLY
64
+ * on `emptyError`, the state that press leads to: Country and Business
65
+ * Name render empty (no placeholder) in Core's error state with
66
+ * `requiredError`. Tax ID never errors — `filled` leaves it empty with the
67
+ * CTA live, which is how we know it does not gate the lookup (DEVIATIONS
68
+ * #WB-2).
65
69
  * 4. The country flag is Core's canonical raster (24×18, radius 2) in the
66
70
  * dropdown's LEADING slot — `PrizmaCountryFlag` composed into
67
71
  * `PrizmaDropdown.renderLeading`, which exists for exactly this case and
@@ -95,9 +99,11 @@ export const WatchlistBusinessFormScreen = forwardRef<
95
99
  ref,
96
100
  ) => {
97
101
  const copy = strings.form;
98
- // `default` draws the CTA greyed, `filled` draws it live. The section is the
99
- // spec, not the field contents (see note 3 above).
100
- const empty = state === 'default';
102
+ // The CTA greys only after an empty submit; the resting form keeps it live
103
+ // so that submit can happen. The section is the spec, not the field
104
+ // contents (see note 3 above).
105
+ const emptyError = state === 'emptyError';
106
+ const errorText = emptyError ? strings.requiredError : undefined;
101
107
 
102
108
  // The label is the country NAME only. The flag is not part of it — it rides
103
109
  // the dropdown's leading slot below, where Core paints the raster.
@@ -112,7 +118,7 @@ export const WatchlistBusinessFormScreen = forwardRef<
112
118
  title={copy.title}
113
119
  subtitle={copy.subtitle}
114
120
  ctaLabel={copy.ctaLabel}
115
- ctaDisabled={empty}
121
+ ctaDisabled={emptyError}
116
122
  ctaLoadingLabel={copy.ctaLoadingLabel}
117
123
  /* Figma draws no verified tag on either form section, but GUIDELINES §1
118
124
  and the standing owner rule (#F-30, #PI-8) keep header AND footer on
@@ -127,9 +133,17 @@ export const WatchlistBusinessFormScreen = forwardRef<
127
133
  >
128
134
  <PrizmaDropdown
129
135
  label={copy.country.label}
130
- placeholder={copy.country.placeholder}
136
+ /* Country and Business Name are required and marked (Elena,
137
+ 2026-09-30); the H5 explains the asterisk. */
138
+ required
139
+ requiredHint={strings.requiredHint}
140
+ /* The empty-submit error draws the field with nothing in it, as the
141
+ `FormsFieldScreen` `emptyError` precedent does. */
142
+ placeholder={emptyError ? undefined : copy.country.placeholder}
131
143
  options={options}
132
- defaultValue={values?.country ?? null}
144
+ defaultValue={emptyError ? null : (values?.country ?? null)}
145
+ error={emptyError}
146
+ errorText={errorText}
133
147
  /* A country catalogue is long enough that Core's own search bar is
134
148
  the right behaviour; no purple section draws the menu open, so
135
149
  nothing about its appearance is being invented here. */
@@ -155,9 +169,13 @@ export const WatchlistBusinessFormScreen = forwardRef<
155
169
  />
156
170
  <PrizmaTextField
157
171
  label={copy.businessName.label}
158
- placeholder={copy.businessName.placeholder}
172
+ required
173
+ requiredHint={strings.requiredHint}
174
+ placeholder={emptyError ? undefined : copy.businessName.placeholder}
159
175
  autoComplete="organization"
160
- defaultValue={values?.businessName ?? ''}
176
+ defaultValue={emptyError ? '' : (values?.businessName ?? '')}
177
+ error={emptyError}
178
+ errorText={errorText}
161
179
  />
162
180
  <PrizmaTextField
163
181
  label={copy.taxId.label}
@@ -165,7 +183,8 @@ export const WatchlistBusinessFormScreen = forwardRef<
165
183
  autoComplete="off"
166
184
  autoCapitalize="characters"
167
185
  spellCheck={false}
168
- defaultValue={values?.taxId ?? ''}
186
+ /* Optional (#WB-2): an empty submit leaves it clean. */
187
+ defaultValue={emptyError ? '' : (values?.taxId ?? '')}
169
188
  />
170
189
  </PrizmaFormScreen>
171
190
  );
@@ -23,15 +23,18 @@
23
23
  * in field content and whether the CTA is live, which is exactly what
24
24
  * GUIDELINES §6-E means by "states are separate screens sharing the identical
25
25
  * layout":
26
- * - `default` — every field empty (the country dropdown on its placeholder),
27
- * CTA greyed
28
- * - `filled` — country selected and business name typed, CTA live
26
+ * - `default` — every field empty (the country dropdown on its placeholder),
27
+ * CTA live so the user can press it (GUIDELINES §6-E empty-submit rule)
28
+ * - `filled` — country selected and business name typed, CTA live
29
+ * - `emptyError` — the user pressed the CTA with the form empty: Country and
30
+ * Business Name render empty in Core's error state with `requiredError`,
31
+ * Tax ID stays clean, CTA greyed (Elena 2026-09-30)
29
32
  *
30
33
  * Note what `filled` does NOT do: it leaves **Tax ID empty** and still enables
31
34
  * the CTA, so the tax identifier is optional and does not gate the lookup
32
- * (DEVIATIONS #WB-2).
35
+ * (DEVIATIONS #WB-2). That is also why `emptyError` leaves Tax ID alone.
33
36
  */
34
- export type WatchlistBusinessFormState = 'default' | 'filled';
37
+ export type WatchlistBusinessFormState = 'default' | 'filled' | 'emptyError';
35
38
 
36
39
  /**
37
40
  * One selectable country of registration. Runtime data, never copy — the host
@@ -81,12 +84,23 @@ export interface WatchlistBusinessStrings {
81
84
  verifiedLabel: string;
82
85
  /** Accessible name for the logo nav row. */
83
86
  navLabel?: string;
87
+ /**
88
+ * The error row the REQUIRED fields (Country, Business Name) show on the
89
+ * `emptyError` form state ("This field is required"). Maps to the SDK's
90
+ * `dynamicForms.fieldRequired` key. GUIDELINES §6-E empty-submit rule.
91
+ */
92
+ requiredError: string;
93
+ /**
94
+ * Required-field marker ("*"), passed to Core's own `requiredHint` prop on
95
+ * Country and Business Name (Elena, 2026-09-30).
96
+ */
97
+ requiredHint?: string;
84
98
 
85
- /** watchlist.{default,filled} */
99
+ /** watchlist.{default,filled,emptyError} */
86
100
  form: {
87
101
  /** H2 ("Watchlist for business"). */
88
102
  title: string;
89
- /** H5 — the purple sections draw none, kept for hosts that want one. */
103
+ /** H5 ("All fields marked with * are required."), explains the asterisk. */
90
104
  subtitle?: string;
91
105
  country: WatchlistBusinessFieldStrings & {
92
106
  /** Search placeholder inside the open menu. */
package/src/flows.ts CHANGED
@@ -83,6 +83,10 @@ export interface FaceCaptureFlowVariant {
83
83
  ctaLoading?: boolean;
84
84
  /** FaceCaptureCapturingScreen — which of the 15 `capturing.*` sections. */
85
85
  state?: FaceCaptureCapturingState;
86
+ /** FaceCaptureCapturingScreen — raise the "Need more time?" offer over the
87
+ * feed. A modifier of `state`, not one of the fifteen: the fallback timer
88
+ * runs underneath whichever guidance is showing. */
89
+ extendTime?: boolean;
86
90
  /** FaceCaptureLoadingScreen — `loading.uploading` vs `loading.processing`. */
87
91
  stage?: 'uploading' | 'processing';
88
92
  /** FaceCaptureResultErrorScreen — the `*Exhausted` half of the error pairs. */
@@ -140,6 +144,14 @@ export const faceCaptureFlow: readonly FaceCaptureFlowStep[] = [
140
144
  /** The off-path screens: manual capture (offered when auto-capture is turned
141
145
  * off or gives up), the two error verdicts, and the connectivity error. */
142
146
  export const faceCaptureBranches: readonly FaceCaptureFlowStep[] = [
147
+ {
148
+ // A branch, not a step: it only appears when accessibility mode is ON for
149
+ // the flow AND the fallback timer is 20 s from expiring. It returns to the
150
+ // capturing states it came from, so it never "follows" anything.
151
+ screenId: 'face-capture-capturing-extend-time',
152
+ component: FaceCaptureCapturingScreen,
153
+ variant: { state: 'alignFace', extendTime: true },
154
+ },
143
155
  {
144
156
  screenId: 'face-capture-manual-capture',
145
157
  component: FaceCaptureManualCaptureScreen,
@@ -479,6 +491,10 @@ export interface IdCaptureFlowVariant {
479
491
  vertical?: boolean;
480
492
  /** IdCaptureStageScreen — which stage section. */
481
493
  state?: IdCaptureStageState;
494
+ /** IdCaptureStageScreen — raise the "Need more time?" offer over the feed.
495
+ * A modifier of `state`, not one of the eleven: the fallback timer runs
496
+ * underneath whichever stage is showing. */
497
+ extendTime?: boolean;
482
498
  /** IdCaptureStageScreen — the `*-manual` sections. */
483
499
  manual?: boolean;
484
500
  /** IdCaptureResultScreen — which verdict section. */
@@ -633,6 +649,15 @@ export const idCaptureBranches: readonly IdCaptureFlowStep[] = [
633
649
  screenId: 'id-capture-age-verification',
634
650
  component: IdCaptureAgeVerificationScreen,
635
651
  },
652
+ {
653
+ // A branch, not a step: it only appears when accessibility mode is ON for
654
+ // the flow AND the 30 s fallback timer is 20 s from expiring. It returns
655
+ // to the hint stage it came from, so it never "follows" anything — the
656
+ // same shape as Face Capture's `capturing-extend-time`.
657
+ screenId: 'id-capture-front-extend-time',
658
+ component: IdCaptureStageScreen,
659
+ variant: { state: 'hint', extendTime: true },
660
+ },
636
661
  {
637
662
  screenId: 'id-capture-front-manual',
638
663
  component: IdCaptureStageScreen,