@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
@@ -13,6 +13,14 @@
13
13
  * spoken step) carry the band here.
14
14
  */
15
15
 
16
+ /* The air above the stepper band, named once because TWO rules depend on it:
17
+ * the band's own offset and the ring's, which counts down from Figma's y=188.
18
+ * Scoped to a shell that actually carries the band, so no other module's
19
+ * screen inherits a property it never reads. */
20
+ .prizma-screen-shell:has(> .video-selfie-stepper) {
21
+ --vs-top-reserve: max(var(--pcs-status-bar, 47px), var(--prizma-spacing-24));
22
+ }
23
+
16
24
  /* ── Journey stepper band ────────────────────────────────────────────────────
17
25
  * Figma draws the Stepper as the first thing under the OS status bar — no logo
18
26
  * row above it (`top content` frame, y=54, the Stepper 342 wide at x=24 on
@@ -42,7 +50,14 @@
42
50
  * ScreenLayout.css already reserves for the tag and shutter, which must sit
43
51
  * above the scrim for exactly the same reason. DEVIATIONS #VS-2. */
44
52
  z-index: 2;
45
- margin-top: var(--pcs-status-bar, 47px);
53
+ /* Clear the OS status bar, and NEVER sit flush against the top edge. A host
54
+ * that draws no phone chrome sets `--pcs-status-bar: 0px` — Core Lab's
55
+ * desktop panel does — and the band then started at pixel zero, hard against
56
+ * the browser bar (Camila, 2026-10-09: "the stepper is way too high and
57
+ * touches the screen border"). The floor is the content column's own
58
+ * Spacing/24 gutter, the same air every other screen has above its first
59
+ * block; on a phone the 47 reserve is larger and nothing moves. */
60
+ margin-top: var(--vs-top-reserve, var(--pcs-status-bar, 47px));
46
61
  padding-inline: var(--prizma-spacing-24);
47
62
  }
48
63
 
@@ -63,6 +78,183 @@
63
78
  z-index: 1;
64
79
  }
65
80
 
