@incodetech/prizma-core-screens 0.2.7 → 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 (168) 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/DemoScreensLayout.css +16 -0
  8. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  9. package/dist/DemoScreensRedirectScreen.js +3 -2
  10. package/dist/DemoScreensStrings.d.ts +5 -0
  11. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  12. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  13. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  14. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  15. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  16. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  17. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  18. package/dist/DigitalIdUploadStrings.js +19 -0
  19. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  20. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  21. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  22. package/dist/EkybCountryScreen.d.ts +6 -6
  23. package/dist/EkybCountryScreen.js +20 -7
  24. package/dist/EkybStrings.d.ts +6 -1
  25. package/dist/ErrorScreensScreen.d.ts +5 -2
  26. package/dist/ErrorScreensScreen.js +15 -4
  27. package/dist/ErrorScreensStrings.d.ts +17 -2
  28. package/dist/ErrorScreensStrings.js +18 -10
  29. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  30. package/dist/FaceCaptureCapturingScreen.js +62 -2
  31. package/dist/FaceCaptureLayout.css +69 -4
  32. package/dist/FaceCaptureStrings.d.ts +23 -0
  33. package/dist/FormScreen.css +14 -0
  34. package/dist/FormScreen.d.ts +28 -0
  35. package/dist/FormScreen.js +3 -2
  36. package/dist/IdCaptureLayout.css +165 -2
  37. package/dist/IdCaptureStageScreen.d.ts +47 -0
  38. package/dist/IdCaptureStageScreen.js +39 -3
  39. package/dist/IdCaptureStrings.d.ts +19 -0
  40. package/dist/ListScreen.d.ts +19 -0
  41. package/dist/ListScreen.js +7 -2
  42. package/dist/LoaderScreen.d.ts +7 -5
  43. package/dist/LoaderScreen.js +7 -5
  44. package/dist/LoadersStrings.d.ts +18 -5
  45. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  46. package/dist/OcrReviewErrorScreen.js +24 -0
  47. package/dist/OcrReviewLayout.css +24 -0
  48. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  49. package/dist/OcrReviewLoadingScreen.js +19 -0
  50. package/dist/OcrReviewScreen.d.ts +76 -0
  51. package/dist/OcrReviewScreen.js +65 -0
  52. package/dist/OcrReviewStrings.d.ts +47 -0
  53. package/dist/OcrReviewStrings.js +17 -0
  54. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  55. package/dist/OcrReviewSuccessScreen.js +24 -0
  56. package/dist/PermissionsAllowScreen.d.ts +8 -11
  57. package/dist/PermissionsAllowScreen.js +8 -11
  58. package/dist/PermissionsAskScreen.d.ts +41 -0
  59. package/dist/PermissionsAskScreen.js +43 -0
  60. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  61. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  62. package/dist/PermissionsLayout.css +47 -0
  63. package/dist/PermissionsStrings.d.ts +20 -9
  64. package/dist/PermissionsStrings.js +10 -4
  65. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  66. package/dist/PhoneInputOtpScreen.js +26 -19
  67. package/dist/PhoneInputStrings.d.ts +11 -3
  68. package/dist/PhoneInputVerifyScreen.js +21 -5
  69. package/dist/QualifiedSignatureDocumentScreen.d.ts +8 -5
  70. package/dist/QualifiedSignatureDocumentScreen.js +9 -6
  71. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  72. package/dist/QualifiedSignatureResultScreen.js +17 -18
  73. package/dist/QualifiedSignatureStrings.d.ts +11 -4
  74. package/dist/ResultScreen.d.ts +8 -0
  75. package/dist/ResultScreen.js +14 -3
  76. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  77. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  78. package/dist/ReusabilityLayout.css +241 -0
  79. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  80. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  81. package/dist/ReusabilityStrings.d.ts +85 -0
  82. package/dist/ReusabilityStrings.js +37 -0
  83. package/dist/ReviewScreen.d.ts +38 -12
  84. package/dist/ReviewScreen.js +41 -8
  85. package/dist/ScreenLayout.css +382 -6
  86. package/dist/VideoSelfieErrorScreen.js +1 -1
  87. package/dist/VideoSelfieIdCaptureScreen.js +1 -1
  88. package/dist/VideoSelfieLayout.css +304 -2
  89. package/dist/VideoSelfieSelfieCaptureScreen.js +1 -1
  90. package/dist/VideoSelfieSpeakScreen.js +1 -1
  91. package/dist/VideoSelfieStrings.d.ts +4 -1
  92. package/dist/VideoSelfieTurnHeadScreen.js +14 -11
  93. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  94. package/dist/VideoSelfieTutorialScreen.js +10 -1
  95. package/dist/VideoSelfieWaveScreen.js +1 -1
  96. package/dist/ViewfinderScreen.css +36 -0
  97. package/dist/flows.d.ts +8 -0
  98. package/dist/flows.js +17 -0
  99. package/dist/index.d.ts +16 -0
  100. package/dist/index.js +12 -0
  101. package/dist/styles.css +3 -0
  102. package/package.json +68 -3
  103. package/samples/video-selfie-camera-wide.jpg +0 -0
  104. package/samples/video-selfie-room-wide.jpg +0 -0
  105. package/src/BiometricConsentScreen.tsx +192 -0
  106. package/src/BiometricConsentStrings.ts +119 -0
  107. package/src/CaptureScreen.tsx +17 -0
  108. package/src/DemoScreensLayout.css +16 -0
  109. package/src/DemoScreensRedirectScreen.tsx +14 -6
  110. package/src/DemoScreensStrings.ts +5 -0
  111. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  112. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  113. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  114. package/src/DigitalIdUploadStrings.ts +88 -0
  115. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  116. package/src/DocumentCaptureIllustration.tsx +1 -1
  117. package/src/EkybCountryScreen.tsx +23 -6
  118. package/src/EkybStrings.ts +6 -1
  119. package/src/ErrorScreensScreen.tsx +15 -4
  120. package/src/ErrorScreensStrings.ts +37 -12
  121. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  122. package/src/FaceCaptureLayout.css +69 -4
  123. package/src/FaceCaptureStrings.ts +24 -0
  124. package/src/FormScreen.css +14 -0
  125. package/src/FormScreen.tsx +34 -0
  126. package/src/IdCaptureLayout.css +165 -2
  127. package/src/IdCaptureStageScreen.tsx +75 -0
  128. package/src/IdCaptureStrings.ts +19 -0
  129. package/src/ListScreen.tsx +48 -9
  130. package/src/LoaderScreen.tsx +7 -5
  131. package/src/LoadersStrings.ts +18 -5
  132. package/src/OcrReviewErrorScreen.tsx +53 -0
  133. package/src/OcrReviewLayout.css +24 -0
  134. package/src/OcrReviewLoadingScreen.tsx +48 -0
  135. package/src/OcrReviewScreen.tsx +190 -0
  136. package/src/OcrReviewStrings.ts +69 -0
  137. package/src/OcrReviewSuccessScreen.tsx +52 -0
  138. package/src/PermissionsAllowScreen.tsx +9 -10
  139. package/src/PermissionsAskScreen.tsx +105 -0
  140. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  141. package/src/PermissionsLayout.css +47 -0
  142. package/src/PermissionsStrings.ts +42 -12
  143. package/src/PhoneInputOtpScreen.tsx +23 -3
  144. package/src/PhoneInputStrings.ts +11 -3
  145. package/src/PhoneInputVerifyScreen.tsx +24 -4
  146. package/src/QualifiedSignatureDocumentScreen.tsx +9 -7
  147. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  148. package/src/QualifiedSignatureStrings.ts +11 -4
  149. package/src/ResultScreen.tsx +33 -2
  150. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  151. package/src/ReusabilityLayout.css +241 -0
  152. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  153. package/src/ReusabilityStrings.ts +127 -0
  154. package/src/ReviewScreen.tsx +96 -27
  155. package/src/ScreenLayout.css +382 -6
  156. package/src/VideoSelfieErrorScreen.tsx +1 -1
  157. package/src/VideoSelfieIdCaptureScreen.tsx +9 -1
  158. package/src/VideoSelfieLayout.css +304 -2
  159. package/src/VideoSelfieSelfieCaptureScreen.tsx +9 -1
  160. package/src/VideoSelfieSpeakScreen.tsx +9 -1
  161. package/src/VideoSelfieStrings.ts +4 -2
  162. package/src/VideoSelfieTurnHeadScreen.tsx +25 -14
  163. package/src/VideoSelfieTutorialScreen.tsx +11 -0
  164. package/src/VideoSelfieWaveScreen.tsx +9 -1
  165. package/src/ViewfinderScreen.css +36 -0
  166. package/src/flows.ts +25 -0
  167. package/src/index.ts +74 -3
  168. package/src/styles.css +3 -0
