@globalpayments-private/gpds-pattern-web 0.0.0-stage → 0.1.0-pr.6.g07fbefe

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 (97) hide show
  1. package/README.md +549 -2
  2. package/dist/cjs/control-strategy-DhhYTbse.js +113 -0
  3. package/dist/cjs/gpds-pattern-web.cjs.js +24 -0
  4. package/dist/cjs/index-Dzm6P89U.js +1565 -0
  5. package/dist/cjs/index.cjs.js +180 -0
  6. package/dist/cjs/loader.cjs.js +12 -0
  7. package/dist/cjs/uds-pattern-login-card_2.cjs.entry.js +1662 -0
  8. package/dist/collection/collection-manifest.json +14 -0
  9. package/dist/collection/components/uds-pattern-login-card/slimmers/identity-memory.slimmer.js +63 -0
  10. package/dist/collection/components/uds-pattern-login-card/slimmers/login-workflow.slimmer.js +357 -0
  11. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-card-renderer.js +100 -0
  12. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-field-renderer.js +129 -0
  13. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-step-renderer.js +192 -0
  14. package/dist/collection/components/uds-pattern-login-card/slimmers/resend-throttle.slimmer.js +65 -0
  15. package/dist/collection/components/uds-pattern-login-card/uds-pattern-login-card.css +154 -0
  16. package/dist/collection/components/uds-pattern-login-card/uds-pattern-login-card.js +508 -0
  17. package/dist/collection/components/uds-pattern-login-card/validation/login-validation-rules.js +86 -0
  18. package/dist/collection/components/uds-pattern-login-card/view-model/login-card-handlers.js +1 -0
  19. package/dist/collection/components/uds-pattern-login-card/view-model/login-step.view-model.js +138 -0
  20. package/dist/collection/components/uds-pattern-login-page/uds-pattern-login-page.css +37 -0
  21. package/dist/collection/components/uds-pattern-login-page/uds-pattern-login-page.js +97 -0
  22. package/dist/collection/index.js +5 -0
  23. package/dist/collection/providers/auth-provider.js +28 -0
  24. package/dist/collection/providers/control-strategy.js +21 -0
  25. package/dist/collection/providers/login-config.js +4 -0
  26. package/dist/collection/providers/memory-auth-provider.js +133 -0
  27. package/dist/collection/providers/parse-auth-result.js +135 -0
  28. package/dist/collection/slimmer/common/log.js +14 -0
  29. package/dist/collection/slimmer/common/symbols.js +10 -0
  30. package/dist/collection/slimmer/common/types.js +2 -0
  31. package/dist/collection/slimmer/decorators/inject-slimmer.js +73 -0
  32. package/dist/collection/slimmer/decorators/map-to-component-field.js +41 -0
  33. package/dist/collection/slimmer/decorators/map-to-component-method.js +33 -0
  34. package/dist/collection/slimmer/decorators/slimmer.js +10 -0
  35. package/dist/collection/slimmer/index.js +5 -0
  36. package/dist/collection/utils/labels.js +81 -0
  37. package/dist/collection/utils/types.js +1 -0
  38. package/dist/components/index.d.ts +35 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/components/p-DOrg55po.js +1 -0
  41. package/dist/components/p-_G8A9gg0.js +1 -0
  42. package/dist/components/uds-pattern-login-card.d.ts +11 -0
  43. package/dist/components/uds-pattern-login-card.js +1 -0
  44. package/dist/components/uds-pattern-login-page.d.ts +11 -0
  45. package/dist/components/uds-pattern-login-page.js +1 -0
  46. package/dist/docs.d.ts +480 -0
  47. package/dist/docs.json +549 -0
  48. package/dist/esm/control-strategy-DOrg55po.js +105 -0
  49. package/dist/esm/gpds-pattern-web.js +20 -0
  50. package/dist/esm/index-DsXlLyGj.js +1537 -0
  51. package/dist/esm/index.js +170 -0
  52. package/dist/esm/loader.js +10 -0
  53. package/dist/esm/uds-pattern-login-card_2.entry.js +1659 -0
  54. package/dist/gpds-pattern-web/gpds-pattern-web.css +1 -0
  55. package/dist/gpds-pattern-web/gpds-pattern-web.esm.js +1 -0
  56. package/dist/gpds-pattern-web/index.esm.js +1 -0
  57. package/dist/gpds-pattern-web/p-2c97d2da.entry.js +1 -0
  58. package/dist/gpds-pattern-web/p-DOrg55po.js +1 -0
  59. package/dist/gpds-pattern-web/p-DsXlLyGj.js +3 -0
  60. package/dist/index.cjs.js +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/types/components/uds-pattern-login-card/slimmers/identity-memory.slimmer.d.ts +21 -0
  63. package/dist/types/components/uds-pattern-login-card/slimmers/login-workflow.slimmer.d.ts +100 -0
  64. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-card-renderer.d.ts +44 -0
  65. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-field-renderer.d.ts +91 -0
  66. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-step-renderer.d.ts +40 -0
  67. package/dist/types/components/uds-pattern-login-card/slimmers/resend-throttle.slimmer.d.ts +21 -0
  68. package/dist/types/components/uds-pattern-login-card/uds-pattern-login-card.d.ts +134 -0
  69. package/dist/types/components/uds-pattern-login-card/validation/login-validation-rules.d.ts +72 -0
  70. package/dist/types/components/uds-pattern-login-card/view-model/login-card-handlers.d.ts +21 -0
  71. package/dist/types/components/uds-pattern-login-card/view-model/login-step.view-model.d.ts +96 -0
  72. package/dist/types/components/uds-pattern-login-page/uds-pattern-login-page.d.ts +21 -0
  73. package/dist/types/components.d.ts +327 -0
  74. package/dist/types/index.d.ts +10 -0
  75. package/dist/types/providers/auth-provider.d.ts +168 -0
  76. package/dist/types/providers/control-strategy.d.ts +144 -0
  77. package/dist/types/providers/login-config.d.ts +52 -0
  78. package/dist/types/providers/memory-auth-provider.d.ts +37 -0
  79. package/dist/types/providers/parse-auth-result.d.ts +8 -0
  80. package/dist/types/slimmer/common/log.d.ts +8 -0
  81. package/dist/types/slimmer/common/symbols.d.ts +10 -0
  82. package/dist/types/slimmer/common/types.d.ts +30 -0
  83. package/dist/types/slimmer/decorators/inject-slimmer.d.ts +24 -0
  84. package/dist/types/slimmer/decorators/map-to-component-field.d.ts +16 -0
  85. package/dist/types/slimmer/decorators/map-to-component-method.d.ts +11 -0
  86. package/dist/types/slimmer/decorators/slimmer.d.ts +10 -0
  87. package/dist/types/slimmer/index.d.ts +6 -0
  88. package/dist/types/stencil-public-runtime.d.ts +1879 -0
  89. package/dist/types/utils/labels.d.ts +13 -0
  90. package/dist/types/utils/types.d.ts +106 -0
  91. package/dist/types/vega.d.ts +11 -0
  92. package/loader/cdn.js +1 -0
  93. package/loader/index.cjs.js +1 -0
  94. package/loader/index.d.ts +24 -0
  95. package/loader/index.es2017.js +1 -0
  96. package/loader/index.js +2 -0
  97. package/package.json +79 -4