81
+ /* A band carrying the coaching bubble keeps the bubble's own height; the frame
82
+ * below is what gives ground on a short screen.
83
+ *
84
+ * `.video-selfie-band` is `flex: 1; min-height: 0`, so on a 360 × 640 panel the
85
+ * flex column took the whole shortfall out of THIS band: it collapsed to zero
86
+ * and the bubble — which is still painted — overflowed it in both directions,
87
+ * across the stepper above and the document frame below (Camila, 2026-10-07).
88
+ * `IdCaptureLayout.css` already names the rule for this exact column: the frame
89
+ * is the one part that shrinks, the band above it keeps its content height. Same
90
+ * rule, this module's class. */
91
+ .video-selfie-band:has(> .video-selfie-feedback) {
92
+ flex: 0 0 auto;
93
+ min-height: min-content;
94
+ }
95
+
96
+ /* …and the band UNDER the document stage never gives up the footer's own
97
+ * footprint, so the frame stops above the verified tag instead of sliding under
98
+ * it. `ScreenLayout.css` reserves `--pcs-footer-space` on
99
+ * `.prizma-screen-layout__content`; these are camera surfaces with no content
100
+ * column, so the reserve has to be claimed here, and claiming it as the BAND's
101
+ * minimum rather than as padding on the viewfinder is what keeps the reference
102
+ * untouched: at 390 the band is already ~100 tall, so a 72 floor is a no-op and
103
+ * the frame stays exactly where Figma centres it (y 172–627 of a 91–708 content
104
+ * area). On a 640-tall panel the floor binds, and the frame — the one part of
105
+ * this column that may get smaller (owner, 2026-08-18) — gives the height back
106
+ * through Core's own `flex-basis`, its aspect narrowing it to match. */
107
+ .video-selfie-stage + .video-selfie-band {
108
+ min-height: var(--pcs-footer-space);
109
+ }
110
+
111
+ /* ── The camera owns the window; only the HUD is a column ────────────────────
112
+ * The five camera screens carry `data-pcs-overlay="window"` on their shell.
113
+ * That attribute is the screens package's contract with a host that FRAMES a
114
+ * screen — Core Lab's desktop panel, which otherwise lays every screen inside
115
+ * the 800 x 840 white card Elena's eKYB frames draw. A form belongs in that
116
+ * card. A camera does not: boxed into it, the feed and its scrim were a narrow
117
+ * strip with white either side of them on a 1440 panel, the desktop version of
118
+ * the "phone pasted onto the tablet" her 2026-10-02 call already fixed for the
119
+ * tablet (Camila, 2026-10-08). `PermissionsAskScreen` set the same attribute
120
+ * first, for its dialog's dim.
121
+ *
122
+ * NOT fixed here: Face Capture's, ID Capture's and Document Capture's camera
123
+ * stages sit in the same card for the same reason. The rule reads as theirs
124
+ * too, but their screens have not been looked at on the panel — reported. */
125
+
126
+ /* ── The HUD stays in a column when the panel is wider than the reference ─────
127
+ * On a panel wider than 390 the camera feed and its scrim are full-bleed — that
128
+ * is what a camera does on a real device, and what the Lab's laid-out
129
+ * breakpoints draw (`bleed`, convert.ts). The copy over the feed is NOT: left to
130
+ * `width: 100%` the stepper ran the full 720 of a tablet and the coaching bubble
131
+ * with it, which reads as a stretched phone rather than a tablet (Camila,
132
+ * 2026-10-07).
133
+ *
134
+ * `--pcs-column` is the host's own property for this — the same contract as
135
+ * `--pcs-status-bar` — and it is already what `ScreenLayout`'s content and band
136
+ * read on a device panel: 390 on a phone, 468 on a tablet or desktop one. Here
137
+ * the two Spacing/24 gutters come off it, because these bands sit INSIDE
138
+ * `.prizma-viewfinder__content`'s own padding, so the result is exactly the
139
+ * content width the reference has (342 at 390, 420 on a tablet). Unset — a plain
140
+ * 390 screen, any SDK host — the calc resolves to 342 and nothing moves. */
141
+ .video-selfie-stepper > *,
142
+ .video-selfie-feedback,
143
+ .video-selfie-recording,
144
+ .video-selfie-turn {
145
+ max-width: calc(var(--pcs-column, 390px) - 2 * var(--prizma-spacing-24));
146
+ margin-inline: auto;
147
+ }
148
+
149
+ /* ── The face ring sits in the SAME place on every screen ────────────────────
150
+ * Figma puts `ring-uploading` at (27, 188) on every frame that draws it —
151
+ * `8.x`, `9.x`, the four head turns and the wave — and never moves it. Left
152
+ * to the flex band the ring drifted instead, because what sits UNDER it
153
+ * changes from screen to screen: an instruction on `8.0`, a spinner and a
154
+ * label on `8.1`, an arrow chip plus a line on the turns, nothing at all
155
+ * behind the wave's card. Centring a taller stack pushes the ring up
156
+ * (measured 150 / 165 / 185 across three screens where the frames say 188),
157
+ * and a capture window that moves between steps reads as a glitch.
158
+ *
159
+ * So the ring band is TOP-aligned and the offset is named once. 188 is
160
+ * Figma's; 84 is where the content column starts here (the status-bar reserve
161
+ * plus the 37 stepper band); 40 is Core's own padding above the stage inside
162
+ * `PrizmaSelfieCapture`. What is left is the air this rule owns. Everything
163
+ * under the ring then flows below it, which is what the frames draw.
164
+ * DEVIATIONS #VS-24. */
165
+ .video-selfie-band--ring {
166
+ justify-content: flex-start;
167
+ /* 188 is Figma's, and on a panel SHORTER than the 844 the frames are drawn
168
+ * against it is read proportionally: 154 at 693, 143 at 640. The reference
169
+ * and anything taller keep the literal 188, which is what `min()` guarantees.
170
+ * Fixed, it was 29% of a 640 panel — the ring plus its status block ran up to
171
+ * 67px past the fold (Camila, 2026-10-07), and the air above a ring is the
172
+ * first thing that should give. */
173
+ padding-top: max(
174
+ 0px,
175
+ calc(
176
+ min(188px, 100cqh * 188 / 844) - var(--vs-top-reserve, var(--pcs-status-bar, 47px)) -
177
+ 37px - var(--prizma-spacing-40)
178
+ )
179
+ );
180
+ }
181
+
182
+ /* ── On a LANDSCAPE window the ring centres instead ──────────────────────────
183
+ * 188 is a portrait measurement: it is 22% down an 844 phone, and the capture
184
+ * block reads as sitting near the top with the instruction under it. Down a
185
+ * 1440 x 820 browser window the same offset leaves the whole bottom half empty
186
+ * and the composition reads as a phone screen pinned to the top of a desktop
187
+ * page (Camila, 2026-10-09).
188
+ *
189
+ * #VS-24's rule survives intact, and it is the reason this is an OFFSET and not
190
+ * `justify-content: center`. The rule is that the ring does not move BETWEEN
191
+ * screens — a capture window that jumps as the step advances reads as a glitch
192
+ * — and the stack under the ring is a different height on every one of them (an
193
+ * instruction here, a spinner and a label there, an arrow chip on the turns).
194
+ * Centring that stack would move the ring again, which is the exact bug #VS-24
195
+ * was written for. Centring the RING instead depends on two numbers only: the
196
+ * shell's height and Core's own 335 capture box. Every ring screen lands in the
197
+ * same place, and the copy flows below it as before. */
198
+ @container pcs-screen (min-aspect-ratio: 1 / 1) {
199
+ .video-selfie-band--ring {
200
+ padding-top: max(
201
+ 0px,
202
+ calc(
203
+ (100cqh - 335px) / 2 - var(--vs-top-reserve, var(--pcs-status-bar, 47px)) - 37px -
204
+ var(--prizma-spacing-40)
205
+ )
206
+ );
207
+ }
208
+ }
209
+
210
+ /* ── …and on a short panel the ring itself gives ground ──────────────────────
211
+ * Scaling the offset is not enough at 640: the stack below the ring is a title
212
+ * and a subtitle on the verdict beats ("Selfie not captured, try again") and an
213
+ * arrow plus an instruction on the turns, and all of it is content height the
214
+ * band cannot take back.
215
+ *
216
+ * So the RING shrinks, exactly the way `IdCaptureLayout.css` already lets the
217
+ * document frame shrink and for the same stated reason — the capture window may
218
+ * get smaller, that is not a constraint (owner, 2026-08-18). Core's stage is
219
+ * `width: 100%; aspect-ratio: 1`, so giving it a flex basis of its own width and
220
+ * letting the column take the shortfall out of its HEIGHT makes the aspect
221
+ * narrow it back: still a circle, still centred, just smaller. Everything inside
222
+ * it is already proportional (the ring is an SVG on a 335 viewBox, the
223
+ * silhouette is in %), so nothing else has to be told.
224
+ *
225
+ * 87.7cqw is Core's own capture width (`min(87.7cqw, 335px)` — 335 of the 390
226
+ * reference); it is named here only because a flex basis cannot read it off the
227
+ * element it is sizing. Outside the query not one of these declarations exists,
228
+ * so the reference stage is untouched. */
229
+ @container pcs-screen (max-height: 720px) {
230
+ .prizma-viewfinder__content:has(> .video-selfie-band--ring) {
231
+ min-height: 0;
232
+ }
233
+
234
+ .video-selfie-band--ring {
235
+ min-height: 0;
236
+ }
237
+
238
+ .video-selfie-band--ring > .prizma-selfie-capture {
239
+ flex: 0 1 auto;
240
+ min-height: 0;
241
+ }
242
+
243
+ .video-selfie-band--ring .prizma-selfie-capture__stage {
244
+ flex: 0 1 auto;
245
+ min-height: 0;
246
+ width: auto;
247
+ max-width: 100%;
248
+ flex-basis: min(335px, 87.7cqw);
249
+ }
250
+
251
+ /* The copy under the ring keeps its own height — the ring is what shrinks. */
252
+ .video-selfie-band--ring > .prizma-selfie-capture > :not(.prizma-selfie-capture__stage),
253
+ .video-selfie-band--ring > .video-selfie-turn {
254
+ flex-shrink: 0;
255
+ }
256
+ }
257
+
66
258
  /* ── Recording indicator (10.1 / 11.1 / 11.3 / 11.5) ─────────────────────────
67
259
  * A sound-wave glyph over the "Recording…" HUD line. The glyph ink is Core's
68
260
  * own Static camera ink — the token `PrizmaCaptureManual` uses for the shutter
@@ -96,6 +288,20 @@
96
288
  bottom: 0;
97
289
  z-index: 3;
98
290
  padding: var(--prizma-spacing-16);
291
+ /* The SCRIM is full-bleed; the card is not. On a panel wider than the
292
+ * reference the panel keeps the host's column (`--pcs-column`, see the HUD
293
+ * block above) and centres, so the card stays a card instead of a 720-wide
294
+ * strip. Its own Spacing/16 inset is untouched, which is what keeps the
295
+ * reference rendering byte-identical: at 390 the column IS the panel. */
296
+ max-width: var(--pcs-column, none);
297
+ margin-inline: auto;
298
+ }
299
+
300
+ /* Core's Modal caps itself too, so on a tablet's 468 column the card has room
301
+ * to spare inside the panel and was landing flush left. Centre it, same as the
302
+ * coaching bubble. */
303
+ .video-selfie-sheet-panel > * {
304
+ margin-inline: auto;
99
305
  }