@@ -97,10 +97,17 @@
97
97
  padding-inline: var(--prizma-spacing-24);
98
98
  }
99
99
 
100
- /* Processing/result screens: fully centered column. */
100
+ /* Processing/result screens: fully centered column.
101
+ * `safe center` second: flex centring is UNSAFE by default, so a stack taller
102
+ * than the column splits its overflow around the centre and the top half
103
+ * climbs over the nav row (#F-37). `safe` starts an overflowing stack at the
104
+ * top instead and does nothing while the stack fits, so the reference is
105
+ * byte-identical; the plain `center` before it is the fallback for engines
106
+ * that do not parse the keyword yet. */
101
107
  .prizma-screen-layout__content--centered {
102
108
  align-items: center;
103
109
  justify-content: center;
110
+ justify-content: safe center;
104
111
  gap: var(--prizma-spacing-16);
105
112
  }
106
113
 
@@ -114,6 +121,68 @@
114
121
  justify-content: center;
115
122
  }
116
123
 
124
+ /* ── Short panels: the illustration gives ground first ───────────────────────
125
+ * A tutorial is authored at 844: nav, title block, a 296 illustration centred
126
+ * in the flexible middle, disclaimer, CTA, tag. On a short panel the middle is
127
+ * the only part of that stack that flexes, and it flexes to ZERO air: on the
128
+ * closed iPhone Duo (678 tall, measured 2026-10-14) the slot came out 270 for
129
+ * Face Capture and 288 for ID Capture, the art scaled into it, and the halo sat
130
+ * flush against the subtitle above and the disclaimer below, both at 0px.
131
+ * Nothing overflowed, so nothing scrolled, but the screen read as jammed.
132
+ *
133
+ * The order of concessions (docs/core-lab-frame-adaptation.md §2) says the
134
+ * illustration is the first thing to shrink, because it is the one element
135
+ * that carries no task information. This rule is that step, as a rule rather
136
+ * than a per-card knob: under 720 the slot keeps Spacing/16 of air above and
137
+ * below, so the art loses 32 more px (270 → 238 on the Duo, above the 200
138
+ * floor) and the title block and the disclaimer get their breathing room back.
139
+ *
140
+ * 720 is the line every short-panel rule in this package uses (Video Selfie's
141
+ * ring and live cues): the reference is 844, and 640, 667, 678 and 693 are all
142
+ * short. A CONTAINER query, for the reason the 340 rule below documents — the
143
+ * browser viewport is not the phone. Scoped to a middle that holds an
144
+ * illustration so archetype E's field stack, which shares `__flex`, is
145
+ * untouched. Outside the query not one declaration exists: the reference is
146
+ * byte-identical. DEVIATIONS #F-35. */
147
+ @container pcs-screen (max-height: 720px) {
148
+ .prizma-screen-layout__flex:has(> [class*='prizma-illustration-']) {
149
+ padding-block: var(--prizma-spacing-16);
150
+ }
151
+ }
152
+
153
+ /* ── Big panels: the illustration takes the room, not the air ───────────────
154
+ * The mirror of the rule above, and the same argument read the other way. The
155
+ * flexible middle is the only part of the stack that flexes, so on a panel
156
+ * TALLER than the reference every extra pixel becomes air around an
157
+ * illustration that never grows: on the 768 x 1024 iPad the slot goes 436 →
158
+ * 616 and Face Capture's tutorial opens from 70 / 70 of air above and below
159
+ * the 296 art to 160 / 160, which reads as a small picture adrift between a
160
+ * title and a button (Camila, 2026-10-15: "it looks rather small… the title
161
+ * and button could be closer to the illustration").
162
+ *
163
+ * The order of concessions (docs/core-lab-frame-adaptation.md §2) makes the
164
+ * illustration the first thing to shrink on a short panel. Run it backwards
165
+ * and it is the first thing to GROW on a big one: it is the element that
166
+ * carries no task information, so it is the one that can absorb the slack
167
+ * without moving anything that does. The art takes the content column's full
168
+ * measure (420 inside the 468 wide column) and the air falls back to 98 / 98,
169
+ * close to the reference's 70.
170
+ *
171
+ * BOTH axes gate it, and deliberately. `min-height: 845px` keeps the 844
172
+ * reference and every short panel byte-identical. `min-width: 468px` keeps it
173
+ * off phones and foldables, where the column stays 390 and the art would grow
174
+ * by 46px for no reason — it fires only on the tablet and desktop panels,
175
+ * which is where the column widens to 468 and the slot is tall. A CONTAINER
176
+ * query, for the reason #F-35 and the 340 rule document: the browser viewport
177
+ * is not the panel. Scoped to a middle that holds an illustration so
178
+ * archetype E's field stack, which shares `__flex`, is untouched.
179
+ * DEVIATIONS #F-36. */
180
+ @container pcs-screen ((min-height: 845px) and (min-width: 468px)) {
181
+ .prizma-screen-layout__flex:has(> [class*='prizma-illustration-']) > [class*='prizma-illustration-'] {
182
+ width: 100%;
183
+ }
184
+ }
185
+
117
186
  /* ── List archetype middle (archetype G) ─────────────────────────────────────
118
187
  * A Core card/option list filling the space between the title block and the CTA
119
188
  * band. NOT `__flex`, which centres AND shrink-wraps its child — that slot is
@@ -170,6 +239,19 @@
170
239
  margin-bottom: var(--prizma-spacing-32);
171
240
  }
172
241
 
242
+ /* `analyzingLayout="centered"` (PrizmaReviewScreen): the header becomes the
243
+ * TOP half of a symmetric flex-1 pair around the loading bar + media group —
244
+ * its content bottom-anchors via `flex-end` so the Spacing/32 margin still
245
+ * lands directly above the stage, and all the leftover vertical space goes
246
+ * above the bar instead of being absorbed by the (now non-flexing) stage
247
+ * below. Paired with `--centered-group` and `__review-trailing-spacer`. */
248
+ .prizma-screen-layout__review-header--centered {
249
+ flex: 1 1 auto;
250
+ display: flex;
251
+ flex-direction: column;
252
+ justify-content: flex-end;
253
+ }
254
+
173
255
  /* The media stage: a centred column that flexes into whatever the header
174
256
  * leaves, so the captured still / file card sits in the middle at every screen
175
257
  * height. Its own child gap is Spacing/16 (the file card → encrypted-notice gap
@@ -193,6 +275,26 @@
193
275
  margin-bottom: var(--prizma-spacing-24);
194
276
  }
195
277
 
278
+ /* The stage no longer absorbs the leftover space itself — the header/spacer
279
+ * pair does that instead — so it shrinks to the media's own height. Centering
280
+ * stays horizontal only (`align-items: center`, inherited). */
281
+ .prizma-screen-layout__review-stage--centered-group {
282
+ flex: 0 0 auto;
283
+ }
284
+
285
+ /* The bottom half of the symmetric pair — equal flex-1 to
286
+ * `__review-header--centered`, so the group centres with even space on both
287
+ * sides, matching the Figma structure exactly (two flex-1 siblings flanking
288
+ * a fixed group). Empty on `analyzing` (pure spacer); bottom-anchors the CTA
289
+ * band on `success`, mirroring Figma's own bottom flex-1 container rather
290
+ * than letting the band just follow the stage at its natural position. */
291
+ .prizma-screen-layout__review-trailing {
292
+ flex: 1 1 auto;
293
+ display: flex;
294
+ flex-direction: column;
295
+ justify-content: flex-end;
296
+ }
297
+
196
298
  /* The `analyzing` loading bar is centred and capped at Figma's 164px instance
197
299
  * width; Core's `PrizmaLoadingBar` is `width: 100%` and takes its cap from the
198
300
  * host. 164 is absent from the spacing SOT — a documented literal, the same
@@ -297,6 +399,21 @@
297
399
  min-width: 0;
298
400
  }
299
401
 
402
+ /* Two tertiary links stacked in the band (archetype G's `linkLabel` +
403
+ * `secondaryLinkLabel`; Reusability's "What data will be shared?" over
404
+ * "Incode’s Privacy Policy"). They share ONE band slot and sit FLUSH: each L
405
+ * link is already a 56px row with its text centred, so the band's flat
406
+ * Spacing/16 between two of them put the two lines 72px apart and they read as
407
+ * unrelated (Camila, 2026-10-13: "way too separate"). Flush, the lines sit 56
408
+ * apart — the frame's Button group pulls them to 48 with a −8 overlap, which
409
+ * is that component's own tightening and not copied (DEVIATIONS #RU-4). The
410
+ * 16 to the neighbouring button or disclaimer is untouched. */
411
+ .prizma-screen-layout__band__links {
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: 0;
415
+ }
416
+
300
417
  /* Band content above the buttons (Face Match's verdict Snackbar). The band's
301
418
  * flat Spacing/16 plus this row's own 16 reaches the designed 32 to the CTA —
302
419
  * the same idiom as the Disclaimer's 12 + 16 = 28 (DEVIATIONS #8, #FM-23). */
