@visns-studio/visns-components 6.3.17 → 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.
- package/package.json +1 -1
- package/src/components/auth/Profile.jsx +315 -192
- package/src/components/styles/Profile.module.scss +436 -396
|
@@ -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,96 +62,84 @@ 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
|
-
/*
|
|
119
|
-
|
|
120
|
-
|
|
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. */
|
|
121
123
|
padding: 0;
|
|
122
124
|
margin: 8px 0;
|
|
125
|
+
min-width: 0;
|
|
123
126
|
height: auto;
|
|
124
127
|
position: relative;
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
/* The panel
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
inside a page rather than the page.
|
|
139
|
-
|
|
140
|
-
It used to be a pale centred band with its own fill and radius: a second
|
|
141
|
-
card sitting on top of the card, and the only centred heading in the
|
|
142
|
-
library. */
|
|
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. */
|
|
143
143
|
.gridtxt__header {
|
|
144
144
|
width: 100%;
|
|
145
145
|
display: flex;
|
|
@@ -147,7 +147,7 @@ select:focus + .fi__span {
|
|
|
147
147
|
justify-content: space-between;
|
|
148
148
|
gap: var(--spacing-md, 1rem);
|
|
149
149
|
box-sizing: border-box;
|
|
150
|
-
padding: var(--spacing-md, 1rem);
|
|
150
|
+
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
|
|
151
151
|
background: none;
|
|
152
152
|
border-bottom: 1px solid var(--border-color, #e1e1e1);
|
|
153
153
|
border-radius: 0;
|
|
@@ -158,146 +158,175 @@ select:focus + .fi__span {
|
|
|
158
158
|
|
|
159
159
|
.gridtxt__header span {
|
|
160
160
|
font-weight: 700;
|
|
161
|
-
/* One step under the page title above it, so the two read as a hierarchy
|
|
162
|
-
rather than as two competing headings. */
|
|
163
161
|
font-size: var(--font-size-lg, 1.125rem);
|
|
164
162
|
line-height: 1.25;
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
library's density hooks. */
|
|
172
|
-
--profile-form-width: 56rem;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.gridtxt > ul {
|
|
176
|
-
width: 100%;
|
|
177
|
-
display: flex;
|
|
178
|
-
justify-content: flex-start;
|
|
179
|
-
flex-direction: row;
|
|
180
|
-
flex-wrap: wrap;
|
|
181
|
-
list-style: none;
|
|
182
|
-
box-sizing: border-box;
|
|
183
|
-
padding: 0.85rem;
|
|
184
|
-
margin: 0;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.gridtxt > ul li {
|
|
188
|
-
flex: 0 0 calc(50% - 10px);
|
|
189
|
-
padding: 0.85rem;
|
|
190
|
-
box-sizing: border-box;
|
|
191
|
-
border: 1px solid rgba(var(--primary-rgb), 0.095);
|
|
192
|
-
color: var(--paragraph-color);
|
|
193
|
-
background: rgba(var(--primary-rgb), 0.015);
|
|
194
|
-
margin: 5px;
|
|
195
|
-
border-radius: var(--br);
|
|
196
|
-
line-height: 1.45;
|
|
197
|
-
font-size: 1rem;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.gridtxt > ul .fw-grid-item {
|
|
201
|
-
flex: 0 0 calc(100% - 10px);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.gridtxt > ul .notecolor {
|
|
205
|
-
border: 1px solid var(--secondary-color);
|
|
206
|
-
}
|
|
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.
|
|
207
169
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
The 2FA tab is a document rather than a form and opts out with its own
|
|
214
|
-
`max-width: 100%`. */
|
|
215
|
-
max-width: var(--profile-form-width, 56rem);
|
|
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 {
|
|
216
175
|
box-sizing: border-box;
|
|
176
|
+
max-width: var(--profile-content-width, 68rem);
|
|
177
|
+
padding: 0 var(--spacing-lg, 1.5rem);
|
|
217
178
|
}
|
|
218
179
|
|
|
219
|
-
.
|
|
220
|
-
width: 100%;
|
|
221
|
-
box-sizing: border-box;
|
|
222
|
-
/* Was flex-wrap with 50%-wide children, so a field that needed the full
|
|
223
|
-
row had no way to say so and the row it started stayed half empty. */
|
|
180
|
+
.section {
|
|
224
181
|
display: grid;
|
|
225
|
-
grid-template-columns:
|
|
182
|
+
grid-template-columns:
|
|
183
|
+
minmax(0, var(--profile-aside-width, 18rem))
|
|
184
|
+
minmax(0, 1fr);
|
|
226
185
|
align-items: start;
|
|
227
|
-
gap: var(--spacing-
|
|
228
|
-
padding: var(--spacing-
|
|
186
|
+
gap: var(--spacing-md, 1rem) var(--spacing-xl, 2rem);
|
|
187
|
+
padding: var(--spacing-lg, 1.5rem) 0;
|
|
229
188
|
|
|
230
|
-
@media (max-width:
|
|
231
|
-
grid-template-columns: 1fr;
|
|
189
|
+
@media (max-width: 900px) {
|
|
190
|
+
grid-template-columns: minmax(0, 1fr);
|
|
191
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
232
192
|
}
|
|
233
193
|
}
|
|
234
194
|
|
|
235
|
-
.
|
|
236
|
-
|
|
237
|
-
padding: 0;
|
|
238
|
-
position: relative;
|
|
239
|
-
box-sizing: border-box;
|
|
195
|
+
.section + .section {
|
|
196
|
+
border-top: 1px solid var(--border-color, #e1e1e1);
|
|
240
197
|
}
|
|
241
198
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.formItemFull {
|
|
245
|
-
grid-column: 1 / -1;
|
|
199
|
+
.sectionAside {
|
|
200
|
+
min-width: 0;
|
|
246
201
|
}
|
|
247
202
|
|
|
248
|
-
.
|
|
249
|
-
|
|
250
|
-
|
|
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);
|
|
251
210
|
}
|
|
252
211
|
|
|
253
|
-
.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
transform-origin: top left;
|
|
260
|
-
cursor: text;
|
|
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);
|
|
261
218
|
}
|
|
262
219
|
|
|
263
|
-
.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
border-radius: var(--br);
|
|
270
|
-
z-index: 500;
|
|
220
|
+
.sectionFields {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
align-items: flex-start;
|
|
224
|
+
gap: var(--spacing-md, 1rem);
|
|
225
|
+
min-width: 0;
|
|
271
226
|
}
|
|
272
227
|
|
|
273
|
-
|
|
274
|
-
.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
/* Was transparent: on a white panel the fields had no edge at all and the
|
|
279
|
-
form read as text floating in space. The width stays 1px so the floating
|
|
280
|
-
label, which is positioned against this box, does not move. */
|
|
281
|
-
border: 1px solid var(--border-color, #e1e1e1);
|
|
282
|
-
padding: 1rem;
|
|
283
|
-
outline: none;
|
|
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 {
|
|
284
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. */
|
|
252
|
+
.formItem {
|
|
253
|
+
display: flex;
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
gap: 0.35rem;
|
|
256
|
+
min-width: 0;
|
|
285
257
|
box-sizing: border-box;
|
|
286
|
-
color: var(--paragraph-color);
|
|
287
|
-
transition: box-shadow 0.15s linear;
|
|
288
258
|
}
|
|
289
259
|
|
|
290
|
-
.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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;
|
|
304
|
+
}
|
|
305
|
+
|
|
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
|
+
}
|
|
312
|
+
|
|
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
|
+
}
|
|
295
322
|
}
|
|
296
323
|
|
|
297
|
-
.input
|
|
298
|
-
|
|
299
|
-
.
|
|
300
|
-
|
|
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;
|
|
301
330
|
}
|
|
302
331
|
|
|
303
332
|
/* --- Password strength ---------------------------------------------------
|
|
@@ -311,8 +340,10 @@ select:focus + .fi__span {
|
|
|
311
340
|
empty password box telling the user off before they had typed. */
|
|
312
341
|
.strengthMeter {
|
|
313
342
|
/* Sits directly under the input it measures, close enough to read as part
|
|
314
|
-
of the field rather than as a rule floating beneath it.
|
|
315
|
-
|
|
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;
|
|
316
347
|
|
|
317
348
|
/* The segments: 2px hairlines with 4px of white between them read as four
|
|
318
349
|
unrelated underlines. A little more weight and a rounded cap makes them
|
|
@@ -332,68 +363,100 @@ select:focus + .fi__span {
|
|
|
332
363
|
}
|
|
333
364
|
|
|
334
365
|
/* --- Signature ------------------------------------------------------------
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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. */
|
|
347
385
|
.signatureField {
|
|
386
|
+
width: 100%;
|
|
348
387
|
display: flex;
|
|
349
388
|
flex-direction: column;
|
|
350
389
|
gap: 0.4rem;
|
|
351
|
-
/* A signature is a fixed-size mark, not a field that
|
|
352
|
-
room. Left to fill the row it became the largest thing on the
|
|
353
|
-
|
|
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);
|
|
354
394
|
}
|
|
355
395
|
|
|
356
|
-
/* The
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
.
|
|
360
|
-
|
|
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);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.signatureCaption {
|
|
407
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
361
408
|
font-weight: var(--font-weight-semibold, 600);
|
|
362
|
-
|
|
409
|
+
letter-spacing: 0.06em;
|
|
410
|
+
text-transform: uppercase;
|
|
411
|
+
line-height: 1.3;
|
|
412
|
+
color: var(--muted-color, #6b7285);
|
|
363
413
|
}
|
|
364
414
|
|
|
365
415
|
.signaturePadWrapper {
|
|
366
416
|
width: 100%;
|
|
367
417
|
position: relative;
|
|
368
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);
|
|
369
424
|
|
|
370
425
|
canvas {
|
|
426
|
+
display: block;
|
|
427
|
+
width: 100%;
|
|
371
428
|
max-width: 100%;
|
|
372
429
|
height: auto !important;
|
|
373
|
-
display: block;
|
|
374
430
|
}
|
|
375
431
|
}
|
|
376
432
|
|
|
377
|
-
.
|
|
378
|
-
|
|
379
|
-
|
|
433
|
+
/* Nothing here changes the canvas's box model — see the note above. */
|
|
434
|
+
.sigcanvas {
|
|
435
|
+
cursor: crosshair;
|
|
436
|
+
touch-action: none;
|
|
380
437
|
}
|
|
381
438
|
|
|
382
|
-
/*
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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. */
|
|
388
451
|
.polActions {
|
|
389
452
|
display: flex;
|
|
390
453
|
align-items: center;
|
|
391
454
|
justify-content: flex-end;
|
|
392
455
|
gap: var(--spacing-sm, 0.5rem);
|
|
393
456
|
width: 100%;
|
|
394
|
-
max-width: var(--profile-
|
|
457
|
+
max-width: var(--profile-content-width, 68rem);
|
|
395
458
|
box-sizing: border-box;
|
|
396
|
-
padding: var(--spacing-md, 1rem);
|
|
459
|
+
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
|
|
397
460
|
border-top: 1px solid var(--border-color, #e1e1e1);
|
|
398
461
|
}
|
|
399
462
|
|
|
@@ -401,12 +464,6 @@ select:focus + .fi__span {
|
|
|
401
464
|
margin: 0;
|
|
402
465
|
}
|
|
403
466
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
.inputError {
|
|
407
|
-
border-color: red !important;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
467
|
.loading {
|
|
411
468
|
position: relative;
|
|
412
469
|
cursor: wait;
|
|
@@ -432,64 +489,67 @@ select:focus + .fi__span {
|
|
|
432
489
|
}
|
|
433
490
|
}
|
|
434
491
|
|
|
435
|
-
/* Two-Factor Authentication
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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 {
|
|
439
497
|
display: flex;
|
|
440
498
|
flex-direction: column;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
.twoFactorStatus {
|
|
444
|
-
margin-bottom: 1.5rem;
|
|
499
|
+
align-items: flex-start;
|
|
500
|
+
gap: var(--spacing-md, 1rem);
|
|
445
501
|
|
|
446
|
-
|
|
447
|
-
margin
|
|
448
|
-
font-size: 1rem;
|
|
502
|
+
h4 {
|
|
503
|
+
margin: 0;
|
|
449
504
|
display: flex;
|
|
450
505
|
align-items: center;
|
|
451
506
|
gap: 0.5rem;
|
|
452
|
-
font-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
line-height: 1.4;
|
|
458
|
-
color: var(--paragraph-color);
|
|
459
|
-
font-size: 0.9rem;
|
|
460
|
-
/* This tab is deliberately full width — it holds a QR code and a grid
|
|
461
|
-
of recovery codes — but the prose explaining 2FA ran the whole way
|
|
462
|
-
across a 1600px panel. Only the paragraphs are capped. */
|
|
463
|
-
max-width: 68ch;
|
|
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);
|
|
464
512
|
}
|
|
465
513
|
}
|
|
466
514
|
|
|
467
515
|
.statusEnabled {
|
|
468
516
|
color: var(--success-color, #2ecc71);
|
|
469
|
-
font-weight:
|
|
470
|
-
padding: 0.
|
|
517
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
518
|
+
padding: 0.15rem 0.45rem;
|
|
471
519
|
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
472
|
-
border-radius: var(--radius-sm,
|
|
473
|
-
font-size: 0.
|
|
520
|
+
border-radius: var(--radius-sm, 6px);
|
|
521
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
522
|
+
letter-spacing: 0.04em;
|
|
474
523
|
}
|
|
475
524
|
|
|
476
525
|
.statusDisabled {
|
|
477
526
|
color: var(--danger-color, #e74c3c);
|
|
478
|
-
font-weight:
|
|
479
|
-
padding: 0.
|
|
527
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
528
|
+
padding: 0.15rem 0.45rem;
|
|
480
529
|
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
481
|
-
border-radius: var(--radius-sm,
|
|
482
|
-
font-size: 0.
|
|
530
|
+
border-radius: var(--radius-sm, 6px);
|
|
531
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
532
|
+
letter-spacing: 0.04em;
|
|
483
533
|
}
|
|
484
534
|
|
|
485
535
|
.confirmedMessage {
|
|
486
|
-
margin:
|
|
536
|
+
margin: 0;
|
|
487
537
|
padding: 0.75rem;
|
|
488
538
|
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
489
539
|
border-left: 3px solid var(--success-color, #2ecc71);
|
|
490
|
-
border-radius: var(--
|
|
491
|
-
color: #333;
|
|
492
|
-
font-size: 0.
|
|
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;
|
|
544
|
+
}
|
|
545
|
+
|
|
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);
|
|
493
553
|
}
|
|
494
554
|
|
|
495
555
|
/* The three variants each carried their own font size and padding, so a row of
|
|
@@ -520,75 +580,67 @@ select:focus + .fi__span {
|
|
|
520
580
|
}
|
|
521
581
|
}
|
|
522
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. */
|
|
523
586
|
.twoFactorSetup {
|
|
524
|
-
margin-top: 1.5rem;
|
|
525
|
-
padding: 1.5rem;
|
|
526
|
-
background-color: var(--paper, #fff);
|
|
527
|
-
border-radius: var(--br);
|
|
528
|
-
border: 1px solid var(--border-color, #e0e0e0);
|
|
529
|
-
box-shadow: var(--shadow-sm, 0 3px 8px rgba(0, 0, 0, 0.05));
|
|
530
587
|
width: 100%;
|
|
531
|
-
|
|
532
|
-
h3 {
|
|
533
|
-
margin-bottom: 1.25rem;
|
|
534
|
-
font-size: 1rem;
|
|
535
|
-
font-weight: 500;
|
|
536
|
-
color: var(--primary-color);
|
|
537
|
-
padding-bottom: 0.75rem;
|
|
538
|
-
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
|
539
|
-
}
|
|
588
|
+
min-width: 0;
|
|
540
589
|
}
|
|
541
590
|
|
|
542
591
|
.setupSteps {
|
|
543
|
-
margin
|
|
544
|
-
|
|
545
|
-
font-size: 0.
|
|
592
|
+
margin: 0;
|
|
593
|
+
padding: 0;
|
|
594
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
546
595
|
counter-reset: step-counter;
|
|
547
596
|
list-style-type: none;
|
|
548
597
|
|
|
549
598
|
li {
|
|
550
|
-
margin-bottom: 1.5rem;
|
|
551
|
-
padding-left: 2.
|
|
599
|
+
margin-bottom: var(--spacing-lg, 1.5rem);
|
|
600
|
+
padding-left: 2.25rem;
|
|
552
601
|
position: relative;
|
|
553
602
|
|
|
603
|
+
&:last-child {
|
|
604
|
+
margin-bottom: 0;
|
|
605
|
+
}
|
|
606
|
+
|
|
554
607
|
&:before {
|
|
555
608
|
content: counter(step-counter);
|
|
556
609
|
counter-increment: step-counter;
|
|
557
610
|
position: absolute;
|
|
558
611
|
left: 0;
|
|
559
612
|
top: 0;
|
|
560
|
-
width: 1.
|
|
561
|
-
height: 1.
|
|
613
|
+
width: 1.5rem;
|
|
614
|
+
height: 1.5rem;
|
|
562
615
|
background-color: var(--primary-color);
|
|
563
|
-
color:
|
|
616
|
+
color: #fff;
|
|
564
617
|
border-radius: 50%;
|
|
565
618
|
display: flex;
|
|
566
619
|
align-items: center;
|
|
567
620
|
justify-content: center;
|
|
568
|
-
font-size: 0.
|
|
569
|
-
font-weight:
|
|
621
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
622
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
570
623
|
}
|
|
571
624
|
|
|
572
625
|
p {
|
|
573
|
-
margin-bottom: 0.
|
|
574
|
-
font-weight: 500;
|
|
575
|
-
font-size: 0.
|
|
576
|
-
|
|
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);
|
|
577
631
|
}
|
|
578
632
|
|
|
579
633
|
ul {
|
|
580
|
-
margin
|
|
581
|
-
margin-bottom: 1rem;
|
|
634
|
+
margin: 0 0 var(--spacing-md, 1rem);
|
|
582
635
|
list-style-type: none;
|
|
583
|
-
background-color
|
|
584
|
-
padding: 0.
|
|
585
|
-
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);
|
|
586
639
|
border-left: 3px solid var(--primary-color);
|
|
587
640
|
|
|
588
641
|
li {
|
|
589
|
-
margin-bottom: 0.
|
|
590
|
-
font-size: 0.
|
|
591
|
-
padding-left: 0;
|
|
642
|
+
margin-bottom: 0.4rem;
|
|
643
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
592
644
|
position: relative;
|
|
593
645
|
padding-left: 1rem;
|
|
594
646
|
|
|
@@ -613,7 +665,7 @@ select:focus + .fi__span {
|
|
|
613
665
|
a {
|
|
614
666
|
color: var(--primary-color);
|
|
615
667
|
text-decoration: none;
|
|
616
|
-
font-weight: 500;
|
|
668
|
+
font-weight: var(--font-weight-medium, 500);
|
|
617
669
|
|
|
618
670
|
&:hover {
|
|
619
671
|
text-decoration: underline;
|
|
@@ -626,17 +678,20 @@ select:focus + .fi__span {
|
|
|
626
678
|
|
|
627
679
|
.qrCodeContainer {
|
|
628
680
|
display: flex;
|
|
629
|
-
justify-content:
|
|
630
|
-
margin: 0
|
|
631
|
-
padding: 1rem;
|
|
632
|
-
background
|
|
633
|
-
border-radius: var(--
|
|
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);
|
|
634
686
|
border: 1px solid var(--border-color, #e0e0e0);
|
|
635
|
-
width
|
|
636
|
-
|
|
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
|
|
637
692
|
clickable — it is a QR code to point a phone at — so the affordance was
|
|
638
693
|
promising an interaction that does not exist. */
|
|
639
|
-
box-shadow:
|
|
694
|
+
box-shadow: none;
|
|
640
695
|
}
|
|
641
696
|
|
|
642
697
|
.qrCodeSvg {
|
|
@@ -656,9 +711,9 @@ select:focus + .fi__span {
|
|
|
656
711
|
display: flex;
|
|
657
712
|
align-items: center;
|
|
658
713
|
justify-content: center;
|
|
659
|
-
background-color: #f5f5f5;
|
|
660
|
-
color: #666;
|
|
661
|
-
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);
|
|
662
717
|
border-radius: 4px;
|
|
663
718
|
animation: pulse 1.5s infinite ease-in-out;
|
|
664
719
|
}
|
|
@@ -678,146 +733,131 @@ select:focus + .fi__span {
|
|
|
678
733
|
.secretKeyContainer {
|
|
679
734
|
display: flex;
|
|
680
735
|
align-items: center;
|
|
681
|
-
margin: 0
|
|
682
|
-
gap: 0.5rem;
|
|
683
|
-
background-color
|
|
684
|
-
padding: 0.
|
|
685
|
-
border-radius: var(--
|
|
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);
|
|
686
741
|
border: 1px solid var(--border-color, #e0e0e0);
|
|
687
742
|
width: 100%;
|
|
743
|
+
box-sizing: border-box;
|
|
688
744
|
}
|
|
689
745
|
|
|
690
746
|
.secretKey {
|
|
691
|
-
padding: 0.
|
|
692
|
-
background
|
|
693
|
-
border-radius: var(--
|
|
747
|
+
padding: 0.4rem 0.65rem;
|
|
748
|
+
background: var(--paper, #fff);
|
|
749
|
+
border-radius: var(--radius-sm, 6px);
|
|
694
750
|
font-family: monospace;
|
|
695
|
-
font-size: 0.
|
|
751
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
696
752
|
letter-spacing: 1px;
|
|
697
|
-
color: #333;
|
|
753
|
+
color: var(--text-color, #333);
|
|
698
754
|
user-select: all;
|
|
699
|
-
border: 1px solid #ddd;
|
|
755
|
+
border: 1px solid var(--border-color, #ddd);
|
|
700
756
|
flex-grow: 1;
|
|
701
|
-
|
|
757
|
+
min-width: 0;
|
|
758
|
+
overflow-wrap: anywhere;
|
|
702
759
|
}
|
|
703
760
|
|
|
704
761
|
.copyButton {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
color: white;
|
|
708
|
-
border: none;
|
|
709
|
-
border-radius: var(--br);
|
|
710
|
-
cursor: pointer;
|
|
711
|
-
font-size: 0.8rem;
|
|
712
|
-
transition: all 0.2s ease;
|
|
713
|
-
display: flex;
|
|
714
|
-
align-items: center;
|
|
715
|
-
|
|
716
|
-
&:hover {
|
|
717
|
-
filter: brightness(90%);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
&:active {
|
|
721
|
-
transform: translateY(1px);
|
|
722
|
-
}
|
|
762
|
+
@include button-secondary;
|
|
763
|
+
@include button-small;
|
|
723
764
|
}
|
|
724
765
|
|
|
725
766
|
.verificationCodeContainer {
|
|
726
767
|
display: flex;
|
|
727
768
|
align-items: center;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
border: 1px solid var(--border-color, #e0e0e0);
|
|
734
|
-
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;
|
|
735
774
|
}
|
|
736
775
|
|
|
776
|
+
/* Same fight with `global.css` as `.input` — see the note there. */
|
|
737
777
|
.verificationCodeInput {
|
|
738
|
-
|
|
739
|
-
|
|
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;
|
|
740
788
|
letter-spacing: 4px;
|
|
741
|
-
width: 140px;
|
|
742
789
|
text-align: center;
|
|
743
|
-
border: 1px solid #ddd;
|
|
744
|
-
border-radius: var(--br);
|
|
745
790
|
font-family: monospace;
|
|
746
|
-
font-weight: 600;
|
|
747
|
-
color: #333;
|
|
748
|
-
background-color: white;
|
|
749
|
-
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
791
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
750
792
|
|
|
751
793
|
&:focus {
|
|
752
|
-
border-color: var(--primary-color);
|
|
753
794
|
outline: none;
|
|
754
|
-
|
|
795
|
+
border-color: var(--primary-color) !important;
|
|
796
|
+
box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32)) !important;
|
|
755
797
|
}
|
|
756
798
|
|
|
757
799
|
&::placeholder {
|
|
758
|
-
color: #bbb;
|
|
800
|
+
color: var(--muted-color, #bbb);
|
|
759
801
|
letter-spacing: 2px;
|
|
760
802
|
}
|
|
761
803
|
}
|
|
762
804
|
|
|
763
805
|
.recoveryCodes {
|
|
764
|
-
margin: 0
|
|
765
|
-
padding: 1rem;
|
|
766
|
-
background-color
|
|
767
|
-
border-radius: var(--
|
|
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);
|
|
768
810
|
border: 1px solid var(--border-color, #e0e0e0);
|
|
769
811
|
width: 100%;
|
|
812
|
+
box-sizing: border-box;
|
|
770
813
|
}
|
|
771
814
|
|
|
772
815
|
.recoveryCodesContainer {
|
|
773
816
|
display: grid;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
grid-template-columns: 1fr;
|
|
780
|
-
}
|
|
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);
|
|
781
822
|
}
|
|
782
823
|
|
|
783
824
|
.recoveryCode {
|
|
784
|
-
padding: 0.
|
|
785
|
-
background
|
|
786
|
-
border-radius: var(--
|
|
787
|
-
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);
|
|
788
829
|
font-family: monospace;
|
|
789
|
-
font-size: 0.
|
|
830
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
790
831
|
letter-spacing: 1px;
|
|
791
|
-
color: #333;
|
|
832
|
+
color: var(--text-color, #333);
|
|
792
833
|
user-select: all;
|
|
793
|
-
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
794
834
|
text-align: center;
|
|
795
835
|
}
|
|
796
836
|
|
|
797
837
|
.noRecoveryCodes {
|
|
798
|
-
padding: 1rem;
|
|
838
|
+
padding: var(--spacing-md, 1rem);
|
|
799
839
|
text-align: center;
|
|
800
|
-
color: #666;
|
|
840
|
+
color: var(--muted-color, #666);
|
|
801
841
|
font-style: italic;
|
|
802
|
-
font-size: 0.
|
|
842
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
803
843
|
}
|
|
804
844
|
|
|
805
845
|
.recoveryCodesWarning {
|
|
806
|
-
margin
|
|
846
|
+
margin: 0;
|
|
807
847
|
padding: 0.75rem;
|
|
808
848
|
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
809
849
|
border-left: 3px solid var(--danger-color, #e74c3c);
|
|
810
|
-
border-radius: var(--
|
|
811
|
-
color: #333;
|
|
812
|
-
font-size: 0.
|
|
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;
|
|
813
854
|
}
|
|
814
855
|
|
|
815
856
|
.setupActions {
|
|
816
857
|
display: flex;
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
border-top: 1px solid var(--border-color, #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);
|
|
821
861
|
}
|
|
822
862
|
|
|
823
863
|
.crmtitle {
|