100
306
 
101
307
  /* The confirmation dialog (10.4) is a true centred modal over a dimmed screen,
@@ -106,6 +312,33 @@
106
312
  align-items: center;
107
313
  }
108
314
 
315
+ /* The two verdict cards (`10.2` processing, `10.3` success) carry no buttons,
316
+ * so their content stack is two rows where the prompt's is three — left alone
317
+ * they came out ~70px shorter and the sheet jumped as the step advanced. The
318
+ * owner's call (2026-09-30) is that every card in the step is the same size,
319
+ * so the card takes the prompt's measure and centres its content in it.
320
+ *
321
+ * 184 is Figma's own `modal-light` height on `10.0` / `10.1`, which is what
322
+ * the prompt renders at; it is the module's geometry, not a token, and it is
323
+ * a MINIMUM — the card still grows if a translation needs more room.
324
+ * DEVIATIONS #VS-25. */
325
+ /* ⚠ The card, not the wrapper, has to do the centring. `height: 100%` on the
326
+ * wrapper resolved to `auto` — Core's `.prizma-modal` is not a flex container
327
+ * and its height is content-driven, so a percentage height had nothing to
328
+ * measure against and the block stayed pinned to the top (owner, 2026-09-30:
329
+ * "these are aligned to the top instead of centered"). Making the CARD the
330
+ * flex column is what actually distributes the extra height. */
331
+ .video-selfie-sheet-panel--status .prizma-modal {
332
+ min-height: 184px;
333
+ display: flex;
334
+ flex-direction: column;
335
+ justify-content: center;
336
+ }
337
+
338
+ .video-selfie-sheet-panel--status .prizma-modal__title {
339
+ text-align: center;
340
+ }
341
+
109
342
  /* The card's verdict mark (10.3 / 11.x success). The glyph itself is the
110
343
  * canonical bank icon Core's own `PrizmaTitle` uses for `status: success`
111
344
  * (`IconStatusPositive` at Title/Icon/Size); only the INK is set here, because
@@ -125,6 +358,36 @@
125
358
  height: var(--prizma-title-icon-size);
126
359
  }
127
360
 
361
+ /* ── The tutorial's seven rows on a short panel ──────────────────────────────
362
+ * The promise list fits the 844 reference to the pixel — measured 497 of the
363
+ * 497 the column leaves — so on a 693 or 640 panel the last two rows painted
364
+ * through the CTA and the verified tag (Camila, 2026-10-07).
365
+ *
366
+ * The concession is the one archetype G already ships and this module already
367
+ * uses on its Android settings walk-through: the ROWS pan, the title, the CTA
368
+ * band and the anchored tag hold still (`__list--scroll`, ScreenLayout.css).
369
+ * It is applied here, under a container query, rather than through the
370
+ * `scrollList` prop, because that variant also adds a Spacing/16 margin below
371
+ * the list — on the reference that is 16 of the 497 the rows need, so the prop
372
+ * would start clipping the very screen it is meant to leave alone.
373
+ *
374
+ * 720 is the same line the live cues use: the reference is 844, and anything
375
+ * at 640 or 693 is a short panel. */
376
+ @container pcs-screen (max-height: 720px) {
377
+ .video-selfie-tutorial .prizma-screen-layout__list {
378
+ overflow-y: auto;
379
+ /* `__list--scroll`'s own Spacing/16, so the row the clip cuts through is not
380
+ flush against the CTA. Inside the query it costs the reference nothing. */
381
+ margin-bottom: var(--prizma-spacing-16);
382
+ }
383
+
384
+ /* Rows keep their intrinsic height instead of being squeezed — the reason
385
+ `__list--scroll` carries the same declaration. */
386
+ .video-selfie-tutorial .prizma-screen-layout__list > * {
387
+ flex-shrink: 0;
388
+ }
389
+ }
390
+
128
391
  /* ── The OS settings walk-through (2.5) ──────────────────────────────────────
129
392
  * Two labelled sections inside archetype G's list slot: a numbered step list
130
393
  * and a plain note. CONTENT typography only — the offset above the block is
@@ -155,10 +418,49 @@
155
418
  color: var(--prizma-text-body-500-secondary);
156
419
  }
157
420
 
421
+ /* ── The document frame's own measure ────────────────────────────────────────
422
+ * `3.x` / `5.x` / `6.x` all draw `ID Capture Frame` at **286 × 455**, centred
423
+ * in the 390 column (52 either side) — and they keep that measure on every
424
+ * device section in the file: 320, 368, 390, 430 and the 892-wide Pixel Fold
425
+ * all show the same 286-wide frame, only the air around it changes.
426
+ *
427
+ * Core's `PrizmaIdCapture` is `width: 100%` + `aspect-ratio: 288/456`, so left
428
+ * alone it takes the whole content column: 342 × 541 at 390, which ran the
429
+ * frame down into the verified tag (owner-reported in the 320 sweep,
430
+ * 2026-09-28, and the reason the stage read broken at every width, 2026-09-30).
431
+ *
432
+ * So the MODULE caps it, exactly as the frames do. `min()` keeps the cap from
433
+ * turning into a floor: on a column narrower than the frame it gives ground
434
+ * instead of spilling, which is what the 320 sections show. The aspect ratio
435
+ * stays Core's. DEVIATIONS #VS-18. */
436
+ .video-selfie-stage {
437
+ /* Figma geometry, not a token: `ID Capture Frame` is 286 wide on every
438
+ device section of this module. */
439
+ width: min(286px, 100%);
440
+ /* The viewfinder column stretches its children, so a child that names its
441
+ own width lands flush LEFT — which put the frame 16px off the phone's
442
+ centre line while the status card floating on it stayed centred. */
443
+ margin-inline: auto;
444
+ }
445
+
446
+ /* `IdCaptureLayout.css` gives every vertical stage inside a viewfinder a
447
+ * `flex-basis` of "the full column over the aspect", so the frame can give up
448
+ * height on a short screen and the aspect then narrows it back (Camila,
449
+ * 2026-09-28). That machinery is right and stays — it just has to start from
450
+ * THIS module's capped width, not the whole column: fed 342 it computed a
451
+ * 541-tall frame and then clamped the width back to 286, which is how the
452
+ * stage ended up 286 × 541 instead of 286 × 455 and grew into the verified
453
+ * tag. Same formula, capped input. */
454
+ .prizma-viewfinder__content
455
+ > .video-selfie-stage.prizma-id-capture--vertical:not(.prizma-id-capture--poa)
456
+ .prizma-id-capture__frame {
457
+ flex-basis: calc(min(286px, 100cqw - 2 * var(--prizma-spacing-24)) * 456 / 288);
458
+ }
459
+
158
460
  /* ── The captured document inside the vertical frame ─────────────────────────
159
461
  * Groups 3 and 5 draw the ID UPRIGHT in a portrait frame: the card is rotated a
160
- * quarter turn so its long edge runs down the screen, exactly as `3.2` and
161
- * `5.2` show it (the licence's text reads bottom-to-top).
462
+ * quarter turn so its long edge runs down the screen, exactly as `3.1`/`3.2`
463
+ * and `5.1`/`5.2` show it (the licence's text reads bottom-to-top).
162
464
  *
163
465
  * The sample scans are landscape (328×202). Dropped into a portrait frame with
164
466
  * Core's `object-fit: cover` they scaled 4.2× to fill the height and lost their
@@ -166,40 +468,41 @@
166
468
  * zoomed in to fit height, when it should be 90 degrees to fit the frame").
167
469
  * Rotating instead of cropping keeps the whole document legible.
168
470
  *
169
- * `width`/`height` are swapped BEFORE the rotation because a rotated element
170
- * still lays out on its pre-transform box: the image is sized to the frame's
171
- * height × width, then turned, so it lands exactly frame-sized. No literal —
172
- * both are percentages of whatever box Core gives it. DEVIATIONS #VS-6.
173
- */
174
- /* The captured document is landscape (a card scan); the VERTICAL frame is
175
- * portrait. `object-fit: cover` cropped its sides away and blew it up 4.2× —
176
- * the band of static the owner reported. Figma turns the card a quarter turn
177
- * instead, so the whole document stays legible with its text running up the
178
- * side, exactly as `3.2` / `5.2` draw it.
179
- *
180
- * The image is sized to the frame's height × width BEFORE the turn, because a
471
+ * The image is sized to the frame's HEIGHT × WIDTH before the turn, because a
181
472
  * rotated element still lays out on its pre-transform box — so after the 90°
182
473
  * it lands exactly frame-sized. Both values are percentages of whatever box
183
- * Core gives it, so nothing is hardcoded. DEVIATIONS #VS-6. */
474
+ * Core gives it, so nothing is hardcoded.
475
+ *
476
+ * ⚠ The first pass centred that oversized box with `display: grid;
477
+ * place-items: center`, which does NOT centre it: a grid item WIDER than its
478
+ * area is clamped to the start edge (the browsers' overflow-safety rule). The
479
+ * card therefore laid out at the frame's top-left, and turning it around ITS
480
+ * centre threw it 75px right and 75px up — hanging off two sides of the frame
481
+ * and across the camera. That is the "ID shown in a very weird way" report
482
+ * (owner, 2026-09-30). Absolute centring has no such clamp, so the turn
483
+ * happens around the frame's real middle. DEVIATIONS #VS-6. */
184
484
  .prizma-id-capture--vertical .prizma-id-capture__still {
185
485
  /* A size container, so the image can be measured against the frame's OWN
186
486
  box — `cqh`/`cqw` below are the only way to swap width and height without
187
487
  writing either number down. The frame has definite dimensions, which is
188
488
  what `container-type: size` requires. */
189
489
  container-type: size;
190
- display: grid;
191
- place-items: center;
192
490
  overflow: hidden;
193
491
  }
194
492
 
195
493
  .prizma-id-capture--vertical .prizma-id-capture__still > img {
196
494
  /* Swapped on purpose: the image is laid out at the frame's HEIGHT × WIDTH,
197
- then turned a quarter, so it lands exactly frame-sized. */
495
+ then turned, so it lands exactly frame-sized. */
496
+ position: absolute;
497
+ inset: auto;
498
+ top: 50%;
499
+ left: 50%;
198
500
  width: 100cqh;
199
501
  height: 100cqw;
200
502
  max-width: none;
201
503
  object-fit: cover;
202
- transform: rotate(90deg);
504
+ /* Centre first, THEN turn — around the frame's own middle. */
505
+ transform: translate(-50%, -50%) rotate(90deg);
203
506
  transform-origin: center;
204
507
  }
205
508
 
@@ -222,26 +525,156 @@
222
525
  pointer-events: none;
223
526
  }