@@ -327,6 +444,16 @@
327
444
  padding-bottom: var(--prizma-spacing-24);
328
445
  }
329
446
 
447
+ /* `--result` reserves `--pcs-footer-space` as a MARGIN outside the band
448
+ * (it sits as a shell-level sibling, not inside the content column that
449
+ * `--review`'s own footer-space reservation lives on) — so `--bare`'s flat
450
+ * padding would otherwise stack underneath it instead of replacing it. Zero
451
+ * it out here so the two combined give exactly the flat Spacing/24 `--bare`
452
+ * promises, not 24 plus a hidden tag's worth of space. */
453
+ .prizma-screen-layout__band--result.prizma-screen-layout__band--bare {
454
+ margin-bottom: 0;
455
+ }
456
+
330
457
  /* Full-width CTA row — for screens whose CTA is not part of a band. */
331
458
  .prizma-screen-layout__cta {
332
459
  flex-shrink: 0;
@@ -336,14 +463,14 @@
336
463
  }
337
464
 
338
465
  /* ── Inline prompt with links ────────────────────────────────────────────────
339
- * A line of body copy carrying one or more INLINE links — Phone Input's
466
+ * A line of body copy carrying one or more inline links — Phone Input's
340
467
  * "Didn't receive the code? Resend code or Change phone number", and the same
341
468
  * shape every other OTP-bearing module draws (Email Input, eKYC…).
342
469
  *
343
470
  * Core has no inline-link primitive: `PrizmaButton variant="tertiary-link"` is
344
- * the right INK (underlined, `button/tertiary/text/default`) but size L is
471
+ * the right ink (underlined, `button/tertiary/text/default`) but size L is
345
472
  * `width: 100%; min-height: 56px`, so it cannot sit inside a sentence. The
346
- * recipe therefore lives here — built from Core's OWN tokens, inventing no
473
+ * recipe therefore lives here — built from Core's own tokens, inventing no
347
474
  * palette — and is placed in the foundation rather than a module file because
348
475
  * it is a cross-module pattern (DEVIATIONS #PI-10, #DC-29 precedent).
349
476
  */