@@ -0,0 +1,192 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { h } from "@stencil/core";
12
+ import { UdsSlimmer, InjectSlimmer, MapToComponentField } from "../../../../slimmer";
13
+ import { LoginFieldRenderer } from "./login-field-renderer";
14
+ /**
15
+ * Renders the body of whichever step the workflow is on.
16
+ *
17
+ * ## Why a renderer slimmer
18
+ *
19
+ * This is the same Slimmer mechanism the stateful layer uses, pointed at
20
+ * rendering: one class, one responsibility, bound to the component by field
21
+ * mapping rather than by parameter passing. Vega applies the convention the
22
+ * same way — `slimmers/renderers/<name>-renderer.tsx`, a class ending in
23
+ * `Renderer`, exposing `render()` — so a reader moving between the design
24
+ * system and the pattern library meets one structure, not two.
25
+ *
26
+ * The gain over the plain `view/` modules this replaces is the binding. A free
27
+ * function has to be handed everything it needs on each call, so its signature
28
+ * grows with its appetite and callers thread arguments through layers that do
29
+ * not use them. `@MapToComponentField()` reaches the component's `viewModel`
30
+ * and `handlers` directly, and types them by indexing the component class, so
31
+ * the renderer cannot drift out of step with what the component actually
32
+ * exposes — rename the getter and this stops compiling.
33
+ *
34
+ * Renderers only read. Every mutation goes back through `handlers`, into the
35
+ * workflow slimmer, and through the control strategy's checkpoints.
36
+ */
37
+ export class LoginStepRenderer extends UdsSlimmer {
38
+ constructor() {
39
+ super();
40
+ this.fields = new LoginFieldRenderer();
41
+ }
42
+ render() {
43
+ const vm = this.viewModel;
44
+ switch (vm.step) {
45
+ case 'identifier':
46
+ return this.identifierStep(vm);
47
+ case 'password':
48
+ return this.passwordStep(vm);
49
+ case 'mfa-method':
50
+ return this.mfaMethodStep(vm);
51
+ case 'otp':
52
+ return this.otpStep(vm);
53
+ case 'forgot-password':
54
+ case 'forgot-username':
55
+ return this.forgotStep(vm);
56
+ case 'reset-password':
57
+ return this.resetPasswordStep(vm);
58
+ case 'sign-up':
59
+ return this.signUpStep(vm);
60
+ default:
61
+ return [];
62
+ }
63
+ }
64
+ identifierStep(vm) {
65
+ const { copy } = vm;
66
+ return [
67
+ this.fields.textField({
68
+ name: 'identifier',
69
+ label: copy.identifierFieldLabel,
70
+ type: 'email',
71
+ autocomplete: 'username',
72
+ }),
73
+ this.fields.checkbox('rememberIdentifier', copy.rememberIdentifier),
74
+ this.fields.submit(copy.continue),
75
+ h("div", { class: "prose" }, h("p", { class: "prose__lead" }, copy.troubleSigningInPrompt), h("p", null, "Follow the steps in \u201C", this.fields.link(copy.troubleSigningIn, this.handlers.onAction('trouble-signing-in')), "\u201D."), vm.can.signUp && (h("p", null, copy.noAccountPrompt, " ", this.fields.link(copy.createAccount, this.handlers.onNavigate('sign-up'))))),
76
+ ];
77
+ }
78
+ passwordStep(vm) {
79
+ const { copy, can } = vm;
80
+ const recovery = [];
81
+ if (can.reset) {
82
+ recovery.push(this.fields.link(copy.forgotPassword, this.handlers.onNavigate('forgot-password')));
83
+ }
84
+ if (can.username) {
85
+ recovery.push(this.fields.link(copy.forgotUsername, this.handlers.onNavigate('forgot-username')));
86
+ }
87
+ return [
88
+ // `vega-input` carries its own password reveal control, so the card no
89
+ // longer renders — or tracks the state for — an eye toggle of its own.
90
+ this.fields.textField({
91
+ name: 'password',
92
+ label: copy.passwordFieldLabel,
93
+ type: 'password',
94
+ autocomplete: 'current-password',
95
+ }),
96
+ ...(recovery.length ? [h("div", { class: "inline-links" }, recovery)] : []),
97
+ this.fields.submit(copy.continue),
98
+ ];
99
+ }
100
+ mfaMethodStep(vm) {
101
+ const { copy } = vm;
102
+ return [
103
+ this.fields.methodGroup(vm.methods, copy.mfaMethodHeading),
104
+ this.fields.submit(copy.continue),
105
+ ];
106
+ }
107
+ otpStep(vm) {
108
+ const { copy } = vm;
109
+ const tail = [];
110
+ if (vm.methods.length > 1) {
111
+ tail.push(this.fields.link(copy.changeMfaMethod, this.handlers.onNavigate('mfa-method')));
112
+ }
113
+ if (vm.can.resend) {
114
+ tail.push(vm.isResendThrottled ? (
115
+ // Rendered as text rather than a disabled link: a countdown is a
116
+ // status, and announcing it politely is more useful than offering a
117
+ // dead target.
118
+ h("span", { class: "countdown", "aria-live": "polite" }, vm.resendCountdownText)) : (this.fields.link(copy.resendCode, this.handlers.onResend)));
119
+ }
120
+ return [
121
+ // `vega-input-passcode` owns the whole multi-box entry: per-character
122
+ // focus advance, backspace retreat, paste distribution and masking. That
123
+ // replaces the card's own OTP slimmer outright.
124
+ this.fields.passcodeField(),
125
+ this.fields.submit(copy.otpSubmit),
126
+ ...(tail.length
127
+ ? [
128
+ h("div", { class: "prose" }, h("p", null, copy.changeMfaMethodPrompt), h("p", { class: "inline-links" }, tail)),
129
+ ]
130
+ : []),
131
+ ];
132
+ }
133
+ forgotStep(vm) {
134
+ const { copy } = vm;
135
+ return [
136
+ this.fields.textField({
137
+ name: vm.step === 'forgot-password' ? 'identifier' : 'email',
138
+ label: copy.identifierFieldLabel,
139
+ type: 'email',
140
+ autocomplete: 'username',
141
+ }),
142
+ this.fields.submit(copy.continue),
143
+ h("p", { class: "prose" }, this.fields.link(copy.back, this.handlers.onNavigate('identifier'))),
144
+ ];
145
+ }
146
+ resetPasswordStep(vm) {
147
+ const { copy } = vm;
148
+ return [
149
+ this.fields.textField({
150
+ name: 'password',
151
+ label: copy.newPasswordFieldLabel,
152
+ type: 'password',
153
+ autocomplete: 'new-password',
154
+ }),
155
+ // The pairing is checked on the field that is wrong, inside the same
156
+ // `vega-form` gate as every other rule — not as a pre-submit test
157
+ // reported through the card-level banner.
158
+ this.fields.textField({
159
+ name: 'confirmPassword',
160
+ label: copy.confirmPasswordFieldLabel,
161
+ type: 'password',
162
+ autocomplete: 'new-password',
163
+ mustMatch: 'password',
164
+ }),
165
+ this.fields.submit(copy.continue),
166
+ ];
167
+ }
168
+ signUpStep(vm) {
169
+ const { copy } = vm;
170
+ return [
171
+ this.fields.textField({ name: 'firstName', label: copy.firstNameFieldLabel, autocomplete: 'given-name' }),
172
+ this.fields.textField({ name: 'lastName', label: copy.lastNameFieldLabel, autocomplete: 'family-name' }),
173
+ this.fields.textField({
174
+ name: 'identifier',
175
+ label: copy.identifierFieldLabel,
176
+ type: 'email',
177
+ autocomplete: 'email',
178
+ }),
179
+ this.fields.submit(copy.signUpSubmit),
180
+ h("p", { class: "prose" }, copy.haveAccountPrompt, " ", this.fields.link(copy.signIn, this.handlers.onNavigate('identifier'))),
181
+ ];
182
+ }
183
+ }
184
+ __decorate([
185
+ InjectSlimmer()
186
+ ], LoginStepRenderer.prototype, "fields", void 0);
187
+ __decorate([
188
+ MapToComponentField()
189
+ ], LoginStepRenderer.prototype, "viewModel", void 0);
190
+ __decorate([
191
+ MapToComponentField()
192
+ ], LoginStepRenderer.prototype, "handlers", void 0);
@@ -0,0 +1,65 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { UdsSlimmer, MapToComponentField, MapToComponentMethod } from "../../../slimmer";
12
+ /**
13
+ * The resend throttle and its countdown.
14
+ *
15
+ * Small, but exactly the kind of thing that disappears into a large component
16
+ * and then gets subtly broken: an interval that must be cleared on disconnect,
17
+ * and a tick that must drive a re-render.
18
+ */
19
+ export class ResendThrottleSlimmer extends UdsSlimmer {
20
+ constructor() {
21
+ super();
22
+ }
23
+ /** Begins a throttle window. A missing or non-positive duration clears it. */
24
+ start(resendAfterMs) {
25
+ this.stop();
26
+ if (!resendAfterMs || resendAfterMs <= 0) {
27
+ this.resendAt = undefined;
28
+ return;
29
+ }
30
+ this.resendAt = Date.now() + resendAfterMs;
31
+ this.nowTick = Date.now();
32
+ this.timer = setInterval(() => {
33
+ this.nowTick = Date.now();
34
+ if (this.resendAt !== undefined && this.nowTick >= this.resendAt) {
35
+ this.stop();
36
+ this.resendAt = undefined;
37
+ }
38
+ }, 1000);
39
+ }
40
+ stop() {
41
+ if (this.timer) {
42
+ clearInterval(this.timer);
43
+ this.timer = undefined;
44
+ }
45
+ }
46
+ /** Seconds left on the throttle; zero when resending is allowed. */
47
+ get secondsRemaining() {
48
+ if (this.resendAt === undefined) {
49
+ return 0;
50
+ }
51
+ return Math.max(0, Math.ceil((this.resendAt - this.nowTick) / 1000));
52
+ }
53
+ get canResend() {
54
+ return this.secondsRemaining === 0;
55
+ }
56
+ }
57
+ __decorate([
58
+ MapToComponentField({ writable: true })
59
+ ], ResendThrottleSlimmer.prototype, "resendAt", void 0);
60
+ __decorate([
61
+ MapToComponentField({ writable: true })
62
+ ], ResendThrottleSlimmer.prototype, "nowTick", void 0);
63
+ __decorate([
64
+ MapToComponentMethod('disconnectedCallback')
65
+ ], ResendThrottleSlimmer.prototype, "stop", null);
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The pattern's own styling, which is now only *layout*.
3
+ *
4
+ * Every control inside the card is a Vega element, and Vega styles its own
5
+ * internals from the design system's tokens. What is left for the pattern is
6
+ * the arrangement: the card surface, the rhythm between fields, and the
7
+ * supporting prose. There is deliberately no rule here for an input, a button,
8
+ * a checkbox or an alert — re-styling those would fork guarantees the design
9
+ * system is accountable for.
10
+ *
11
+ * Shadow DOM also means these rules cannot reach inside a `vega-*` element even
12
+ * if someone tried; Vega's own custom properties are the supported route.
13
+ */
14
+
15
+ :host {
16
+ display: block;
17
+ width: 100%;
18
+ max-width: var(--uds-pattern-login-card-max-width);
19
+ box-sizing: border-box;
20
+
21
+ /** Maximum width of the card. */
22
+ --uds-pattern-login-card-max-width: 35rem;
23
+ }
24
+
25
+ :host *,
26
+ :host *::before,
27
+ :host *::after {
28
+ box-sizing: inherit;
29
+ }
30
+
31
+ /* --- Card surface ------------------------------------------------------- */
32
+
33
+ .card {
34
+ padding: var(--uds-pattern-space-2xl);
35
+ background: var(--uds-pattern-color-surface);
36
+ border-radius: var(--uds-pattern-radius-lg);
37
+ box-shadow: var(--uds-pattern-shadow-card);
38
+ }
39
+
40
+ .card__heading {
41
+ margin: 0 0 var(--uds-pattern-space-sm);
42
+ font-size: var(--uds-pattern-font-size-lg);
43
+ font-weight: var(--uds-pattern-font-weight-bold);
44
+ line-height: var(--uds-pattern-line-height-md);
45
+ }
46
+
47
+ /* The heading takes focus on a step change so the change is announced; it is
48
+ not interactive, so it should not look it.
49
+
50
+ There is deliberately no :focus-visible ring here. The heading is
51
+ tabindex="-1", so a keyboard user can never reach it themselves — the only
52
+ thing that ever focuses it is our own step-change call. Chromium matches
53
+ :focus-visible on a programmatic focus regardless of whether the last
54
+ interaction was a pointer, so a ring would draw a box around the heading on
55
+ every step change for every user, mouse included. Announcement is the whole
56
+ job here; the visual belongs to whichever control we focus next. */
57
+ .card__heading:focus,
58
+ .card__heading:focus-visible {
59
+ outline: none;
60
+ }
61
+
62
+ .card__description {
63
+ margin: 0 0 var(--uds-pattern-space-lg);
64
+ font-size: var(--uds-pattern-font-size-sm);
65
+ color: var(--uds-pattern-color-text-subtle);
66
+ }
67
+
68
+ .card__footer {
69
+ margin-top: var(--uds-pattern-space-xl);
70
+ padding-top: var(--uds-pattern-space-xl);
71
+ border-top: 1px solid var(--uds-pattern-color-border);
72
+ font-size: var(--uds-pattern-font-size-sm);
73
+ color: var(--uds-pattern-color-text-subtle);
74
+ }
75
+
76
+ .card__footer p {
77
+ margin: 0;
78
+ }
79
+
80
+ /* --- Rhythm ------------------------------------------------------------- */
81
+
82
+ /* `vega-form` renders `<form><slot/></form>` into its own shadow root, so the
83
+ controls written inside it are slotted and are laid out by that internal
84
+ `<form>` — not by this host. A `display: flex` here would wrap exactly one
85
+ flex item (the internal form), so `gap` would space nothing at all. The
86
+ arrangement therefore belongs to `.form__body`, which the pattern owns.
87
+
88
+ Only the form's own offset from the content above it is set here. */
89
+ .form {
90
+ margin-top: var(--uds-pattern-space-lg);
91
+ }
92
+
93
+ /* The real layout container: a plain element in this shadow root whose real
94
+ children are the step's controls, so flex applies as written. Making them
95
+ flex items also blockifies them, which removes the inline-block line-box
96
+ whitespace the controls would otherwise be subject to.
97
+
98
+ The gap is measured from the approved sign-in design rather than picked: the
99
+ reference puts 31–32px between the input, the checkbox and the button, which
100
+ is `space-xl`. */
101
+ .form__body {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: var(--uds-pattern-space-xl);
105
+ align-items: stretch;
106
+ }
107
+
108
+ .alert {
109
+ display: block;
110
+ margin-bottom: var(--uds-pattern-space-md);
111
+ }
112
+
113
+ .options {
114
+ display: block;
115
+ }
116
+
117
+ /* --- Supporting prose --------------------------------------------------- */
118
+
119
+ .prose {
120
+ margin: 0;
121
+ font-size: var(--uds-pattern-font-size-sm);
122
+ color: var(--uds-pattern-color-text-subtle);
123
+ }
124
+
125
+ .prose p {
126
+ margin: 0 0 var(--uds-pattern-space-xs);
127
+ }
128
+
129
+ .prose p:last-child {
130
+ margin-bottom: 0;
131
+ }
132
+
133
+ .prose__lead {
134
+ font-weight: var(--uds-pattern-font-weight-bold);
135
+ color: var(--uds-pattern-color-text);
136
+ }
137
+
138
+ /* Links sit inline within a sentence, so they must not become block boxes. */
139
+ .link {
140
+ display: inline-block;
141
+ vertical-align: baseline;
142
+ }
143
+
144
+ .inline-links {
145
+ display: flex;
146
+ flex-wrap: wrap;
147
+ gap: var(--uds-pattern-space-md);
148
+ }
149
+
150
+ /* A resend countdown is a status, not a control — it is rendered as text and
151
+ styled to read as the disabled sibling of the link it replaces. */
152
+ .countdown {
153
+ color: var(--uds-pattern-color-text-subtle);
154
+ }