224
527
 
225
- .video-selfie-stage-status > * {
226
- padding: var(--prizma-spacing-16) var(--prizma-spacing-24);
528
+ /* The card itself: hugs its content, never wider than the document frame it
529
+ * floats on (Figma's widest, `6.1.3`'s three-line rejection, is 260 inside the
530
+ * 286 frame), and always dark — the camera stage never mode-flips. The mark
531
+ * stacks above the label because the content is Core's `PrizmaTitle`; the card
532
+ * only supplies the surface and the air around it. */
533
+ .video-selfie-stage-status__card {
534
+ box-sizing: border-box;
535
+ max-width: min(260px, 100%);
536
+ padding: var(--prizma-spacing-24);
227
537
  border-radius: var(--prizma-border-radius-medium);
228
- background: var(--prizma-surface-neutral-900-80-static);
538
+ background: var(--prizma-surface-neutral-900-static);
229
539
  }
230
540
 
231
- /* ── Tutorial step rows ──────────────────────────────────────────────────────
232
- * The <ul> Core's `PrizmaList` would normally supply. Composed here only
233
- * because the wrapper's icon variants cap at six rows and the tutorial draws
234
- * seven (#VS-8); the gap is the variant's OWN Spacing/12 — nothing new.
235
- */
236
- .video-selfie-tutorial-list {
541
+ /* Both pieces inside the card sit ON that dark surface, so their ink is the
542
+ * camera HUD's own Static white rather than the theme's primary — the same
543
+ * re-seat `SelfieCapture.css` does for its overlay status band. */
544
+ .video-selfie-stage-status__card .prizma-title__heading,
545
+ .video-selfie-stage-status__card .prizma-loading-spinner__title {
546
+ color: var(--prizma-text-body-0-static);
547
+ }
548
+
549
+ /* ── The coaching bubble above the document frame ────────────────────────────
550
+ * The five condition frames put `Capture feedback` at the TOP of the content,
551
+ * over the camera and above the frame. The band it sits in is already the
552
+ * flexible one; this only pins it to the band's top edge and lifts it over
553
+ * the stage's scrim (the trap `ViewfinderScreen.css` documents on `__intro`:
554
+ * the scrim is an absolutely positioned sibling with an escaping box-shadow,
555
+ * so unlifted content renders under an 80% veil). The bubble's own width,
556
+ * radius, ink and illustration are Core's. DEVIATIONS #VS-28. */
557
+ .video-selfie-feedback {
558
+ position: relative;
559
+ z-index: 1;
560
+ width: 100%;
561
+ /* Top of the band, which is directly under the stepper — where the five
562
+ condition frames seat it (bubble top y≈88 of 844). */
563
+ margin-block: 0 auto;
564
+ }
565
+
566
+ /* Core caps the pill itself at 400 (CaptureFeedback.css, FLAG-DOUBLE-MAXWIDTH),
567
+ * which is narrower than a tablet's 420 column — so inside the wrapper it has
568
+ * 20px to spare and was landing flush left, off the frame's centre line. Same
569
+ * centring `.video-selfie-stage` needs, for the same reason. */
570
+ .video-selfie-feedback > .prizma-capture-feedback {
571
+ margin-inline: auto;
572
+ }
573
+
574
+ /* ── The live cues go COMPACT on a small phone ───────────────────────────────
575
+ * `PrizmaCaptureFeedback` ships two templates on its own `Show Animation` axis:
576
+ * A, the 135px pill with the illustration beside the copy at Headline/H3, and
577
+ * B, a 72px pill with no illustration at Headline/H5 (CaptureFeedback.css,
578
+ * "Compact modifier"). The module's frames draw A, and A is what every screen
579
+ * with room keeps.
580
+ *
581
+ * It is the wrong template on a small phone. At 320 the pill's 70px
582
+ * illustration column plus its Spacing/16 gap take 86 of the 272 the content
583
+ * has — a third of the width spent on a decoration — so "Center your ID inside
584
+ * the frame" wrapped to three lines and the pill grew to 150; on the 640-tall
585
+ * panel the same pill is a quarter of the whole screen. Either way it ate the
586
+ * air the document frame needs, and the frame came down through it (Camila,
587
+ * 2026-10-07: "in smaller phones, the live cues should change to the compact
588
+ * version"). Template B says the same two lines in 72.
589
+ *
590
+ * A CONTAINER query, and in the SCREENS layer rather than CaptureFeedback.css,
591
+ * for the reason `ScreenLayout.css` gives on the capture notice: Core web
592
+ * components are width-agnostic by design, and the browser viewport is not the
593
+ * phone — in Core Lab a 320 shell sits inside a 1400px window, so `@media`
594
+ * would read the wrong number. `pcs-screen` is the shell's own container
595
+ * (ScreenShell.css).
596
+ *
597
+ * Two triggers, because two different things run out:
598
+ * · 340 wide — the line above the narrowest phone we support and below the
599
+ * next one, the same threshold the capture notice drops its glyph at: 320
600
+ * goes compact, 360 and 390 keep the illustration.
601
+ * · 720 tall — the reference is 844 and the frames are drawn against it. A
602
+ * panel at 640 or 693 has no air to spend on a 135px pill, however wide it
603
+ * is. 720 clears 844, 932 and the tablet's 1024.
604
+ *
605
+ * Nothing is re-skinned: the pill's fill, radius, stroke and shadow are Core's
606
+ * and identical between A and B (capture-feedback.md §4.4). What changes is
607
+ * exactly Template B's four switches. DEVIATIONS #VS-30. */
608
+ /* ⚠ ONE `or` condition, not two comma-separated queries. A container query
609
+ * names its container ONCE and the conditions go in a single boolean group:
610
+ * `pcs-screen (a), pcs-screen (b)` parses in Chrome and is a hard error in
611
+ * lightningcss, so the hub's production build failed on it while the dev server
612
+ * was fine. Any future multi-condition query in this package wants this shape. */
613
+ @container pcs-screen ((max-width: 340px) or (max-height: 720px)) {
614
+ .video-selfie-feedback .prizma-capture-feedback__illustration {
615
+ display: none;
616
+ }
617
+
618
+ /* Template B: body padding-bottom 12 → 16, itemSpacing 8 → 4. */
619
+ .video-selfie-feedback .prizma-capture-feedback__body {
620
+ padding: var(--prizma-spacing-16) 0;
621
+ gap: var(--prizma-spacing-4);
622
+ }
623
+
624
+ /* Title Headline/H3 → Headline/H5. */
625
+ .video-selfie-feedback .prizma-capture-feedback__title {
626
+ font-weight: var(--prizma-headline-h5-font-weight);
627
+ font-size: var(--prizma-headline-h5-font-size);
628
+ line-height: var(--prizma-headline-h5-line-height);
629
+ letter-spacing: var(--prizma-headline-h5-letter-spacing);
630
+ }
631
+
632
+ /* Subtitle Headline/H5 → Body/M Regular. */
633
+ .video-selfie-feedback .prizma-capture-feedback__subtitle {
634
+ font-family: var(--prizma-body-family);
635
+ font-weight: var(--prizma-body-m-regular-font-weight);
636
+ font-size: var(--prizma-body-m-regular-font-size);
637
+ line-height: var(--prizma-body-m-regular-line-height);
638
+ letter-spacing: var(--prizma-body-m-regular-letter-spacing);
639
+ }
640
+ }
641
+
642
+ /* ── The head-turn mark and its instruction ──────────────────────────────────
643
+ * The `🙂‍↔️ Turn Head` band puts a 48 circled arrow between the ring and the
644
+ * line that says which way to turn (`element container` 342×48, the glyph
645
+ * centred, then 16 to the text). The gap is Core's own Spacing/16; the copy is
646
+ * the foundation's `.prizma-viewfinder__title`, unchanged. Lifted over the
647
+ * stage's scrim for the reason `ViewfinderScreen.css` documents on `__intro`.
648
+ * DEVIATIONS #VS-20. */
649
+ .video-selfie-turn {
650
+ position: relative;
651
+ z-index: 1;
237
652
  display: flex;
238
653
  flex-direction: column;
239
- align-items: flex-start;
654
+ align-items: center;
655
+ gap: var(--prizma-spacing-16);
240
656
  width: 100%;
241
- margin: 0;
242
- padding: 0;
243
- list-style: none;
244
- gap: var(--prizma-spacing-12);
657
+ }
658
+
659
+ /* The mark is ONE bank glyph at 2xl — a filled disc with the arrow knocked
660
+ * out — so this only inks it. The first pass wrapped a 24 bare arrow in a
661
+ * hand-made disc and read as a thin chevron on the camera. */
662
+ .video-selfie-turn__mark {
663
+ display: flex;
664
+ align-items: center;
665
+ justify-content: center;
666
+ color: var(--prizma-icon-neutral-50-static);
667
+ }
668
+
669
+ /* The mark NEVER mirrors in a right-to-left locale (owner decision, Sofia
670
+ * 2026-10-07). A head turn is a physical direction, not a reading direction,
671
+ * so "left" stays left in Hebrew too. Core flips the horizontal chevrons in
672
+ * RTL through `[dir="rtl"] .prizma-icon--rtl-mirror` (Navigation.css); this
673
+ * rule, the same selector scoped to the mark, outranks it on this screen only
674
+ * and every other chevron keeps mirroring. The solid circle arrows the mark
675
+ * used to be never flipped, so this restores their behaviour. */
676
+ [dir='rtl'] .video-selfie-turn__mark .prizma-icon--rtl-mirror {
677
+ transform: none;
245
678
  }
246
679
 
247
680
  /* Step rows — the <ol> `PrizmaList` would supply, composed here only because