@@ -379,6 +506,79 @@
379
506
  text-decoration: none;
380
507
  }
381
508
 
509
+ /* ── Presented surfaces: sheet and dialog over a screen ──────────────────────
510
+ * The scrim + panel split every presented Core surface in this package uses
511
+ * (Document Capture's method sheet #DC-30, the Permissions ask #GEN-1): Core's
512
+ * `PrizmaBottomSheetContent` / `PrizmaModalContent` own the panel (handle,
513
+ * radius, padding, title, button group); the SCREEN owns the scrim and the
514
+ * anchoring, because Core's safe presenters portal to `document.body` and would
515
+ * escape the 390 × 844 frame and the host's `transform: scale()`. The scrim
516
+ * drinks Core's own `Surface/Overlay` token. No animation: a screen is one
517
+ * static spec state.
518
+ *
519
+ * Promoted here from two identical module copies (`permissions-sheet-*`,
520
+ * `document-capture-sheet-*`) when a third module needed the same three rules
521
+ * (Biometric Consent's quit confirmation, DEVIATIONS #BC-8). The two module
522
+ * copies still stand until their owners move them; new consumers use these. */
523
+ .prizma-screen-layout__sheet-scrim {
524
+ position: absolute;
525
+ inset: 0;
526
+ z-index: 1;
527
+ background: var(--prizma-surface-overlay);
528
+ }
529
+
530
+ /* A bottom sheet: pinned to the frame's bottom edge. */
531
+ .prizma-screen-layout__sheet-panel {
532
+ position: absolute;
533
+ left: 0;
534
+ right: 0;
535
+ bottom: 0;
536
+ z-index: 2;
537
+ }
538
+
539
+ /* The frame's `System UI Bottom Bar` (34) sits INSIDE the sheet. Core reserves
540
+ * it as `env(safe-area-inset-bottom, …)`, but a browser defines the inset as 0
541
+ * on a flat screen, so the fallback never applies and the CTAs drop to 12px off
542
+ * the frame's bottom. Screens here reserve device chrome as fixed values
543
+ * (`--pcs-status-bar`), so the sheet takes Core's own Sheet/Size/Safe-area
544
+ * token directly. */
545
+ .prizma-screen-layout__sheet-panel .prizma-bottom-sheet__safe-area {
546
+ height: var(--prizma-sheet-size-safe-area);
547
+ }
548
+
549
+ /* Core's Modal, placed the way Core's own presenter places it
550
+ * (`.prizma-modal-presenter__scrim`, Modal.css): the card pins to the BOTTOM
551
+ * of the frame with a Spacing/12 margin on the sides and bottom — never
552
+ * centred (owner reference 2026-07-12). `PrizmaModalContent` is the card
553
+ * (width 100% up to its own Modal/Size/Max-width); this is the presenter's
554
+ * geometry without its portal and its clock. Biometric Consent's quit
555
+ * confirmation is the first consumer (DEVIATIONS #BC-7). */
556
+ .prizma-screen-layout__modal-panel {
557
+ position: absolute;
558
+ inset: 0;
559
+ z-index: 2;
560
+ display: flex;
561
+ align-items: flex-end;
562
+ justify-content: center;
563
+ padding: var(--prizma-spacing-12);
564
+ box-sizing: border-box;
565
+ }
566
+
567
+ /* A CENTRED dialog — the reading the Permissions ask and NFC's settings
568
+ * dialog take for a desktop browser window, where a card pinned to the bottom
569
+ * of a wide window reads as a phone pasted on the page. Same card, centred
570
+ * with the Spacing/12 gutter. */
571
+ .prizma-screen-layout__dialog-panel {
572
+ position: absolute;
573
+ inset: 0;
574
+ z-index: 2;
575
+ display: flex;
576
+ align-items: center;
577
+ justify-content: center;
578
+ padding-inline: var(--prizma-spacing-12);
579
+ box-sizing: border-box;
580
+ }
581
+
382
582
  /* ── Footer (verified-by tag) ─────────────────────────────────────────────────
383
583
  * ANCHORED to the shell, not flowed after the content. The owner's rule is
384
584
  * absolute: where a tag exists it sits at exactly the same position on every
@@ -418,6 +618,108 @@
418
618
  padding-bottom: var(--pcs-capture-chrome-space);
419
619
  }
420
620
 
621
+ /* ── Short panels: the result column never climbs over the nav ───────────────
622
+ * Archetype F (`PrizmaResultScreen`) centres its title block in the flexible
623
+ * column. On the iPhone Duo open full screen (890 x 626, measured 2026-10-20)
624
+ * ID Capture's Document error drew its error status mark across the Incode
625
+ * logo (developer report via Camila): the column was 318 tall with 24 of
626
+ * padding above and 88 below, the title block (114) + gap (16) + the
627
+ * accepted-documents card (180) needed 422, and unsafe centring split the 104
628
+ * of overflow into 52 above the column and 52 below. `safe center` (declared
629
+ * with the column above) stops the climb; the rest of the fit comes from the
630
+ * order of concessions (docs/core-lab-frame-adaptation.md §2), scoped to the
631
+ * archetype:
632
+ *
633
+ * 1. The column stops reserving the footer twice. Every content column pads
634
+ * its bottom by `--pcs-footer-space` so content never sits under the tag,
635
+ * but F's CTA band is a shell-level SIBLING of the column that reserves the
636
+ * same space again as its margin (`__band--result`). On the 844 reference
637
+ * that is 62 of invisible air above the band; on a 626 panel it is a
638
+ * quarter of the column. Under 720 the column drops it when a result band
639
+ * follows. The reference keeps the double reserve on purpose: dropping it
640
+ * there would move the centred block 31 lower on every result screen,
641
+ * which is the owner's call, not a short-panel fix.
642
+ * 2. Rhythm (step 2 of the order): the column's 24 above the title goes to 16
643
+ * and its 16 gaps to 12, the band's 16 gap to 12. The same values the Lab's
644
+ * `compact` adaptation uses. With both, the Document error fits the 626
645
+ * panel exactly (322 in 322).
646
+ *
647
+ * What still does not fit after that pans: `overflow-y: auto` on the column,
648
+ * the answer archetype G's scroll variant gives a long list, so a longer
649
+ * document list or a two-line Spanish title scrolls above the CTA instead of
650
+ * running under it. Never the H2, the CTA or the tag. Outside the query not
651
+ * one declaration exists. DEVIATIONS #F-37. */
652
+ @container pcs-screen (max-height: 720px) {
653
+ .prizma-screen-layout__content--centered {
654
+ overflow-y: auto;
655
+ }
656
+
657
+ /* (0,3,0), the footer reserve's own specificity, and later in the file: it
658
+ wins the tie only where a result band follows the column. */
659
+ .prizma-screen-shell .prizma-screen-layout__content--centered:has(+ .prizma-screen-layout__band--result) {
660
+ padding-top: var(--prizma-spacing-16);
661
+ padding-bottom: 0;
662
+ gap: var(--prizma-spacing-12);
663
+ }
664
+
665
+ .prizma-screen-layout__band--result {
666
+ gap: var(--prizma-spacing-12);
667
+ }
668
+ }
669
+
670
+ /* ── Short panels: a list that does not fit pans ─────────────────────────────
671
+ * Archetype G's middle is `flex: 1; min-height: 0`, so on a short panel it
672
+ * shrinks to whatever the title block and the band leave, and its rows, which
673
+ * keep their height, run straight through the band: on the Duo open full
674
+ * screen ID Capture's Common issues drew "Take the photo manually" across its
675
+ * third row (56px of overlap, measured 2026-10-20). The archetype already owns
676
+ * the answer as the `--scroll` variant a screen opts into when it knows it is
677
+ * long; a short panel is the same situation decided by the panel rather than
678
+ * the screen, so under 720 every list takes it, all three declarations: the
679
+ * rows pan, Spacing/16 of air stays above the band, the title, the CTA and the
680
+ * tag hold still. Rows keep their intrinsic height for the same reason the
681
+ * variant's do (#QS-1). The `--end` variant still hangs its rows off the
682
+ * bottom when they fit.
683
+ *
684
+ * An ILLUSTRATION in the middle is the exception, and it goes the other way:
685
+ * step 1 of the order of concessions, it shrinks to the slot instead of
686
+ * panning. NFC's symbol and try-again screens hang Core's 342 x 392 art under
687
+ * a top-aligned title (that is why they are on G, not A), and on the 626 panel
688
+ * a rigid art scrolled with its bottom third cut off under the CTA. Shrunk, the
689
+ * svg's own `xMidYMid meet` scales the drawing into the shorter box and nothing
690
+ * is cut. Outside the query not one declaration exists. DEVIATIONS #F-38. */
691
+ @container pcs-screen (max-height: 720px) {
692
+ .prizma-screen-layout__list {
693
+ overflow-y: auto;
694
+ margin-bottom: var(--prizma-spacing-16);
695
+ }
696
+
697
+ .prizma-screen-layout__list > :not([class*='prizma-illustration-']) {
698
+ flex-shrink: 0;
699
+ }
700
+
701
+ .prizma-screen-layout__list > [class*='prizma-illustration-'] {
702
+ flex: 0 1 auto;
703
+ min-height: 0;
704
+ /* A shrunk art is narrower than the column, and a `fit-content` wrapper
705
+ is not stretched, so it centres itself the way the slot centres rows. */
706
+ align-self: center;
707
+ }
708
+
709
+ /* The drawing follows the box. Core's location art is a bare svg with its
710
+ 342 x 392 attributes and no sizing rule of its own, so a shorter wrapper
711
+ alone left it at full size and cut; the scan art already sizes its svg to
712
+ the wrapper. One rule for both: the svg takes the wrapper's height and
713
+ keeps its ratio. When nothing shrinks, 100% of the intrinsic height is the
714
+ intrinsic height, so the reference is byte-identical. */
715
+ .prizma-screen-layout__list > [class*='prizma-illustration-'] > svg {
716
+ display: block;
717
+ height: 100%;
718
+ width: auto;
719
+ max-width: 100%;
720
+ }
721
+ }
722
+
421
723
  /* Camera screens built on the viewfinder surface (ID / Document stages) reserve
422
724
  * the same chrome footprint on their content column. Without it, Core's poa
423
725
  * frame — which has NO aspect lock and FILLS its box — grows to the full shell
@@ -453,6 +755,80 @@
453
755
  max-width: 100%;
454
756
  }
455
757
 
758
+ /* ── Capture notice band ─────────────────────────────────────────────────────
759
+ * A Snackbar raised over the live feed without taking the camera away — Face
760
+ * Capture's "Need more time?" offer (2026-10-05) is the first consumer.
761
+ *
762
+ * ANCHORED on the same rail as the control strip above, and deliberately at the
763
+ * same offset: the two are alternatives, never stacked. A capture screen either
764
+ * hands the user a shutter or raises a notice, so sharing the rail keeps the
765
+ * band in one place instead of inventing a second vertical system.
766
+ *
767
+ * It does NOT get a `--pcs-*` content reserve the way the strip does. The strip
768
+ * is a fixed 64px row, so reserving it is arithmetic; a notice is multiline and
769
+ * localized, so its height is not knowable from a token. It floats over the gap
770
+ * Core already leaves under the instruction line, which is where the module
771
+ * Figma draws it. */
772
+ .prizma-screen-shell .prizma-screen-layout__capture-notice {
773
+ position: absolute;
774
+ left: var(--prizma-spacing-24);
775
+ right: var(--prizma-spacing-24);
776
+ bottom: calc(var(--pcs-footer-space) + var(--prizma-spacing-8));
777
+ display: flex;
778
+ }
779
+
780
+ /* On a 320 phone the notice drops its status glyph (Camila, 2026-10-05).
781
+ *
782
+ * The Snackbar's icon slot is 32px plus the root's 12px gap: 44 of the 272px
783
+ * the content column has at 320, a sixth of the width, spent on a decorative
784
+ * mark. It pushed the body from two lines to four and the band grew upward
785
+ * until it covered the capture instruction — the one line the screen exists
786
+ * to show. At 390 there is room for both and the glyph stays.
787
+ *
788
+ * A CONTAINER query, not a media query, and that is the whole reason this
789
+ * rule lives in the screens layer rather than in Snackbar.css: Core web
790
+ * components are width-agnostic by design, and the browser viewport is not
791
+ * the phone — in Core Lab a 320 shell sits inside a 1400px window, so
792
+ * `@media` would read the wrong number. `pcs-screen` is the shell's own
793
+ * container (ScreenShell.css), so this asks the question that matters: how
794
+ * wide is the screen. Same mechanism as FormScreen.css's 420 breakpoint.
795
+ *
796
+ * 340 is the line because it is the gap above the narrowest phone we support
797
+ * and below the next one: 320 loses the glyph, 360 and 390 keep it — measured,
798
+ * both of those clear the instruction line with the icon in. The icon is
799
+ * `aria-hidden`, so
800
+ * nothing is lost for a screen-reader user; what is lost is the non-colour
801
+ * signal of state, and the copy here carries the meaning without it
802
+ * ("Need more time? / Manual capture starts in 20 s"). */
803
+ @container pcs-screen (max-width: 340px) {
804
+ .prizma-screen-layout__capture-notice .prizma-snackbar__icon {
805
+ display: none;
806
+ }
807
+
808
+ /* …and the capture frame gives up Spacing/40 of its width to make room for
809
+ * the band. Dropping the glyph was enough for English and not for Spanish:
810
+ * measured at 320, the instruction ends at 488 and the notice starts at 500
811
+ * in English but 482 in Spanish, because both the instruction AND the body
812
+ * wrap to two lines. Six pixels of overlap, on the one line the screen is
813
+ * for.
814
+ *
815
+ * The frame is the right thing to give. It is the lever Core already uses
816
+ * when anchored chrome needs room — `--pcs-capture-chrome-space` exists so
817
+ * the shutter strip can push the frame smaller — and the owner's rule is
818
+ * explicit that the frame may get smaller, that is not a constraint
819
+ * (2026-08-18). The alternative was the Figma-exact padding and gap inside
820
+ * `.prizma-selfie-capture`, which are measured values and not ours to bend.
821
+ *
822
+ * The stage is `width: 100%` with `aspect-ratio: 1`, so 40 off the width is
823
+ * 40 off the height and everything below rises by it: Spanish goes from 6px
824
+ * of overlap to 34px of clearance, English to 52. Scoped to screens that
825
+ * actually carry a notice — a capturing screen without one keeps the full
826
+ * frame at 320. */
827
+ .prizma-screen-shell:has(.prizma-screen-layout__capture-notice) .prizma-selfie-capture {
828
+ max-width: calc(100% - var(--prizma-spacing-40));
829
+ }
830
+ }
831
+
456
832
  /* ── Anchored-chrome layering ─────────────────────────────────────────────────
457
833
  * The camera HUD bands lift themselves to `z-index: 1` so their copy paints
458
834
  * above `PrizmaIdCapture`'s scrim (an absolutely positioned box-shadow that
@@ -463,7 +839,7 @@
463
839
  * chrome are ordered in a single place. */
