@visns-studio/visns-components 6.3.16 → 6.3.18
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.
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
@use 'controls' as *;
|
|
2
2
|
|
|
3
|
+
/* ============================================================================
|
|
4
|
+
Profile — density hooks
|
|
5
|
+
----------------------------------------------------------------------------
|
|
6
|
+
The screen is an account-settings page: a tab rail on the left, and a panel
|
|
7
|
+
whose tabs are a stack of titled sections rather than one flat form. Each
|
|
8
|
+
section is the classic settings two-column — what the section is on the
|
|
9
|
+
left, the controls on the right — so the panel's right half is used rather
|
|
10
|
+
than left empty.
|
|
11
|
+
|
|
12
|
+
Every measurement below is written `var(--hook, <the value it always had>)`,
|
|
13
|
+
so an unset hook is a no-op and a project retunes the page from `:root`
|
|
14
|
+
without forking the component:
|
|
15
|
+
|
|
16
|
+
--profile-rail-width the tab rail column. default: 14rem
|
|
17
|
+
--profile-content-width the measure the sections and the footer share,
|
|
18
|
+
so Save lands under the right edge of the
|
|
19
|
+
fields. default: 68rem
|
|
20
|
+
--profile-aside-width the section's title/description column.
|
|
21
|
+
default: 18rem
|
|
22
|
+
--profile-signature-width the signature pad. default: 29rem
|
|
23
|
+
|
|
24
|
+
Literal fallbacks throughout: `--spacing-*`, `--font-size-*`, `--radius-*`,
|
|
25
|
+
`--muted-color` and `--surface-color` are not in every consuming project's
|
|
26
|
+
token set, and a bare `var()` for a token that does not exist is invalid at
|
|
27
|
+
computed-value time — the whole declaration is dropped, which is how a form
|
|
28
|
+
like this silently loses its padding.
|
|
29
|
+
========================================================================= */
|
|
30
|
+
|
|
3
31
|
.btn {
|
|
4
32
|
@include button-primary;
|
|
5
33
|
}
|
|
6
|
-
/* Profile.module.scss */
|
|
7
|
-
|
|
8
|
-
/* Form field label animation */
|
|
9
|
-
input:not(:placeholder-shown) + .fi__span,
|
|
10
|
-
textarea:not(:placeholder-shown) + .fi__span,
|
|
11
|
-
select:not(:placeholder-shown) + .fi__span,
|
|
12
|
-
input:focus + .fi__span,
|
|
13
|
-
textarea:focus + .fi__span,
|
|
14
|
-
select:focus + .fi__span {
|
|
15
|
-
transform: translateY(-40%) translateX(10px) scale(0.9);
|
|
16
|
-
opacity: 1;
|
|
17
|
-
padding: 0 0.25rem;
|
|
18
|
-
background: var(--tertiary-color);
|
|
19
|
-
font-weight: 400;
|
|
20
|
-
border-radius: var(--br);
|
|
21
|
-
}
|
|
22
34
|
|
|
23
35
|
.grid {
|
|
24
36
|
width: 100%;
|
|
@@ -50,260 +62,406 @@ select:focus + .fi__span {
|
|
|
50
62
|
box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
/* The rail/panel row.
|
|
66
|
+
|
|
67
|
+
It was a wrapping flex row of `flex: 1` against `flex: 0 1 80%` — two
|
|
68
|
+
percentage bases plus a gap, which is the arrangement that puts the panel's
|
|
69
|
+
right edge past the page's right padding while the rail keeps its left one,
|
|
70
|
+
and the reason the page read as lopsided. Two grid tracks always sum to the
|
|
71
|
+
container, gap included, so the two cards sit inside the same gutters. */
|
|
53
72
|
.grid__subrow {
|
|
54
73
|
width: 100%;
|
|
55
|
-
display:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns:
|
|
76
|
+
minmax(0, var(--profile-rail-width, 14rem))
|
|
77
|
+
minmax(0, 1fr);
|
|
78
|
+
/* The rail is as tall as its two tabs. `stretch` — the flex and grid
|
|
79
|
+
default — ran it the full height of the panel beside it, so a card
|
|
80
|
+
holding two links was most of a screen of empty white. */
|
|
81
|
+
align-items: start;
|
|
82
|
+
gap: var(--spacing-md, 1rem);
|
|
83
|
+
|
|
84
|
+
@media (max-width: 900px) {
|
|
85
|
+
grid-template-columns: minmax(0, 1fr);
|
|
86
|
+
}
|
|
59
87
|
}
|
|
60
88
|
|
|
89
|
+
/* The tab rail. Same spec as `.grid__subnav` in GenericIndex/GenericDetail —
|
|
90
|
+
a flat surface, a hairline, and the framing of the items left to
|
|
91
|
+
TableFilter's own stylesheet, which is what actually draws them.
|
|
92
|
+
|
|
93
|
+
Deliberately not sticky: neither of the two pages that share this rail is,
|
|
94
|
+
and the app's nav bar is itself `position: sticky` at `top: 0` on a higher
|
|
95
|
+
z-index, so a rail stuck to the top of the viewport would slide underneath
|
|
96
|
+
it. */
|
|
61
97
|
.grid__subnav {
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
background: var(--surface-color, #fff);
|
|
99
|
+
border: 1px solid var(--border-color, #e1e1e1);
|
|
64
100
|
border-radius: var(--br);
|
|
65
|
-
border: 1px solid var(--border-color);
|
|
66
101
|
box-sizing: border-box;
|
|
67
|
-
padding:
|
|
102
|
+
padding: var(--spacing-xs, 0.25rem);
|
|
68
103
|
margin: 8px 0;
|
|
69
104
|
height: auto;
|
|
70
|
-
box-shadow:
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.grid__subnav > ul {
|
|
74
|
-
width: 100%;
|
|
75
|
-
list-style: none;
|
|
76
|
-
padding: 0;
|
|
77
|
-
margin: 0;
|
|
78
|
-
display: flex;
|
|
79
|
-
flex-wrap: wrap;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.grid__subnav > ul li {
|
|
83
|
-
width: 100%;
|
|
84
|
-
margin-bottom: 3px;
|
|
105
|
+
box-shadow: none;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
text-decoration: none;
|
|
93
|
-
background: rgba(var(--primary-rgb), 0.05);
|
|
94
|
-
color: var(--primary-color);
|
|
95
|
-
border-radius: 5px;
|
|
96
|
-
transition: background 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
97
|
-
outline: none;
|
|
98
|
-
cursor: pointer;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.grid__subnav > ul li:hover a {
|
|
102
|
-
background: var(--primary-color);
|
|
103
|
-
color: var(--tertiary-color);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.grid__subnav > ul li .subactive {
|
|
107
|
-
font-weight: 700;
|
|
108
|
-
background: var(--primary-color);
|
|
109
|
-
color: var(--tertiary-color);
|
|
108
|
+
/* `.tableFilterContainer` carries a 20px bottom margin, which is the right
|
|
109
|
+
amount of room under a rail that runs the height of the page and dead space
|
|
110
|
+
under one that hugs its two items. */
|
|
111
|
+
.grid__subnav > div {
|
|
112
|
+
margin-bottom: 0;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
.grid__subcontent {
|
|
113
|
-
|
|
114
|
-
background: var(--tertiary-color);
|
|
116
|
+
background: var(--surface-color, #fff);
|
|
115
117
|
border-radius: var(--br);
|
|
116
|
-
border: 1px solid
|
|
118
|
+
border: 1px solid var(--border-color, #e1e1e1);
|
|
117
119
|
box-sizing: border-box;
|
|
118
|
-
|
|
120
|
+
/* The card has none of its own: its header, sections and footer each own
|
|
121
|
+
their padding, and a couple of pixels here left the header's hairline
|
|
122
|
+
floating short of the card edge. */
|
|
123
|
+
padding: 0;
|
|
119
124
|
margin: 8px 0;
|
|
125
|
+
min-width: 0;
|
|
120
126
|
height: auto;
|
|
121
127
|
position: relative;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
/* The tab panel: header, sections, footer. The card around it has no padding
|
|
131
|
+
of its own, so each of those three owns its inset — which is what lets the
|
|
132
|
+
header's and the footer's hairlines run the full width of the card while the
|
|
133
|
+
sections in between stay on a measure. */
|
|
134
|
+
.gridtxt {
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
/* The panel header. Same spec as the unified page header documented at the top
|
|
140
|
+
of GenericIndex.module.scss — the panel's own surface, one bottom hairline,
|
|
141
|
+
the title left-aligned on the padding the body below it uses, one step down
|
|
142
|
+
in type size because this names a tab inside a page rather than the page. */
|
|
131
143
|
.gridtxt__header {
|
|
132
144
|
width: 100%;
|
|
133
|
-
display:
|
|
134
|
-
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: space-between;
|
|
148
|
+
gap: var(--spacing-md, 1rem);
|
|
135
149
|
box-sizing: border-box;
|
|
136
|
-
padding:
|
|
137
|
-
|
|
150
|
+
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
|
|
151
|
+
background: none;
|
|
152
|
+
border-bottom: 1px solid var(--border-color, #e1e1e1);
|
|
153
|
+
border-radius: 0;
|
|
138
154
|
margin-bottom: 0;
|
|
139
|
-
|
|
140
|
-
text-align: center;
|
|
155
|
+
text-align: left;
|
|
141
156
|
color: var(--paragraph-color);
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
.gridtxt__header span {
|
|
145
160
|
font-weight: 700;
|
|
161
|
+
font-size: var(--font-size-lg, 1.125rem);
|
|
162
|
+
line-height: 1.25;
|
|
146
163
|
}
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
/* --- Sections -------------------------------------------------------------
|
|
166
|
+
A settings tab is a list of decisions, not one continuous form. Each of them
|
|
167
|
+
gets a title, a line saying what it is for, and only the controls that
|
|
168
|
+
belong to it; a hairline separates one from the next.
|
|
169
|
+
|
|
170
|
+
The two columns are what keeps the panel from having an empty right half:
|
|
171
|
+
the prose that used to have nowhere to live now occupies the left third, and
|
|
172
|
+
the fields take a measure a name actually wants rather than 600px of empty
|
|
173
|
+
box. */
|
|
174
|
+
.form {
|
|
155
175
|
box-sizing: border-box;
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
max-width: var(--profile-content-width, 68rem);
|
|
177
|
+
padding: 0 var(--spacing-lg, 1.5rem);
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
.section {
|
|
181
|
+
display: grid;
|
|
182
|
+
grid-template-columns:
|
|
183
|
+
minmax(0, var(--profile-aside-width, 18rem))
|
|
184
|
+
minmax(0, 1fr);
|
|
185
|
+
align-items: start;
|
|
186
|
+
gap: var(--spacing-md, 1rem) var(--spacing-xl, 2rem);
|
|
187
|
+
padding: var(--spacing-lg, 1.5rem) 0;
|
|
188
|
+
|
|
189
|
+
@media (max-width: 900px) {
|
|
190
|
+
grid-template-columns: minmax(0, 1fr);
|
|
191
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
192
|
+
}
|
|
171
193
|
}
|
|
172
194
|
|
|
173
|
-
.
|
|
174
|
-
|
|
195
|
+
.section + .section {
|
|
196
|
+
border-top: 1px solid var(--border-color, #e1e1e1);
|
|
175
197
|
}
|
|
176
198
|
|
|
177
|
-
.
|
|
178
|
-
|
|
199
|
+
.sectionAside {
|
|
200
|
+
min-width: 0;
|
|
179
201
|
}
|
|
180
202
|
|
|
181
|
-
.
|
|
182
|
-
|
|
203
|
+
.sectionTitle {
|
|
204
|
+
margin: 0;
|
|
205
|
+
font-size: var(--font-size-md, 0.9375rem);
|
|
206
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
207
|
+
line-height: 1.3;
|
|
208
|
+
letter-spacing: 0;
|
|
209
|
+
color: var(--heading-color, #10192a);
|
|
183
210
|
}
|
|
184
211
|
|
|
185
|
-
.
|
|
186
|
-
|
|
212
|
+
.sectionDescription {
|
|
213
|
+
margin: 0.35rem 0 0;
|
|
214
|
+
max-width: 42ch;
|
|
215
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
216
|
+
line-height: 1.55;
|
|
217
|
+
color: var(--muted-color, #6b7285);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.sectionFields {
|
|
187
221
|
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
188
223
|
align-items: flex-start;
|
|
189
|
-
|
|
224
|
+
gap: var(--spacing-md, 1rem);
|
|
225
|
+
min-width: 0;
|
|
190
226
|
}
|
|
191
227
|
|
|
228
|
+
/* Two fields on a line where the column is wide enough for two, one where it
|
|
229
|
+
is not. `auto-fit` measures the column it is actually in, which a media
|
|
230
|
+
query on the viewport cannot do — the fields column is two-thirds of the
|
|
231
|
+
panel, and the panel is a fraction of the page. */
|
|
232
|
+
.fieldPair {
|
|
233
|
+
width: 100%;
|
|
234
|
+
display: grid;
|
|
235
|
+
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
|
|
236
|
+
align-items: start;
|
|
237
|
+
gap: var(--spacing-md, 1rem);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* --- Fields ---------------------------------------------------------------
|
|
241
|
+
The notched floating label — a `<span>` absolutely positioned inside the
|
|
242
|
+
box, sliding up into a gap in the border once the field held something —
|
|
243
|
+
was the last of that treatment in the library, and it read a decade older
|
|
244
|
+
than every other control on the page. The label is a line of text above the
|
|
245
|
+
box now, on the same uppercase/semibold/muted spec the library gives a small
|
|
246
|
+
label everywhere else (`h5`/`h6` in global.css, the section headings in
|
|
247
|
+
GenericDetail).
|
|
248
|
+
|
|
249
|
+
Profile owns these classes outright: nothing here reaches the shared
|
|
250
|
+
`Field`/`Form` components, whose floating treatment is left exactly as it
|
|
251
|
+
is. */
|
|
192
252
|
.formItem {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
253
|
+
display: flex;
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
gap: 0.35rem;
|
|
256
|
+
min-width: 0;
|
|
196
257
|
box-sizing: border-box;
|
|
258
|
+
}
|
|
197
259
|
|
|
198
|
-
|
|
199
|
-
|
|
260
|
+
.fieldLabel {
|
|
261
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
262
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
263
|
+
letter-spacing: 0.06em;
|
|
264
|
+
text-transform: uppercase;
|
|
265
|
+
line-height: 1.3;
|
|
266
|
+
color: var(--muted-color, #6b7285);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Why the `!important`s.
|
|
270
|
+
----------------------------------------------------------------------------
|
|
271
|
+
`global.css` — which this component imports — dresses every text input on
|
|
272
|
+
the page through
|
|
273
|
+
|
|
274
|
+
textarea, select,
|
|
275
|
+
input[type]:not([type='search'])…:not(.react-datepicker__year-select)
|
|
276
|
+
|
|
277
|
+
Thirteen `:not()`s put that selector at specificity (0, 14, 1). A CSS module
|
|
278
|
+
can only ever produce one hashed class, (0, 1, 0), so *every* property that
|
|
279
|
+
rule sets — background, border, radius, padding, font-size, line-height,
|
|
280
|
+
box-shadow — lands on these fields no matter what is written here, and the
|
|
281
|
+
only way a scoped stylesheet can restate one is `!important`. That is the
|
|
282
|
+
sole reason for them below; a property the global rule does not touch
|
|
283
|
+
(`min-height`, `font-family`, `::placeholder`) is left plain and comes from
|
|
284
|
+
the shared `input-base` mixin as usual.
|
|
285
|
+
|
|
286
|
+
This is also why the previous pass's field styling never showed: it was
|
|
287
|
+
correct CSS that never won. Fixing it at the source means rewriting a
|
|
288
|
+
selector every component in the library is sitting under, which is a
|
|
289
|
+
separate job. */
|
|
290
|
+
.input {
|
|
291
|
+
/* The shared control shape: one height, one radius, one border, the same
|
|
292
|
+
as every other input in 6.3. */
|
|
293
|
+
@include input-base;
|
|
294
|
+
|
|
295
|
+
padding: 0 0.65rem !important;
|
|
296
|
+
border: 1px solid var(--border-color) !important;
|
|
297
|
+
border-radius: var(--radius-sm) !important;
|
|
298
|
+
background: var(--paper) !important;
|
|
299
|
+
font-size: var(--font-size-md) !important;
|
|
300
|
+
line-height: 1.25 !important;
|
|
301
|
+
|
|
302
|
+
&:hover:not(:disabled):not([readonly]):not(.inputError) {
|
|
303
|
+
border-color: var(--border-color-dark) !important;
|
|
200
304
|
}
|
|
201
|
-
}
|
|
202
305
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
306
|
+
&:focus {
|
|
307
|
+
outline: none;
|
|
308
|
+
border-color: var(--primary-color) !important;
|
|
309
|
+
/* A ring rather than a thicker border, so nothing shifts on focus. */
|
|
310
|
+
box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32)) !important;
|
|
311
|
+
}
|
|
207
312
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
313
|
+
/* Email is filled from the profile and only an administrator can change
|
|
314
|
+
it. Same surface the shared mixin gives a disabled control, so it reads
|
|
315
|
+
as "not yours to edit" rather than as a field that has gone wrong — but
|
|
316
|
+
the value keeps full-strength ink, because it is still information the
|
|
317
|
+
user came here to read. */
|
|
318
|
+
&[readonly] {
|
|
319
|
+
background: var(--bg-color-secondary, #f4f5f7) !important;
|
|
320
|
+
cursor: default;
|
|
321
|
+
}
|
|
216
322
|
}
|
|
217
323
|
|
|
218
|
-
.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
324
|
+
/* Set from JS as `styles.inputError`, appended to the input's class list. It is
|
|
325
|
+
written as a compound so it outranks `.input:focus` above — an invalid field
|
|
326
|
+
that has been focused to correct it should still look invalid. */
|
|
327
|
+
.input.inputError {
|
|
328
|
+
border-color: var(--danger-color, #e74c3c) !important;
|
|
329
|
+
box-shadow: 0 0 0 3px var(--danger-light, rgba(231, 76, 60, 0.14)) !important;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* --- Password strength ---------------------------------------------------
|
|
333
|
+
`react-password-strength-bar` styles its own markup inline, so a class can
|
|
334
|
+
only reach it with `!important` — that is what every rule below is for, and
|
|
335
|
+
the only reason for it. The bar itself is four flex children; the score word
|
|
336
|
+
is the `<p>` after them.
|
|
337
|
+
|
|
338
|
+
The meter is rendered only once the field has a value (see Profile.jsx), so
|
|
339
|
+
nothing here has to hide the "too short" state that used to sit under an
|
|
340
|
+
empty password box telling the user off before they had typed. */
|
|
341
|
+
.strengthMeter {
|
|
342
|
+
/* Sits directly under the input it measures, close enough to read as part
|
|
343
|
+
of the field rather than as a rule floating beneath it. The distance is
|
|
344
|
+
`.formItem`'s own gap — this only cancels the library's default so the
|
|
345
|
+
two do not stack. */
|
|
346
|
+
margin-top: 0 !important;
|
|
347
|
+
|
|
348
|
+
/* The segments: 2px hairlines with 4px of white between them read as four
|
|
349
|
+
unrelated underlines. A little more weight and a rounded cap makes them
|
|
350
|
+
one meter. */
|
|
351
|
+
div > div {
|
|
352
|
+
height: 4px !important;
|
|
353
|
+
border-radius: var(--radius-pill, 999px) !important;
|
|
354
|
+
}
|
|
226
355
|
}
|
|
227
356
|
|
|
228
|
-
.
|
|
229
|
-
.
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
357
|
+
.strengthWord {
|
|
358
|
+
margin: 0.3rem 0 0 !important;
|
|
359
|
+
font-size: var(--font-size-xs, 0.75rem) !important;
|
|
360
|
+
line-height: 1.2 !important;
|
|
361
|
+
color: var(--muted-color, #6b7688) !important;
|
|
362
|
+
text-align: right !important;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* --- Signature ------------------------------------------------------------
|
|
366
|
+
signature_pad maps a pointer to a canvas coordinate through the element's
|
|
367
|
+
bounding rect and assumes that rect is the drawing surface. A border and
|
|
368
|
+
padding on the canvas itself sit inside the rect and outside the surface, so
|
|
369
|
+
every stroke landed up and to the left of the cursor by exactly the border
|
|
370
|
+
plus the padding. The frame belongs on the wrapper; the canvas keeps its own
|
|
371
|
+
box model and nothing else.
|
|
372
|
+
|
|
373
|
+
The pad's size comes from `--profile-signature-width` and the `height` prop
|
|
374
|
+
in Profile.jsx together. The wrapper library writes `width`/`height`
|
|
375
|
+
attributes of `offsetWidth * dpr` by `height prop * dpr` and scales the
|
|
376
|
+
context by `dpr`; with `width: 100%` and `height: auto` the canvas's own
|
|
377
|
+
intrinsic ratio then renders it at exactly `height` CSS pixels, so the
|
|
378
|
+
attribute geometry and the CSS geometry agree and a stroke lands under the
|
|
379
|
+
cursor. Change one of the two and check the other.
|
|
380
|
+
|
|
381
|
+
Neither may be made smaller. Profile loads a stored signature into the pad
|
|
382
|
+
on mount and `handleSubmitProfile` re-exports the canvas on any save, so a
|
|
383
|
+
smaller pad writes back a cropped signature without anyone asking it to —
|
|
384
|
+
the full reasoning is at the `height` prop in Profile.jsx. */
|
|
385
|
+
.signatureField {
|
|
236
386
|
width: 100%;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
.
|
|
243
|
-
|
|
244
|
-
.select:hover {
|
|
245
|
-
border: 1px solid rgba(var(--highlight-color-rgb), 0.85);
|
|
246
|
-
transition: border 0.15s linear;
|
|
387
|
+
display: flex;
|
|
388
|
+
flex-direction: column;
|
|
389
|
+
gap: 0.4rem;
|
|
390
|
+
/* A signature is a fixed-size mark, not a field that gains anything from
|
|
391
|
+
more room. Left to fill the row it became the largest thing on the
|
|
392
|
+
page. */
|
|
393
|
+
max-width: var(--profile-signature-width, 29rem);
|
|
247
394
|
}
|
|
248
395
|
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
.
|
|
252
|
-
|
|
396
|
+
/* The caption and the Clear action share the line above the pad. Clear used to
|
|
397
|
+
be promoted into the page footer beside Save, where a control that wipes one
|
|
398
|
+
field sat at the same rank as the one that saves the screen. */
|
|
399
|
+
.signatureHeader {
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: space-between;
|
|
403
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
253
404
|
}
|
|
254
405
|
|
|
255
|
-
.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
406
|
+
.signatureCaption {
|
|
407
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
408
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
409
|
+
letter-spacing: 0.06em;
|
|
410
|
+
text-transform: uppercase;
|
|
411
|
+
line-height: 1.3;
|
|
412
|
+
color: var(--muted-color, #6b7285);
|
|
259
413
|
}
|
|
260
414
|
|
|
261
|
-
/* Removed signatureContainer and signatureLabel styles as they're no longer needed */
|
|
262
|
-
|
|
263
415
|
.signaturePadWrapper {
|
|
264
416
|
width: 100%;
|
|
265
|
-
margin-bottom: 0.5rem;
|
|
266
417
|
position: relative;
|
|
267
418
|
z-index: 1;
|
|
419
|
+
box-sizing: border-box;
|
|
420
|
+
padding: var(--spacing-xs, 0.25rem);
|
|
421
|
+
border: 1px solid var(--border-color, #e1e1e1);
|
|
422
|
+
border-radius: var(--radius-sm, 6px);
|
|
423
|
+
background: var(--paper, #fff);
|
|
268
424
|
|
|
269
425
|
canvas {
|
|
426
|
+
display: block;
|
|
427
|
+
width: 100%;
|
|
270
428
|
max-width: 100%;
|
|
271
429
|
height: auto !important;
|
|
272
430
|
}
|
|
273
431
|
}
|
|
274
432
|
|
|
433
|
+
/* Nothing here changes the canvas's box model — see the note above. */
|
|
434
|
+
.sigcanvas {
|
|
435
|
+
cursor: crosshair;
|
|
436
|
+
touch-action: none;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* A small, quiet button for an action that belongs to one field rather than to
|
|
440
|
+
the page. */
|
|
441
|
+
.btnQuiet {
|
|
442
|
+
@include button-secondary;
|
|
443
|
+
@include button-small;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* The panel's footer. Save was `position: fixed` in the bottom-right corner of
|
|
447
|
+
the viewport, floating over whatever the user had scrolled to. It is the
|
|
448
|
+
bottom edge of the card now, on the same hairline-and-padding spec as the
|
|
449
|
+
header, and it shares the sections' measure so it lands under the right edge
|
|
450
|
+
of the fields rather than out at the card's edge. */
|
|
275
451
|
.polActions {
|
|
276
|
-
position: fixed;
|
|
277
|
-
bottom: 20px;
|
|
278
|
-
right: 20px;
|
|
279
|
-
width: auto;
|
|
280
452
|
display: flex;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
453
|
+
align-items: center;
|
|
454
|
+
justify-content: flex-end;
|
|
455
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
456
|
+
width: 100%;
|
|
457
|
+
max-width: var(--profile-content-width, 68rem);
|
|
458
|
+
box-sizing: border-box;
|
|
459
|
+
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
|
|
460
|
+
border-top: 1px solid var(--border-color, #e1e1e1);
|
|
290
461
|
}
|
|
291
462
|
|
|
292
463
|
.polActions button {
|
|
293
|
-
padding: 0.65em 1em;
|
|
294
464
|
margin: 0;
|
|
295
|
-
transition: all 0.2s ease;
|
|
296
|
-
|
|
297
|
-
&:disabled {
|
|
298
|
-
opacity: 0.6;
|
|
299
|
-
cursor: not-allowed;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
.inputError {
|
|
306
|
-
border-color: red !important;
|
|
307
465
|
}
|
|
308
466
|
|
|
309
467
|
.loading {
|
|
@@ -331,166 +489,158 @@ select:focus + .fi__span {
|
|
|
331
489
|
}
|
|
332
490
|
}
|
|
333
491
|
|
|
334
|
-
/* Two-Factor Authentication
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
492
|
+
/* --- Two-Factor Authentication -------------------------------------------
|
|
493
|
+
The tab is sectioned exactly like Overview — the status in one section, the
|
|
494
|
+
pairing walkthrough in the next — so moving between the two tabs does not
|
|
495
|
+
feel like moving between two different pages. */
|
|
496
|
+
.twoFactorStatus {
|
|
338
497
|
display: flex;
|
|
339
498
|
flex-direction: column;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
.twoFactorStatus {
|
|
343
|
-
margin-bottom: 1.5rem;
|
|
499
|
+
align-items: flex-start;
|
|
500
|
+
gap: var(--spacing-md, 1rem);
|
|
344
501
|
|
|
345
|
-
|
|
346
|
-
margin
|
|
347
|
-
font-size: 1rem;
|
|
502
|
+
h4 {
|
|
503
|
+
margin: 0;
|
|
348
504
|
display: flex;
|
|
349
505
|
align-items: center;
|
|
350
506
|
gap: 0.5rem;
|
|
351
|
-
font-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
line-height: 1.4;
|
|
357
|
-
color: var(--paragraph-color);
|
|
358
|
-
font-size: 0.9rem;
|
|
507
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
508
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
509
|
+
letter-spacing: 0.06em;
|
|
510
|
+
text-transform: uppercase;
|
|
511
|
+
color: var(--muted-color, #6b7285);
|
|
359
512
|
}
|
|
360
513
|
}
|
|
361
514
|
|
|
362
515
|
.statusEnabled {
|
|
363
|
-
color: #2ecc71;
|
|
364
|
-
font-weight:
|
|
365
|
-
padding: 0.
|
|
366
|
-
background-color: rgba(46, 204, 113, 0.1);
|
|
367
|
-
border-radius:
|
|
368
|
-
font-size: 0.
|
|
516
|
+
color: var(--success-color, #2ecc71);
|
|
517
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
518
|
+
padding: 0.15rem 0.45rem;
|
|
519
|
+
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
520
|
+
border-radius: var(--radius-sm, 6px);
|
|
521
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
522
|
+
letter-spacing: 0.04em;
|
|
369
523
|
}
|
|
370
524
|
|
|
371
525
|
.statusDisabled {
|
|
372
|
-
color: #e74c3c;
|
|
373
|
-
font-weight:
|
|
374
|
-
padding: 0.
|
|
375
|
-
background-color: rgba(231, 76, 60, 0.1);
|
|
376
|
-
border-radius:
|
|
377
|
-
font-size: 0.
|
|
526
|
+
color: var(--danger-color, #e74c3c);
|
|
527
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
528
|
+
padding: 0.15rem 0.45rem;
|
|
529
|
+
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
530
|
+
border-radius: var(--radius-sm, 6px);
|
|
531
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
532
|
+
letter-spacing: 0.04em;
|
|
378
533
|
}
|
|
379
534
|
|
|
380
535
|
.confirmedMessage {
|
|
381
|
-
margin:
|
|
536
|
+
margin: 0;
|
|
382
537
|
padding: 0.75rem;
|
|
383
|
-
background-color: rgba(46, 204, 113, 0.1);
|
|
384
|
-
border-left: 3px solid #2ecc71;
|
|
385
|
-
border-radius: var(--
|
|
386
|
-
color: #333;
|
|
387
|
-
font-size: 0.
|
|
538
|
+
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
539
|
+
border-left: 3px solid var(--success-color, #2ecc71);
|
|
540
|
+
border-radius: var(--radius-sm, 6px);
|
|
541
|
+
color: var(--paragraph-color, #333);
|
|
542
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
543
|
+
line-height: 1.5;
|
|
388
544
|
}
|
|
389
545
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
546
|
+
/* The confirmed message and the button that undoes it, stacked and left-hung
|
|
547
|
+
under the status line. */
|
|
548
|
+
.statusActions {
|
|
549
|
+
display: flex;
|
|
550
|
+
flex-direction: column;
|
|
551
|
+
align-items: flex-start;
|
|
552
|
+
gap: var(--spacing-md, 1rem);
|
|
553
|
+
}
|
|
395
554
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
555
|
+
/* The three variants each carried their own font size and padding, so a row of
|
|
556
|
+
them next to `.btn` — which comes from the shared `_controls` mixins — sat a
|
|
557
|
+
few pixels short and half a step smaller. They are colour only now; the
|
|
558
|
+
shape comes from the one place that defines it. */
|
|
559
|
+
.btnPrimary {
|
|
560
|
+
@include button-primary;
|
|
399
561
|
}
|
|
400
562
|
|
|
401
563
|
.btnSecondary {
|
|
402
|
-
|
|
403
|
-
color: #333;
|
|
404
|
-
border: 1px solid #ddd;
|
|
405
|
-
font-size: 0.85rem;
|
|
406
|
-
padding: 0.5rem 0.85rem;
|
|
407
|
-
|
|
408
|
-
&:hover:not(:disabled) {
|
|
409
|
-
background-color: var(--bg-color, #f5f3ef);
|
|
410
|
-
border-color: var(--border-color, #e1e1e1);
|
|
411
|
-
}
|
|
564
|
+
@include button-secondary;
|
|
412
565
|
}
|
|
413
566
|
|
|
414
567
|
.btnDanger {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
568
|
+
@include button-base;
|
|
569
|
+
|
|
570
|
+
background: var(--danger-color, #e74c3c);
|
|
571
|
+
border-color: var(--danger-color, #e74c3c);
|
|
572
|
+
color: #fff;
|
|
419
573
|
|
|
420
574
|
&:hover:not(:disabled) {
|
|
421
|
-
|
|
575
|
+
/* Deliberately still a solid fill rather than the library's ghost
|
|
576
|
+
`button-danger`: this is the control that turns a second factor off,
|
|
577
|
+
and it should not read as quieter than the button that turned it
|
|
578
|
+
on. */
|
|
579
|
+
filter: brightness(0.9);
|
|
422
580
|
}
|
|
423
581
|
}
|
|
424
582
|
|
|
583
|
+
/* The setup walkthrough. It used to be a bordered, shadowed card inside the
|
|
584
|
+
panel card, carrying a heading that repeated the heading above it. The
|
|
585
|
+
section owns the title and the separation now, so this is only the steps. */
|
|
425
586
|
.twoFactorSetup {
|
|
426
|
-
margin-top: 1.5rem;
|
|
427
|
-
padding: 1.5rem;
|
|
428
|
-
background-color: white;
|
|
429
|
-
border-radius: var(--br);
|
|
430
|
-
border: 1px solid #e0e0e0;
|
|
431
|
-
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
|
|
432
587
|
width: 100%;
|
|
433
|
-
|
|
434
|
-
h3 {
|
|
435
|
-
margin-bottom: 1.25rem;
|
|
436
|
-
font-size: 1rem;
|
|
437
|
-
font-weight: 500;
|
|
438
|
-
color: var(--primary-color);
|
|
439
|
-
padding-bottom: 0.75rem;
|
|
440
|
-
border-bottom: 1px solid #f0f0f0;
|
|
441
|
-
}
|
|
588
|
+
min-width: 0;
|
|
442
589
|
}
|
|
443
590
|
|
|
444
591
|
.setupSteps {
|
|
445
|
-
margin
|
|
446
|
-
|
|
447
|
-
font-size: 0.
|
|
592
|
+
margin: 0;
|
|
593
|
+
padding: 0;
|
|
594
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
448
595
|
counter-reset: step-counter;
|
|
449
596
|
list-style-type: none;
|
|
450
597
|
|
|
451
598
|
li {
|
|
452
|
-
margin-bottom: 1.5rem;
|
|
453
|
-
padding-left: 2.
|
|
599
|
+
margin-bottom: var(--spacing-lg, 1.5rem);
|
|
600
|
+
padding-left: 2.25rem;
|
|
454
601
|
position: relative;
|
|
455
602
|
|
|
603
|
+
&:last-child {
|
|
604
|
+
margin-bottom: 0;
|
|
605
|
+
}
|
|
606
|
+
|
|
456
607
|
&:before {
|
|
457
608
|
content: counter(step-counter);
|
|
458
609
|
counter-increment: step-counter;
|
|
459
610
|
position: absolute;
|
|
460
611
|
left: 0;
|
|
461
612
|
top: 0;
|
|
462
|
-
width: 1.
|
|
463
|
-
height: 1.
|
|
613
|
+
width: 1.5rem;
|
|
614
|
+
height: 1.5rem;
|
|
464
615
|
background-color: var(--primary-color);
|
|
465
|
-
color:
|
|
616
|
+
color: #fff;
|
|
466
617
|
border-radius: 50%;
|
|
467
618
|
display: flex;
|
|
468
619
|
align-items: center;
|
|
469
620
|
justify-content: center;
|
|
470
|
-
font-size: 0.
|
|
471
|
-
font-weight:
|
|
621
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
622
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
472
623
|
}
|
|
473
624
|
|
|
474
625
|
p {
|
|
475
|
-
margin-bottom: 0.
|
|
476
|
-
font-weight: 500;
|
|
477
|
-
font-size: 0.
|
|
478
|
-
|
|
626
|
+
margin-bottom: var(--spacing-sm, 0.5rem);
|
|
627
|
+
font-weight: var(--font-weight-medium, 500);
|
|
628
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
629
|
+
line-height: 1.5;
|
|
630
|
+
color: var(--text-color, #333);
|
|
479
631
|
}
|
|
480
632
|
|
|
481
633
|
ul {
|
|
482
|
-
margin
|
|
483
|
-
margin-bottom: 1rem;
|
|
634
|
+
margin: 0 0 var(--spacing-md, 1rem);
|
|
484
635
|
list-style-type: none;
|
|
485
|
-
background-color
|
|
486
|
-
padding: 0.
|
|
487
|
-
border-radius: var(--
|
|
636
|
+
background: var(--bg-color-secondary, #f9f9f9);
|
|
637
|
+
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
638
|
+
border-radius: var(--radius-sm, 6px);
|
|
488
639
|
border-left: 3px solid var(--primary-color);
|
|
489
640
|
|
|
490
641
|
li {
|
|
491
|
-
margin-bottom: 0.
|
|
492
|
-
font-size: 0.
|
|
493
|
-
padding-left: 0;
|
|
642
|
+
margin-bottom: 0.4rem;
|
|
643
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
494
644
|
position: relative;
|
|
495
645
|
padding-left: 1rem;
|
|
496
646
|
|
|
@@ -515,7 +665,7 @@ select:focus + .fi__span {
|
|
|
515
665
|
a {
|
|
516
666
|
color: var(--primary-color);
|
|
517
667
|
text-decoration: none;
|
|
518
|
-
font-weight: 500;
|
|
668
|
+
font-weight: var(--font-weight-medium, 500);
|
|
519
669
|
|
|
520
670
|
&:hover {
|
|
521
671
|
text-decoration: underline;
|
|
@@ -528,20 +678,20 @@ select:focus + .fi__span {
|
|
|
528
678
|
|
|
529
679
|
.qrCodeContainer {
|
|
530
680
|
display: flex;
|
|
531
|
-
justify-content:
|
|
532
|
-
margin: 0
|
|
533
|
-
padding: 1rem;
|
|
534
|
-
background
|
|
535
|
-
border-radius: var(--
|
|
536
|
-
border: 1px solid #e0e0e0;
|
|
537
|
-
width
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
681
|
+
justify-content: flex-start;
|
|
682
|
+
margin: 0 0 var(--spacing-sm, 0.5rem);
|
|
683
|
+
padding: var(--spacing-md, 1rem);
|
|
684
|
+
background: var(--paper, #fff);
|
|
685
|
+
border-radius: var(--radius-sm, 6px);
|
|
686
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
687
|
+
/* Was full width with the code centred in it, which drew a wide empty
|
|
688
|
+
panel around a 180px square. It is the size of what it holds. */
|
|
689
|
+
width: max-content;
|
|
690
|
+
max-width: 100%;
|
|
691
|
+
/* The panel used to lift and deepen its shadow on hover. Nothing in it is
|
|
692
|
+
clickable — it is a QR code to point a phone at — so the affordance was
|
|
693
|
+
promising an interaction that does not exist. */
|
|
694
|
+
box-shadow: none;
|
|
545
695
|
}
|
|
546
696
|
|
|
547
697
|
.qrCodeSvg {
|
|
@@ -561,9 +711,9 @@ select:focus + .fi__span {
|
|
|
561
711
|
display: flex;
|
|
562
712
|
align-items: center;
|
|
563
713
|
justify-content: center;
|
|
564
|
-
background-color: #f5f5f5;
|
|
565
|
-
color: #666;
|
|
566
|
-
font-size: 0.
|
|
714
|
+
background-color: var(--bg-color-secondary, #f5f5f5);
|
|
715
|
+
color: var(--muted-color, #666);
|
|
716
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
567
717
|
border-radius: 4px;
|
|
568
718
|
animation: pulse 1.5s infinite ease-in-out;
|
|
569
719
|
}
|
|
@@ -583,146 +733,131 @@ select:focus + .fi__span {
|
|
|
583
733
|
.secretKeyContainer {
|
|
584
734
|
display: flex;
|
|
585
735
|
align-items: center;
|
|
586
|
-
margin: 0
|
|
587
|
-
gap: 0.5rem;
|
|
588
|
-
background-color
|
|
589
|
-
padding: 0.
|
|
590
|
-
border-radius: var(--
|
|
591
|
-
border: 1px solid #e0e0e0;
|
|
736
|
+
margin: 0 0 var(--spacing-sm, 0.5rem);
|
|
737
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
738
|
+
background: var(--bg-color-secondary, #f9f9f9);
|
|
739
|
+
padding: var(--spacing-sm, 0.5rem);
|
|
740
|
+
border-radius: var(--radius-sm, 6px);
|
|
741
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
592
742
|
width: 100%;
|
|
743
|
+
box-sizing: border-box;
|
|
593
744
|
}
|
|
594
745
|
|
|
595
746
|
.secretKey {
|
|
596
|
-
padding: 0.
|
|
597
|
-
background
|
|
598
|
-
border-radius: var(--
|
|
747
|
+
padding: 0.4rem 0.65rem;
|
|
748
|
+
background: var(--paper, #fff);
|
|
749
|
+
border-radius: var(--radius-sm, 6px);
|
|
599
750
|
font-family: monospace;
|
|
600
|
-
font-size: 0.
|
|
751
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
601
752
|
letter-spacing: 1px;
|
|
602
|
-
color: #333;
|
|
753
|
+
color: var(--text-color, #333);
|
|
603
754
|
user-select: all;
|
|
604
|
-
border: 1px solid #ddd;
|
|
755
|
+
border: 1px solid var(--border-color, #ddd);
|
|
605
756
|
flex-grow: 1;
|
|
606
|
-
|
|
757
|
+
min-width: 0;
|
|
758
|
+
overflow-wrap: anywhere;
|
|
607
759
|
}
|
|
608
760
|
|
|
609
761
|
.copyButton {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
color: white;
|
|
613
|
-
border: none;
|
|
614
|
-
border-radius: var(--br);
|
|
615
|
-
cursor: pointer;
|
|
616
|
-
font-size: 0.8rem;
|
|
617
|
-
transition: all 0.2s ease;
|
|
618
|
-
display: flex;
|
|
619
|
-
align-items: center;
|
|
620
|
-
|
|
621
|
-
&:hover {
|
|
622
|
-
filter: brightness(90%);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
&:active {
|
|
626
|
-
transform: translateY(1px);
|
|
627
|
-
}
|
|
762
|
+
@include button-secondary;
|
|
763
|
+
@include button-small;
|
|
628
764
|
}
|
|
629
765
|
|
|
630
766
|
.verificationCodeContainer {
|
|
631
767
|
display: flex;
|
|
632
768
|
align-items: center;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
border: 1px solid #e0e0e0;
|
|
639
|
-
width: 100%;
|
|
769
|
+
flex-wrap: wrap;
|
|
770
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
771
|
+
/* Was a tinted, bordered strip around an input and a button — a third
|
|
772
|
+
surface for two controls that already have their own edges. */
|
|
773
|
+
margin: 0;
|
|
640
774
|
}
|
|
641
775
|
|
|
776
|
+
/* Same fight with `global.css` as `.input` — see the note there. */
|
|
642
777
|
.verificationCodeInput {
|
|
643
|
-
|
|
644
|
-
|
|
778
|
+
@include input-base;
|
|
779
|
+
|
|
780
|
+
width: 9rem !important;
|
|
781
|
+
flex: 0 0 auto;
|
|
782
|
+
padding: 0 0.65rem !important;
|
|
783
|
+
border: 1px solid var(--border-color) !important;
|
|
784
|
+
border-radius: var(--radius-sm) !important;
|
|
785
|
+
background: var(--paper) !important;
|
|
786
|
+
font-size: 1.05rem !important;
|
|
787
|
+
line-height: 1.25 !important;
|
|
645
788
|
letter-spacing: 4px;
|
|
646
|
-
width: 140px;
|
|
647
789
|
text-align: center;
|
|
648
|
-
border: 1px solid #ddd;
|
|
649
|
-
border-radius: var(--br);
|
|
650
790
|
font-family: monospace;
|
|
651
|
-
font-weight: 600;
|
|
652
|
-
color: #333;
|
|
653
|
-
background-color: white;
|
|
654
|
-
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
791
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
655
792
|
|
|
656
793
|
&:focus {
|
|
657
|
-
border-color: var(--primary-color);
|
|
658
794
|
outline: none;
|
|
659
|
-
|
|
795
|
+
border-color: var(--primary-color) !important;
|
|
796
|
+
box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32)) !important;
|
|
660
797
|
}
|
|
661
798
|
|
|
662
799
|
&::placeholder {
|
|
663
|
-
color: #bbb;
|
|
800
|
+
color: var(--muted-color, #bbb);
|
|
664
801
|
letter-spacing: 2px;
|
|
665
802
|
}
|
|
666
803
|
}
|
|
667
804
|
|
|
668
805
|
.recoveryCodes {
|
|
669
|
-
margin: 0
|
|
670
|
-
padding: 1rem;
|
|
671
|
-
background-color
|
|
672
|
-
border-radius: var(--
|
|
673
|
-
border: 1px solid #e0e0e0;
|
|
806
|
+
margin: 0 0 var(--spacing-sm, 0.5rem);
|
|
807
|
+
padding: var(--spacing-md, 1rem);
|
|
808
|
+
background: var(--bg-color-secondary, #f9f9f9);
|
|
809
|
+
border-radius: var(--radius-sm, 6px);
|
|
810
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
674
811
|
width: 100%;
|
|
812
|
+
box-sizing: border-box;
|
|
675
813
|
}
|
|
676
814
|
|
|
677
815
|
.recoveryCodesContainer {
|
|
678
816
|
display: grid;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
grid-template-columns: 1fr;
|
|
685
|
-
}
|
|
817
|
+
/* Was two fixed columns, which on the narrower fields column left the
|
|
818
|
+
codes stretched to twice the width of the text in them. */
|
|
819
|
+
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
|
820
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
821
|
+
margin-bottom: var(--spacing-md, 1rem);
|
|
686
822
|
}
|
|
687
823
|
|
|
688
824
|
.recoveryCode {
|
|
689
|
-
padding: 0.
|
|
690
|
-
background
|
|
691
|
-
border-radius: var(--
|
|
692
|
-
border: 1px solid #ddd;
|
|
825
|
+
padding: 0.4rem 0.65rem;
|
|
826
|
+
background: var(--paper, #fff);
|
|
827
|
+
border-radius: var(--radius-sm, 6px);
|
|
828
|
+
border: 1px solid var(--border-color, #ddd);
|
|
693
829
|
font-family: monospace;
|
|
694
|
-
font-size: 0.
|
|
830
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
695
831
|
letter-spacing: 1px;
|
|
696
|
-
color: #333;
|
|
832
|
+
color: var(--text-color, #333);
|
|
697
833
|
user-select: all;
|
|
698
|
-
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
699
834
|
text-align: center;
|
|
700
835
|
}
|
|
701
836
|
|
|
702
837
|
.noRecoveryCodes {
|
|
703
|
-
padding: 1rem;
|
|
838
|
+
padding: var(--spacing-md, 1rem);
|
|
704
839
|
text-align: center;
|
|
705
|
-
color: #666;
|
|
840
|
+
color: var(--muted-color, #666);
|
|
706
841
|
font-style: italic;
|
|
707
|
-
font-size: 0.
|
|
842
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
708
843
|
}
|
|
709
844
|
|
|
710
845
|
.recoveryCodesWarning {
|
|
711
|
-
margin
|
|
846
|
+
margin: 0;
|
|
712
847
|
padding: 0.75rem;
|
|
713
|
-
background-color: rgba(231, 76, 60, 0.1);
|
|
714
|
-
border-left: 3px solid #e74c3c;
|
|
715
|
-
border-radius: var(--
|
|
716
|
-
color: #333;
|
|
717
|
-
font-size: 0.
|
|
848
|
+
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
849
|
+
border-left: 3px solid var(--danger-color, #e74c3c);
|
|
850
|
+
border-radius: var(--radius-sm, 6px);
|
|
851
|
+
color: var(--paragraph-color, #333);
|
|
852
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
853
|
+
line-height: 1.5;
|
|
718
854
|
}
|
|
719
855
|
|
|
720
856
|
.setupActions {
|
|
721
857
|
display: flex;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
border-top: 1px solid #f0f0f0;
|
|
858
|
+
/* The steps hang off the left margin; so does the way out of them. */
|
|
859
|
+
justify-content: flex-start;
|
|
860
|
+
margin-top: var(--spacing-lg, 1.5rem);
|
|
726
861
|
}
|
|
727
862
|
|
|
728
863
|
.crmtitle {
|