464
840
  .prizma-screen-shell .prizma-screen-layout__footer,
465
841
  .prizma-screen-shell .prizma-screen-layout__band__tag,
466
- .prizma-screen-shell .prizma-screen-layout__capture-strip {
842
+ .prizma-screen-shell .prizma-screen-layout__capture-strip,
843
+ .prizma-screen-shell .prizma-screen-layout__capture-notice {
467
844
  z-index: 2;
468
845
  }
469
-
@@ -120,7 +120,7 @@ export const VideoSelfieErrorScreen = forwardRef<
120
120
  }
121
121
  : {
122
122
  title: slice.noConnection.title,
123
- subtitle: undefined,
123
+ subtitle: slice.noConnection.subtitle,
124
124
  ctaLabel: slice.noConnection.ctaLabel,
125
125
  quitLabel: slice.noConnection.quitLabel,
126
126
  };
@@ -191,7 +191,15 @@ export const VideoSelfieIdCaptureScreen = forwardRef<
191
191
  condition === 'wrongSide' ? WRONG_SIDE_FEEDBACK[side] : CONDITION_FEEDBACK[condition];
192
192
 
193
193
  return (
194
- <PrizmaScreenShell ref={ref} dark className={className} {...shell}>
194
+ <PrizmaScreenShell
195
+ ref={ref}
196
+ dark
197
+ /* The camera owns the whole window, never a column of it — see the
198
+ contract note in `VideoSelfieLayout.css`. */
199
+ data-pcs-overlay="window"
200
+ className={className}
201
+ {...shell}
202
+ >
195
203
  <div className="prizma-viewfinder__surface">{camera}</div>
196
204
  <div className="video-selfie-stepper">
197
205
  <PrizmaStepper