@asksable/site-connector 0.1.6 → 0.3.0
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/README.md +89 -1
- package/dist/bones/booking-widget.bones.json +495 -0
- package/dist/bones/registry.d.ts +2 -0
- package/dist/bones/registry.d.ts.map +1 -0
- package/dist/bones/registry.js +10 -0
- package/dist/bones/registry.js.map +1 -0
- package/dist/booking-widget.d.ts +76 -1
- package/dist/booking-widget.d.ts.map +1 -1
- package/dist/booking-widget.js +1362 -199
- package/dist/booking-widget.js.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +35 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +9 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +20 -1
- package/dist/provider.js.map +1 -1
- package/dist/styles.css +2173 -191
- package/dist/translations.d.ts +282 -0
- package/dist/translations.d.ts.map +1 -0
- package/dist/translations.js +348 -0
- package/dist/translations.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
package/dist/styles.css
CHANGED
|
@@ -1,20 +1,54 @@
|
|
|
1
|
+
/* Mobile-web hygiene scoped to the widget. Each rule is a known
|
|
2
|
+
iOS Safari / Android Chrome gotcha called out by the alvsr-mobile-
|
|
3
|
+
ui-review audit. -webkit-text-size-adjust stops iOS Safari from
|
|
4
|
+
auto-enlarging the widget's text in landscape; -webkit-tap-
|
|
5
|
+
highlight-color removes the default blue tap flash; touch-action:
|
|
6
|
+
manipulation kills the legacy 300ms double-tap delay on iOS so
|
|
7
|
+
buttons feel responsive. */
|
|
8
|
+
.bw,
|
|
9
|
+
.bw * {
|
|
10
|
+
-webkit-text-size-adjust: 100%;
|
|
11
|
+
-webkit-tap-highlight-color: transparent;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.bw button,
|
|
15
|
+
.bw [role='button'],
|
|
16
|
+
.bw a {
|
|
17
|
+
touch-action: manipulation;
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
.bw {
|
|
21
|
+
/* Theme tokens — overridable per host site (color + font). */
|
|
2
22
|
--bw-font: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3
23
|
--bw-bg: #ffffff;
|
|
4
24
|
--bw-text: #1a1a1a;
|
|
5
25
|
--bw-text-secondary: #6b6b6b;
|
|
6
|
-
|
|
26
|
+
/* Bumped from #999 (2.85:1, fails WCAG AA) to #737373 (~4.74:1,
|
|
27
|
+
passes AA for normal text). Still distinct from --bw-text-secondary
|
|
28
|
+
(#6b6b6b, 5.74:1) so the visual hierarchy holds. */
|
|
29
|
+
--bw-text-muted: #737373;
|
|
7
30
|
--bw-border: #e8e8e8;
|
|
8
31
|
--bw-border-light: #f0f0f0;
|
|
9
|
-
--bw-radius: 8px;
|
|
10
|
-
--bw-radius-lg: 12px;
|
|
11
32
|
--bw-primary: #1a1a1a;
|
|
12
33
|
--bw-primary-text: #ffffff;
|
|
13
34
|
--bw-hover: #f8f8f8;
|
|
14
35
|
--bw-error-bg: #fef2f2;
|
|
15
36
|
--bw-error-text: #b91c1c;
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
/* Structural tokens — Sable design rhythm; tenants generally
|
|
38
|
+
don't override these. Mirrors the radius scale documented in
|
|
39
|
+
productdesign.md (cards 12/16, inputs 22, buttons 28). */
|
|
40
|
+
--bw-radius-xs: 4px;
|
|
41
|
+
--bw-radius-sm: 8px;
|
|
42
|
+
--bw-radius-md: 12px;
|
|
43
|
+
--bw-radius: 12px;
|
|
44
|
+
--bw-radius-lg: 16px;
|
|
45
|
+
--bw-radius-xl: 22px;
|
|
46
|
+
--bw-radius-pill: 9999px;
|
|
47
|
+
--bw-shadow: 0 1px 2px rgba(15, 15, 15, 0.04), 0 8px 24px -12px rgba(15, 15, 15, 0.08);
|
|
48
|
+
--bw-shadow-badge: 0 1px 4px rgba(15, 15, 15, 0.12);
|
|
49
|
+
--bw-shadow-lift: 0 1px 2px rgba(15, 15, 15, 0.06), 0 12px 32px -16px rgba(15, 15, 15, 0.12);
|
|
50
|
+
--bw-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
|
51
|
+
--bw-duration: 220ms;
|
|
18
52
|
position: relative;
|
|
19
53
|
display: flex;
|
|
20
54
|
flex-direction: column;
|
|
@@ -22,9 +56,20 @@
|
|
|
22
56
|
background: var(--bw-bg);
|
|
23
57
|
color: var(--bw-text);
|
|
24
58
|
font-family: var(--bw-font);
|
|
59
|
+
font-feature-settings: "ss01", "cv11";
|
|
60
|
+
-webkit-font-smoothing: antialiased;
|
|
61
|
+
-moz-osx-font-smoothing: grayscale;
|
|
62
|
+
text-wrap: pretty;
|
|
25
63
|
overflow: hidden;
|
|
26
64
|
}
|
|
27
65
|
|
|
66
|
+
.bw h1,
|
|
67
|
+
.bw h2,
|
|
68
|
+
.bw h3,
|
|
69
|
+
.bw h4 {
|
|
70
|
+
text-wrap: balance;
|
|
71
|
+
}
|
|
72
|
+
|
|
28
73
|
.bw,
|
|
29
74
|
.bw * {
|
|
30
75
|
box-sizing: border-box;
|
|
@@ -51,67 +96,12 @@
|
|
|
51
96
|
animation: bw-shimmer 1.5s ease infinite;
|
|
52
97
|
}
|
|
53
98
|
|
|
54
|
-
.bw-skel--title {
|
|
55
|
-
width: min(340px, 70%);
|
|
56
|
-
height: 32px;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.bw-skel--text {
|
|
60
|
-
width: 48%;
|
|
61
|
-
height: 14px;
|
|
62
|
-
margin-top: 14px;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.bw-skel--text-lg {
|
|
66
|
-
width: min(520px, 85%);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.bw-skel--card {
|
|
70
|
-
width: 100%;
|
|
71
|
-
height: 56px;
|
|
72
|
-
margin-bottom: 10px;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.bw-skel--calendar {
|
|
76
|
-
width: 100%;
|
|
77
|
-
height: 300px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.bw-skel--input {
|
|
81
|
-
width: 100%;
|
|
82
|
-
height: 40px;
|
|
83
|
-
margin-bottom: 12px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.bw-skel--textarea {
|
|
87
|
-
width: 100%;
|
|
88
|
-
height: 92px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
99
|
.bw-skel--slot {
|
|
92
|
-
height:
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
flex-direction: column;
|
|
98
|
-
gap: 0;
|
|
99
|
-
margin-top: 16px;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.bw-skel-slots {
|
|
103
|
-
margin-top: 8px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.bw-skel-slots-grid {
|
|
107
|
-
display: grid;
|
|
108
|
-
grid-template-columns: repeat(4, 1fr);
|
|
109
|
-
gap: 10px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.bw-skel-mobile {
|
|
113
|
-
display: none;
|
|
114
|
-
padding: 0 24px 24px;
|
|
100
|
+
/* Default mobile pill height: matches .bw-slot { padding: 12px 0;
|
|
101
|
+
font-size: 13px } ≈ 44px. Desktop overrides this below to match
|
|
102
|
+
the taller .bw-slots-desktop .bw-slot { padding: 14px 16px } pill. */
|
|
103
|
+
height: 44px;
|
|
104
|
+
border-radius: var(--bw-radius);
|
|
115
105
|
}
|
|
116
106
|
|
|
117
107
|
.bw-loading,
|
|
@@ -174,7 +164,11 @@
|
|
|
174
164
|
|
|
175
165
|
.bw-body {
|
|
176
166
|
display: grid;
|
|
177
|
-
|
|
167
|
+
/* Left col widened from 22% to 28% so the service rows have room
|
|
168
|
+
to render the duration + description on their secondary line
|
|
169
|
+
without crunching. The middle (calendar) absorbs the difference;
|
|
170
|
+
the right (slots/details) stays at 22%. */
|
|
171
|
+
grid-template-columns: 28% 1fr 22%;
|
|
178
172
|
gap: 32px;
|
|
179
173
|
padding: 32px 48px 48px;
|
|
180
174
|
min-height: 0;
|
|
@@ -190,6 +184,86 @@
|
|
|
190
184
|
min-height: 0;
|
|
191
185
|
}
|
|
192
186
|
|
|
187
|
+
/* Mobile-only review column. Desktop uses bw-details-view for review,
|
|
188
|
+
so this col stays hidden at all desktop widths and only the mobile
|
|
189
|
+
media query opts it back in on step 4. */
|
|
190
|
+
.bw-col--review {
|
|
191
|
+
display: none;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Review variant runs full-bleed inside its col: no card chrome (no
|
|
195
|
+
border, no rounded background), generous type. Sized for arm's-
|
|
196
|
+
length reading on phone — body 15px floor, total 17px, with a
|
|
197
|
+
thin hairline separating each group. */
|
|
198
|
+
.bw-summary--review {
|
|
199
|
+
width: 100%;
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
gap: 20px;
|
|
203
|
+
padding: 0;
|
|
204
|
+
border: 0;
|
|
205
|
+
border-radius: 0;
|
|
206
|
+
background: transparent;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.bw-summary--review .bw-summary-title {
|
|
210
|
+
margin-bottom: 4px;
|
|
211
|
+
font-size: 17px;
|
|
212
|
+
font-weight: 600;
|
|
213
|
+
letter-spacing: -0.01em;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.bw-summary--review .bw-summary-rows {
|
|
217
|
+
gap: 12px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.bw-summary--review .bw-summary-row {
|
|
221
|
+
font-size: 15px;
|
|
222
|
+
line-height: 1.5;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.bw-summary--review .bw-summary-total {
|
|
226
|
+
font-size: 17px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.bw-summary-group {
|
|
230
|
+
display: flex;
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
gap: 12px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.bw-summary-subhead {
|
|
236
|
+
display: block;
|
|
237
|
+
font-size: 13px;
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
letter-spacing: -0.005em;
|
|
240
|
+
color: var(--bw-text-muted);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.bw-summary-row--stack {
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
align-items: flex-start;
|
|
246
|
+
gap: 4px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.bw-summary-row--stack .bw-summary-val {
|
|
250
|
+
text-align: left;
|
|
251
|
+
white-space: normal;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.bw-summary-notes {
|
|
255
|
+
margin: 0;
|
|
256
|
+
font-size: 15px;
|
|
257
|
+
line-height: 1.55;
|
|
258
|
+
color: var(--bw-text);
|
|
259
|
+
white-space: pre-wrap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.bw-summary-rows--total {
|
|
263
|
+
padding-top: 16px;
|
|
264
|
+
border-top: 1px solid var(--bw-border);
|
|
265
|
+
}
|
|
266
|
+
|
|
193
267
|
.bw-step-1 {
|
|
194
268
|
display: flex;
|
|
195
269
|
flex-direction: column;
|
|
@@ -203,8 +277,8 @@
|
|
|
203
277
|
|
|
204
278
|
.bw-label {
|
|
205
279
|
display: block;
|
|
206
|
-
margin-bottom:
|
|
207
|
-
font-size:
|
|
280
|
+
margin-bottom: 14px;
|
|
281
|
+
font-size: 14px;
|
|
208
282
|
font-weight: 500;
|
|
209
283
|
color: var(--bw-text);
|
|
210
284
|
}
|
|
@@ -215,6 +289,35 @@
|
|
|
215
289
|
background: var(--bw-border);
|
|
216
290
|
}
|
|
217
291
|
|
|
292
|
+
/* Service groups — categories. We use generous vertical rhythm
|
|
293
|
+
(28px gap) and a small caption-style heading instead of a hard
|
|
294
|
+
divider line, so the category break reads through whitespace +
|
|
295
|
+
typographic shift, not via a blend-in 1px border. */
|
|
296
|
+
.bw-svc-group {
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
gap: 2px;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.bw-svc-group + .bw-svc-group {
|
|
303
|
+
margin-top: 28px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Category heading: smaller than the section labels (14/500) but
|
|
307
|
+
heavier weight so the section break still reads cleanly. The
|
|
308
|
+
wider top gap above (28px between groups) carries most of the
|
|
309
|
+
visual separation. */
|
|
310
|
+
.bw-svc-category {
|
|
311
|
+
margin: 0 0 12px;
|
|
312
|
+
padding: 0;
|
|
313
|
+
font-size: 13px;
|
|
314
|
+
font-weight: 600;
|
|
315
|
+
line-height: 1.4;
|
|
316
|
+
letter-spacing: 0.01em;
|
|
317
|
+
color: var(--bw-text-secondary);
|
|
318
|
+
text-transform: none;
|
|
319
|
+
}
|
|
320
|
+
|
|
218
321
|
.bw-staff-dropdown {
|
|
219
322
|
position: relative;
|
|
220
323
|
margin-bottom: 10px;
|
|
@@ -223,20 +326,30 @@
|
|
|
223
326
|
.bw-staff-trigger {
|
|
224
327
|
display: flex;
|
|
225
328
|
align-items: center;
|
|
226
|
-
gap:
|
|
329
|
+
gap: 12px;
|
|
227
330
|
width: 100%;
|
|
228
|
-
padding:
|
|
229
|
-
border:
|
|
331
|
+
padding: 10px 14px;
|
|
332
|
+
border: 1px solid var(--bw-border);
|
|
230
333
|
border-radius: var(--bw-radius);
|
|
231
334
|
background: transparent;
|
|
232
335
|
cursor: pointer;
|
|
233
336
|
text-align: left;
|
|
234
|
-
transition: border-color
|
|
337
|
+
transition: border-color var(--bw-duration) var(--bw-ease),
|
|
338
|
+
box-shadow var(--bw-duration) var(--bw-ease),
|
|
339
|
+
background var(--bw-duration) var(--bw-ease);
|
|
235
340
|
}
|
|
236
341
|
|
|
237
|
-
.bw-staff-trigger:hover
|
|
238
|
-
|
|
342
|
+
.bw-staff-trigger:hover {
|
|
343
|
+
border-color: var(--bw-text-secondary);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.bw-staff-trigger:focus-visible {
|
|
239
347
|
border-color: var(--bw-primary);
|
|
348
|
+
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.bw-staff-trigger[disabled] {
|
|
352
|
+
cursor: default;
|
|
240
353
|
}
|
|
241
354
|
|
|
242
355
|
.bw-staff-trigger-content,
|
|
@@ -252,6 +365,7 @@
|
|
|
252
365
|
}
|
|
253
366
|
|
|
254
367
|
.bw-staff-avatar {
|
|
368
|
+
position: relative;
|
|
255
369
|
display: flex;
|
|
256
370
|
flex-shrink: 0;
|
|
257
371
|
align-items: center;
|
|
@@ -261,6 +375,15 @@
|
|
|
261
375
|
border-radius: 999px;
|
|
262
376
|
background: var(--bw-border-light);
|
|
263
377
|
color: var(--bw-text-secondary);
|
|
378
|
+
overflow: hidden;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.bw-staff-avatar-img {
|
|
382
|
+
position: absolute;
|
|
383
|
+
inset: 0;
|
|
384
|
+
width: 100%;
|
|
385
|
+
height: 100%;
|
|
386
|
+
object-fit: cover;
|
|
264
387
|
}
|
|
265
388
|
|
|
266
389
|
.bw-staff-avatar svg,
|
|
@@ -299,6 +422,9 @@
|
|
|
299
422
|
font-weight: 500;
|
|
300
423
|
line-height: 1.3;
|
|
301
424
|
color: var(--bw-text);
|
|
425
|
+
white-space: nowrap;
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
text-overflow: ellipsis;
|
|
302
428
|
}
|
|
303
429
|
|
|
304
430
|
.bw-staff-trigger-desc,
|
|
@@ -333,8 +459,9 @@
|
|
|
333
459
|
z-index: 9;
|
|
334
460
|
display: flex;
|
|
335
461
|
flex-direction: column;
|
|
462
|
+
gap: 2px;
|
|
336
463
|
width: 100%;
|
|
337
|
-
padding:
|
|
464
|
+
padding: 6px;
|
|
338
465
|
border: 1px solid var(--bw-border);
|
|
339
466
|
border-radius: var(--bw-radius);
|
|
340
467
|
background: var(--bw-bg);
|
|
@@ -353,10 +480,11 @@
|
|
|
353
480
|
width: 100%;
|
|
354
481
|
padding: 10px 12px;
|
|
355
482
|
border: 0;
|
|
356
|
-
border-radius:
|
|
483
|
+
border-radius: var(--bw-radius-sm);
|
|
357
484
|
background: transparent;
|
|
358
485
|
cursor: pointer;
|
|
359
486
|
text-align: left;
|
|
487
|
+
transition: background var(--bw-duration) var(--bw-ease);
|
|
360
488
|
}
|
|
361
489
|
|
|
362
490
|
.bw-staff-option:hover,
|
|
@@ -364,52 +492,147 @@
|
|
|
364
492
|
background: var(--bw-hover);
|
|
365
493
|
}
|
|
366
494
|
|
|
495
|
+
/* Provider has no slots on the selected date. Visually muted, not
|
|
496
|
+
clickable, with an "Unavailable" subtitle under the name (rendered
|
|
497
|
+
inline via .bw-staff-option-desc). */
|
|
498
|
+
.bw-staff-option.is-disabled {
|
|
499
|
+
cursor: not-allowed;
|
|
500
|
+
opacity: 0.5;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.bw-staff-option.is-disabled:hover {
|
|
504
|
+
background: transparent;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.bw-staff-option.is-disabled .bw-staff-option-desc {
|
|
508
|
+
color: var(--bw-muted);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* When the picker is in expanded mode (full list visible), let it
|
|
512
|
+
stretch to fill the column so the scroll list extends down to the
|
|
513
|
+
bottom of the left column instead of capping at an arbitrary
|
|
514
|
+
height. Detected via :has() — the .bw-svc-scroll only renders in
|
|
515
|
+
expanded mode; in collapsed mode the picker contains only the
|
|
516
|
+
selected card, which sizes to its content (so the provider section
|
|
517
|
+
below sits flush, not pushed to the bottom). */
|
|
518
|
+
.bw-service-picker:has(.bw-svc-scroll) {
|
|
519
|
+
display: flex;
|
|
520
|
+
flex-direction: column;
|
|
521
|
+
flex: 1;
|
|
522
|
+
min-height: 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
367
525
|
.bw-svc-scroll {
|
|
368
|
-
display:
|
|
369
|
-
|
|
526
|
+
display: flex;
|
|
527
|
+
flex-direction: column;
|
|
528
|
+
flex: 1;
|
|
529
|
+
min-height: 0;
|
|
370
530
|
}
|
|
371
531
|
|
|
372
532
|
.bw-svc-scroll-wrap {
|
|
533
|
+
position: relative;
|
|
534
|
+
flex: 1;
|
|
373
535
|
min-height: 0;
|
|
374
|
-
max-height: 320px;
|
|
375
536
|
overflow-y: auto;
|
|
537
|
+
/* Soft fade at the top + bottom so the scroll edge looks
|
|
538
|
+
intentional instead of cutting service rows in half. Matches
|
|
539
|
+
the editorial "warm paper, not cold glass" feel — depth from
|
|
540
|
+
softness, not a hard line. */
|
|
541
|
+
-webkit-mask-image: linear-gradient(
|
|
542
|
+
to bottom,
|
|
543
|
+
transparent 0,
|
|
544
|
+
#000 18px,
|
|
545
|
+
#000 calc(100% - 18px),
|
|
546
|
+
transparent 100%
|
|
547
|
+
);
|
|
548
|
+
mask-image: linear-gradient(
|
|
549
|
+
to bottom,
|
|
550
|
+
transparent 0,
|
|
551
|
+
#000 18px,
|
|
552
|
+
#000 calc(100% - 18px),
|
|
553
|
+
transparent 100%
|
|
554
|
+
);
|
|
555
|
+
scroll-padding-block: 16px;
|
|
556
|
+
/* Native scrollbar tightened so it doesn't dominate the
|
|
557
|
+
softened edge. Webkit-only; Firefox uses default. */
|
|
558
|
+
scrollbar-width: thin;
|
|
559
|
+
scrollbar-color: var(--bw-border) transparent;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.bw-svc-scroll-wrap::-webkit-scrollbar {
|
|
563
|
+
width: 6px;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.bw-svc-scroll-wrap::-webkit-scrollbar-thumb {
|
|
567
|
+
border-radius: var(--bw-radius-pill);
|
|
568
|
+
background: var(--bw-border);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.bw-svc-scroll-wrap::-webkit-scrollbar-thumb:hover {
|
|
572
|
+
background: var(--bw-text-muted);
|
|
376
573
|
}
|
|
377
574
|
|
|
378
575
|
.bw-svc-scroll-inner {
|
|
379
576
|
display: flex;
|
|
380
577
|
flex-direction: column;
|
|
381
|
-
padding-
|
|
578
|
+
padding-block: 12px 24px;
|
|
382
579
|
}
|
|
383
580
|
|
|
581
|
+
/* Service row — refactored to fix the narrow-width hierarchy issues:
|
|
582
|
+
- Top-aligned items so price reads with the first line of the name
|
|
583
|
+
- Name allowed to wrap with text-wrap: pretty (no orphans)
|
|
584
|
+
- Description line-clamps to 2 lines instead of single-line ellipsis
|
|
585
|
+
- Concentric inner radius (row 12px - 14px padding around 20px check
|
|
586
|
+
leaves the optical center on the first line of text) */
|
|
384
587
|
.bw-svc-row {
|
|
385
588
|
display: flex;
|
|
386
|
-
align-items:
|
|
387
|
-
gap:
|
|
589
|
+
align-items: flex-start;
|
|
590
|
+
gap: 12px;
|
|
388
591
|
width: 100%;
|
|
389
|
-
padding:
|
|
592
|
+
padding: 14px;
|
|
390
593
|
border: 0;
|
|
391
|
-
border-radius:
|
|
594
|
+
border-radius: var(--bw-radius);
|
|
392
595
|
background: transparent;
|
|
393
596
|
cursor: pointer;
|
|
394
597
|
text-align: left;
|
|
395
|
-
transition: background
|
|
598
|
+
transition: background var(--bw-duration) var(--bw-ease),
|
|
599
|
+
box-shadow var(--bw-duration) var(--bw-ease),
|
|
600
|
+
transform var(--bw-duration) var(--bw-ease);
|
|
396
601
|
}
|
|
397
602
|
|
|
398
603
|
.bw-svc-row:hover {
|
|
399
604
|
background: var(--bw-hover);
|
|
400
605
|
}
|
|
401
606
|
|
|
607
|
+
.bw-svc-row:focus-visible {
|
|
608
|
+
background: var(--bw-hover);
|
|
609
|
+
box-shadow: inset 0 0 0 1.5px var(--bw-text);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.bw-svc-row:active {
|
|
613
|
+
transform: scale(0.98);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Checkbox: aligned to first line of name (1.4 line-height on 14px =
|
|
617
|
+
~20px line, matches check height so they share a baseline). */
|
|
402
618
|
.bw-check {
|
|
403
619
|
display: flex;
|
|
404
620
|
flex-shrink: 0;
|
|
405
621
|
align-items: center;
|
|
406
622
|
justify-content: center;
|
|
407
|
-
width:
|
|
408
|
-
height:
|
|
409
|
-
|
|
410
|
-
border
|
|
623
|
+
width: 20px;
|
|
624
|
+
height: 20px;
|
|
625
|
+
margin-top: 1px;
|
|
626
|
+
border: 1.5px solid var(--bw-border);
|
|
627
|
+
border-radius: var(--bw-radius-xs);
|
|
411
628
|
color: transparent;
|
|
412
|
-
transition:
|
|
629
|
+
transition: border-color var(--bw-duration) var(--bw-ease),
|
|
630
|
+
background var(--bw-duration) var(--bw-ease),
|
|
631
|
+
color var(--bw-duration) var(--bw-ease);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.bw-svc-row:hover .bw-check {
|
|
635
|
+
border-color: var(--bw-text-secondary);
|
|
413
636
|
}
|
|
414
637
|
|
|
415
638
|
.bw-check.is-checked {
|
|
@@ -419,38 +642,156 @@
|
|
|
419
642
|
}
|
|
420
643
|
|
|
421
644
|
.bw-check--lg {
|
|
422
|
-
width:
|
|
423
|
-
height:
|
|
424
|
-
|
|
645
|
+
width: 22px;
|
|
646
|
+
height: 22px;
|
|
647
|
+
margin-top: 0;
|
|
648
|
+
border-radius: var(--bw-radius-xs);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* Service thumbnail (Fresha/Square pattern). Replaces the checkbox
|
|
652
|
+
slot when a service has an image. Selected state shows a small
|
|
653
|
+
primary-colored badge in the bottom-right corner with the same
|
|
654
|
+
check icon — keeps the affordance consistent with image-less
|
|
655
|
+
rows. Concentric radius (md = 12px) matches surrounding chrome. */
|
|
656
|
+
.bw-svc-image {
|
|
657
|
+
position: relative;
|
|
658
|
+
flex-shrink: 0;
|
|
659
|
+
width: 80px;
|
|
660
|
+
height: 80px;
|
|
661
|
+
margin-top: 0;
|
|
662
|
+
border-radius: var(--bw-radius-md);
|
|
663
|
+
overflow: hidden;
|
|
664
|
+
background: var(--bw-border-light);
|
|
665
|
+
outline: 1px solid rgba(0, 0, 0, 0.06);
|
|
666
|
+
outline-offset: -1px;
|
|
667
|
+
transition: outline-color var(--bw-duration) var(--bw-ease);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.bw-svc-image img {
|
|
671
|
+
display: block;
|
|
672
|
+
width: 100%;
|
|
673
|
+
height: 100%;
|
|
674
|
+
object-fit: cover;
|
|
675
|
+
object-position: center;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.bw-svc-row:hover .bw-svc-image {
|
|
679
|
+
outline-color: rgba(0, 0, 0, 0.12);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.bw-svc-image.is-checked {
|
|
683
|
+
outline-color: var(--bw-primary);
|
|
684
|
+
outline-width: 1.5px;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.bw-svc-image-badge {
|
|
688
|
+
position: absolute;
|
|
689
|
+
right: 4px;
|
|
690
|
+
bottom: 4px;
|
|
691
|
+
display: flex;
|
|
692
|
+
align-items: center;
|
|
693
|
+
justify-content: center;
|
|
694
|
+
width: 18px;
|
|
695
|
+
height: 18px;
|
|
696
|
+
border-radius: var(--bw-radius-pill);
|
|
697
|
+
background: var(--bw-primary);
|
|
698
|
+
color: var(--bw-primary-text);
|
|
699
|
+
box-shadow: var(--bw-shadow-badge);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.bw-svc-image-badge svg {
|
|
703
|
+
width: 11px;
|
|
704
|
+
height: 11px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.bw-svc-image--lg {
|
|
708
|
+
width: 96px;
|
|
709
|
+
height: 96px;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Meta footer row: groups duration + price on a single line under
|
|
713
|
+
the description with a dot separator. Replaces the old top-right
|
|
714
|
+
floating price so the row reads as a tidy stack instead of an
|
|
715
|
+
L-shape. */
|
|
716
|
+
.bw-svc-meta-row {
|
|
717
|
+
display: inline-flex;
|
|
718
|
+
align-items: baseline;
|
|
719
|
+
gap: 8px;
|
|
720
|
+
margin-top: 4px;
|
|
721
|
+
font-variant-numeric: tabular-nums;
|
|
722
|
+
color: var(--bw-text-secondary);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.bw-svc-meta-row .bw-svc-meta {
|
|
726
|
+
margin: 0;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.bw-svc-meta-row .bw-svc-price {
|
|
730
|
+
margin: 0;
|
|
731
|
+
flex-shrink: 0;
|
|
732
|
+
color: var(--bw-text);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.bw-svc-meta-dot {
|
|
736
|
+
color: var(--bw-text-muted);
|
|
737
|
+
user-select: none;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.bw-svc-info {
|
|
741
|
+
display: flex;
|
|
742
|
+
flex-direction: column;
|
|
743
|
+
gap: 4px;
|
|
744
|
+
min-width: 0;
|
|
745
|
+
flex: 1 1 auto;
|
|
425
746
|
}
|
|
426
747
|
|
|
748
|
+
/* Name: 14px, allowed to wrap, pretty wrap to avoid one-word
|
|
749
|
+
orphans, hard cap at 2 lines so very long names truncate
|
|
750
|
+
cleanly instead of dominating the row. */
|
|
427
751
|
.bw-svc-name {
|
|
428
|
-
font-size:
|
|
429
|
-
font-weight:
|
|
430
|
-
line-height: 1.
|
|
752
|
+
font-size: 14px;
|
|
753
|
+
font-weight: 600;
|
|
754
|
+
line-height: 1.35;
|
|
755
|
+
letter-spacing: -0.005em;
|
|
431
756
|
color: var(--bw-text);
|
|
757
|
+
text-wrap: pretty;
|
|
758
|
+
display: -webkit-box;
|
|
759
|
+
-webkit-line-clamp: 2;
|
|
760
|
+
-webkit-box-orient: vertical;
|
|
761
|
+
overflow: hidden;
|
|
432
762
|
}
|
|
433
763
|
|
|
764
|
+
/* Meta + description sit together as a soft secondary line. Meta
|
|
765
|
+
(duration) renders inline, then description picks up after a
|
|
766
|
+
middle dot. Wrapping is allowed; we line-clamp to 2 so multi-
|
|
767
|
+
sentence descriptions don't blow up the row height. */
|
|
434
768
|
.bw-svc-meta {
|
|
435
|
-
font-size:
|
|
436
|
-
line-height: 1.
|
|
437
|
-
color: var(--bw-text-
|
|
769
|
+
font-size: 13px;
|
|
770
|
+
line-height: 1.45;
|
|
771
|
+
color: var(--bw-text-secondary);
|
|
772
|
+
font-variant-numeric: tabular-nums;
|
|
438
773
|
}
|
|
439
774
|
|
|
440
775
|
.bw-svc-desc {
|
|
441
|
-
font-size:
|
|
442
|
-
line-height: 1.
|
|
443
|
-
color: var(--bw-text-
|
|
444
|
-
|
|
445
|
-
text-overflow: ellipsis;
|
|
446
|
-
white-space: nowrap;
|
|
776
|
+
font-size: 13px;
|
|
777
|
+
line-height: 1.45;
|
|
778
|
+
color: var(--bw-text-secondary);
|
|
779
|
+
text-wrap: pretty;
|
|
447
780
|
}
|
|
448
781
|
|
|
782
|
+
/* Price: same scale as name so the first-line baseline locks them
|
|
783
|
+
together optically. Regular weight (not 600) so price doesn't
|
|
784
|
+
compete for attention with the name — name leads, price reads
|
|
785
|
+
as a quiet endcap. */
|
|
449
786
|
.bw-svc-price {
|
|
450
787
|
flex-shrink: 0;
|
|
788
|
+
margin-top: 1px;
|
|
451
789
|
font-size: 14px;
|
|
452
|
-
font-weight:
|
|
790
|
+
font-weight: 500;
|
|
791
|
+
line-height: 1.35;
|
|
453
792
|
color: var(--bw-text);
|
|
793
|
+
font-variant-numeric: tabular-nums;
|
|
794
|
+
letter-spacing: -0.005em;
|
|
454
795
|
}
|
|
455
796
|
|
|
456
797
|
.bw-step-2-heading {
|
|
@@ -486,7 +827,7 @@
|
|
|
486
827
|
}
|
|
487
828
|
|
|
488
829
|
.bw-svc-row--full .bw-svc-meta {
|
|
489
|
-
font-size:
|
|
830
|
+
font-size: 13px;
|
|
490
831
|
}
|
|
491
832
|
|
|
492
833
|
.bw-col--center {
|
|
@@ -503,9 +844,10 @@
|
|
|
503
844
|
flex: 1;
|
|
504
845
|
flex-direction: column;
|
|
505
846
|
gap: 24px;
|
|
506
|
-
padding: 32px;
|
|
847
|
+
padding: 24px 32px 32px;
|
|
507
848
|
border: 1px solid var(--bw-border);
|
|
508
|
-
border-radius: var(--bw-radius);
|
|
849
|
+
border-radius: var(--bw-radius-lg);
|
|
850
|
+
background: var(--bw-bg);
|
|
509
851
|
}
|
|
510
852
|
|
|
511
853
|
.bw-cal-prompt,
|
|
@@ -518,9 +860,402 @@
|
|
|
518
860
|
color: var(--bw-text-muted);
|
|
519
861
|
}
|
|
520
862
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
863
|
+
/* Transition the service-picker swap (full list <-> selected card)
|
|
864
|
+
and the provider section's reveal. Each branch animates in on
|
|
865
|
+
mount via the shared bw-slots-fade-in keyframe so the picker
|
|
866
|
+
feels like a settling state-machine, not a hard swap. */
|
|
867
|
+
.bw-svc-selected,
|
|
868
|
+
.bw-svc-scroll,
|
|
869
|
+
.bw-provider-section {
|
|
870
|
+
animation: bw-slots-fade-in 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
@media (prefers-reduced-motion: reduce) {
|
|
874
|
+
.bw-svc-selected,
|
|
875
|
+
.bw-svc-scroll,
|
|
876
|
+
.bw-provider-section {
|
|
877
|
+
animation: none;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* Provider list — replaces the old dropdown so every option is
|
|
882
|
+
visible at a glance. Two layouts share the same DOM:
|
|
883
|
+
Desktop (>=1025px): vertical stack of horizontal rows
|
|
884
|
+
(avatar | name + desc) — reads like the old dropdown list.
|
|
885
|
+
Mobile (<=1024px): horizontal scrolling strip of square-ish
|
|
886
|
+
cards (avatar above name) so the row fits on a phone column.
|
|
887
|
+
Selection, hover, and disabled-state chrome is shared between
|
|
888
|
+
both layouts so the visual language matches the rest of the
|
|
889
|
+
widget. */
|
|
890
|
+
.bw-staff-row {
|
|
891
|
+
display: flex;
|
|
892
|
+
flex-direction: column;
|
|
893
|
+
gap: 4px;
|
|
894
|
+
padding: 0;
|
|
895
|
+
margin: 0;
|
|
896
|
+
/* Desktop list shouldn't scroll horizontally; if the column is
|
|
897
|
+
ever taller than the card, the surrounding scroller handles
|
|
898
|
+
vertical overflow. */
|
|
899
|
+
max-height: 320px;
|
|
900
|
+
overflow-y: auto;
|
|
901
|
+
overscroll-behavior: contain;
|
|
902
|
+
scrollbar-width: thin;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.bw-staff-row::-webkit-scrollbar {
|
|
906
|
+
width: 6px;
|
|
907
|
+
height: 6px;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.bw-staff-row::-webkit-scrollbar-track {
|
|
911
|
+
background: transparent;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.bw-staff-row::-webkit-scrollbar-thumb {
|
|
915
|
+
background: var(--bw-border);
|
|
916
|
+
border-radius: 3px;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.bw-staff-card {
|
|
920
|
+
display: flex;
|
|
921
|
+
flex-direction: row;
|
|
922
|
+
align-items: center;
|
|
923
|
+
gap: 12px;
|
|
924
|
+
width: 100%;
|
|
925
|
+
padding: 10px 12px;
|
|
926
|
+
border: 0;
|
|
927
|
+
border-radius: var(--bw-radius-sm);
|
|
928
|
+
background: transparent;
|
|
929
|
+
color: var(--bw-text);
|
|
930
|
+
cursor: pointer;
|
|
931
|
+
text-align: left;
|
|
932
|
+
transition:
|
|
933
|
+
background var(--bw-duration) var(--bw-ease),
|
|
934
|
+
color var(--bw-duration) var(--bw-ease);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.bw-staff-card:hover {
|
|
938
|
+
background: var(--bw-hover);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.bw-staff-card.is-active {
|
|
942
|
+
background: var(--bw-bg);
|
|
943
|
+
/* Inset shadow so the active state shows a crisp black ring
|
|
944
|
+
without shifting layout (default card has no border). */
|
|
945
|
+
box-shadow: inset 0 0 0 1.5px var(--bw-text);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.bw-staff-card.is-active:hover {
|
|
949
|
+
background: var(--bw-bg);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.bw-staff-card.is-disabled {
|
|
953
|
+
cursor: not-allowed;
|
|
954
|
+
opacity: 0.5;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.bw-staff-card.is-disabled:hover {
|
|
958
|
+
background: transparent;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.bw-staff-card-avatar {
|
|
962
|
+
position: relative;
|
|
963
|
+
display: inline-flex;
|
|
964
|
+
align-items: center;
|
|
965
|
+
justify-content: center;
|
|
966
|
+
width: 36px;
|
|
967
|
+
height: 36px;
|
|
968
|
+
border-radius: 999px;
|
|
969
|
+
background: var(--bw-border-light);
|
|
970
|
+
color: var(--bw-text-secondary);
|
|
971
|
+
flex-shrink: 0;
|
|
972
|
+
overflow: hidden;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.bw-staff-card-avatar svg {
|
|
976
|
+
width: 16px;
|
|
977
|
+
height: 16px;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.bw-staff-card-avatar .bw-staff-initials {
|
|
981
|
+
font-size: 12px;
|
|
982
|
+
font-weight: 500;
|
|
983
|
+
color: var(--bw-text);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.bw-staff-card-info {
|
|
987
|
+
display: flex;
|
|
988
|
+
flex-direction: column;
|
|
989
|
+
gap: 2px;
|
|
990
|
+
min-width: 0;
|
|
991
|
+
flex: 1;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.bw-staff-card-name {
|
|
995
|
+
font-size: 14px;
|
|
996
|
+
font-weight: 500;
|
|
997
|
+
line-height: 1.25;
|
|
998
|
+
color: var(--bw-text);
|
|
999
|
+
overflow: hidden;
|
|
1000
|
+
text-overflow: ellipsis;
|
|
1001
|
+
white-space: nowrap;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.bw-staff-card-desc {
|
|
1005
|
+
font-size: 12px;
|
|
1006
|
+
line-height: 1.25;
|
|
1007
|
+
color: var(--bw-text-muted);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/* Mobile uses the same vertical list as desktop — no horizontal
|
|
1011
|
+
scrolling chip strip. The single layout keeps the visual language
|
|
1012
|
+
identical across breakpoints; if the list exceeds the card's
|
|
1013
|
+
built-in 320px max-height, it scrolls vertically inside the
|
|
1014
|
+
card. */
|
|
1015
|
+
|
|
1016
|
+
/* Mobile step 2 stacks three frames in bw-col--center: the calendar
|
|
1017
|
+
card (existing), the provider card, then the times card. Each
|
|
1018
|
+
mobile-only card matches the calendar card chrome so the column
|
|
1019
|
+
reads as one family. Hidden on desktop where each section has
|
|
1020
|
+
its own column. */
|
|
1021
|
+
.bw-mobile-card,
|
|
1022
|
+
.bw-provider-section--mobile {
|
|
1023
|
+
display: none;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
@media (max-width: 1024px) {
|
|
1027
|
+
.bw-mobile-card {
|
|
1028
|
+
display: flex;
|
|
1029
|
+
flex-direction: column;
|
|
1030
|
+
gap: 12px;
|
|
1031
|
+
margin-top: 16px;
|
|
1032
|
+
padding: 20px 20px 24px;
|
|
1033
|
+
border: 1px solid var(--bw-border);
|
|
1034
|
+
border-radius: var(--bw-radius-lg);
|
|
1035
|
+
background: var(--bw-bg);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/* Times card needs extra bottom breathing room so the slot pills
|
|
1039
|
+
don't sit flush against the card border. */
|
|
1040
|
+
.bw-mobile-card.bw-slots-mobile {
|
|
1041
|
+
padding-bottom: 28px;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* Label sits at the top of each mobile card. Its desktop margin
|
|
1045
|
+
would double up with the parent's flex gap, so we zero it. */
|
|
1046
|
+
.bw-mobile-card > .bw-label {
|
|
1047
|
+
margin-bottom: 0;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/* The provider section uses .bw-mobile-card chrome — kill the
|
|
1051
|
+
desktop bw-provider-section animation so it doesn't replay
|
|
1052
|
+
when the section enters the mobile flow. */
|
|
1053
|
+
.bw-mobile-card.bw-provider-section--mobile {
|
|
1054
|
+
animation: none;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/* Service picker — collapsed selected state. Once a service is
|
|
1059
|
+
picked, the long list collapses to a read-only card showing just
|
|
1060
|
+
the selected service plus a "Change" link. Below this card the
|
|
1061
|
+
provider section reveals (per the service-first flow). The card
|
|
1062
|
+
reuses .bw-svc-row chrome but with --readonly disabling hover/cursor. */
|
|
1063
|
+
.bw-svc-selected {
|
|
1064
|
+
display: flex;
|
|
1065
|
+
flex-direction: column;
|
|
1066
|
+
gap: 12px;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.bw-svc-selected-header {
|
|
1070
|
+
display: flex;
|
|
1071
|
+
align-items: baseline;
|
|
1072
|
+
justify-content: space-between;
|
|
1073
|
+
gap: 12px;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.bw-svc-selected-header .bw-label {
|
|
1077
|
+
margin-bottom: 0;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.bw-svc-change {
|
|
1081
|
+
padding: 0;
|
|
1082
|
+
border: 0;
|
|
1083
|
+
background: transparent;
|
|
1084
|
+
color: var(--bw-text-secondary);
|
|
1085
|
+
font-size: 13px;
|
|
1086
|
+
font-weight: 500;
|
|
1087
|
+
cursor: pointer;
|
|
1088
|
+
transition: color var(--bw-duration) var(--bw-ease);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
.bw-svc-change:hover {
|
|
1092
|
+
color: var(--bw-text);
|
|
1093
|
+
text-decoration: underline;
|
|
1094
|
+
text-underline-offset: 3px;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.bw-svc-row--readonly {
|
|
1098
|
+
cursor: default;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.bw-svc-row--readonly:hover {
|
|
1102
|
+
background: transparent;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.bw-svc-row--readonly:hover .bw-check {
|
|
1106
|
+
border-color: var(--bw-primary);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* === Payment panel (design proposal) ===
|
|
1110
|
+
Renders inside the details form when paymentMode === 'full' or
|
|
1111
|
+
'deposit'. Two stacks: a soft cararra-tinted summary card with
|
|
1112
|
+
line items + total + charge breakdown (mirrors the reference
|
|
1113
|
+
pattern but in Sable's tone), then a card-details slot for the
|
|
1114
|
+
eventual Stripe PaymentElement, then a small "secured by Stripe"
|
|
1115
|
+
reassurance line. Stub fields are placeholders only — real impl
|
|
1116
|
+
replaces .bw-pay-card-slot's children with <PaymentElement />. */
|
|
1117
|
+
.bw-pay {
|
|
1118
|
+
display: flex;
|
|
1119
|
+
flex-direction: column;
|
|
1120
|
+
gap: 18px;
|
|
1121
|
+
margin-top: 4px;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.bw-pay-summary {
|
|
1125
|
+
display: flex;
|
|
1126
|
+
flex-direction: column;
|
|
1127
|
+
padding: 18px 20px;
|
|
1128
|
+
border: 1px solid var(--bw-border);
|
|
1129
|
+
border-radius: var(--bw-radius-lg);
|
|
1130
|
+
background: var(--bw-border-light);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.bw-pay-summary-header {
|
|
1134
|
+
margin-bottom: 14px;
|
|
1135
|
+
font-size: 13px;
|
|
1136
|
+
font-weight: 600;
|
|
1137
|
+
color: var(--bw-text);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.bw-pay-summary-row {
|
|
1141
|
+
display: flex;
|
|
1142
|
+
align-items: baseline;
|
|
1143
|
+
justify-content: space-between;
|
|
1144
|
+
gap: 16px;
|
|
1145
|
+
padding: 4px 0;
|
|
1146
|
+
font-size: 13px;
|
|
1147
|
+
line-height: 1.45;
|
|
1148
|
+
color: var(--bw-text);
|
|
1149
|
+
font-variant-numeric: tabular-nums;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.bw-pay-summary-row--muted {
|
|
1153
|
+
color: var(--bw-text-secondary);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.bw-pay-summary-row--total {
|
|
1157
|
+
font-weight: 600;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.bw-pay-summary-row--strong {
|
|
1161
|
+
margin-top: 2px;
|
|
1162
|
+
font-size: 14px;
|
|
1163
|
+
font-weight: 600;
|
|
1164
|
+
color: var(--bw-text);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.bw-pay-summary-val {
|
|
1168
|
+
flex-shrink: 0;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.bw-pay-summary-divider {
|
|
1172
|
+
height: 1px;
|
|
1173
|
+
margin: 10px 0;
|
|
1174
|
+
background: var(--bw-border);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.bw-pay-card {
|
|
1178
|
+
display: flex;
|
|
1179
|
+
flex-direction: column;
|
|
1180
|
+
gap: 10px;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.bw-pay-card .bw-label {
|
|
1184
|
+
margin-bottom: 0;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/* Card slot: a soft surface where Stripe's PaymentElement will
|
|
1188
|
+
mount. Until then, render a static stub of the fields so the
|
|
1189
|
+
spacing + chrome can be validated. */
|
|
1190
|
+
.bw-pay-card-slot {
|
|
1191
|
+
padding: 14px;
|
|
1192
|
+
border: 1px solid var(--bw-border);
|
|
1193
|
+
border-radius: var(--bw-radius-lg);
|
|
1194
|
+
background: var(--bw-bg);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.bw-pay-card-stub {
|
|
1198
|
+
display: flex;
|
|
1199
|
+
flex-direction: column;
|
|
1200
|
+
gap: 12px;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.bw-pay-card-stub-row {
|
|
1204
|
+
display: flex;
|
|
1205
|
+
flex-direction: column;
|
|
1206
|
+
gap: 4px;
|
|
1207
|
+
padding: 10px 12px;
|
|
1208
|
+
border: 1px solid var(--bw-border-light);
|
|
1209
|
+
border-radius: var(--bw-radius);
|
|
1210
|
+
background: var(--bw-border-light);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.bw-pay-card-stub-label {
|
|
1214
|
+
font-size: 11px;
|
|
1215
|
+
font-weight: 500;
|
|
1216
|
+
color: var(--bw-text-muted);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.bw-pay-card-stub-value {
|
|
1220
|
+
font-size: 13px;
|
|
1221
|
+
color: var(--bw-text-secondary);
|
|
1222
|
+
font-variant-numeric: tabular-nums;
|
|
1223
|
+
letter-spacing: 0.04em;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.bw-pay-card-stub-grid {
|
|
1227
|
+
display: grid;
|
|
1228
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
1229
|
+
gap: 8px;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
.bw-pay-secure {
|
|
1233
|
+
margin: 0;
|
|
1234
|
+
font-size: 12px;
|
|
1235
|
+
color: var(--bw-text-muted);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/* Empty-state when no service is selected. Centered in the slots
|
|
1239
|
+
column so the right pane reads as "waiting for input" instead
|
|
1240
|
+
of just blank. Used in both desktop (right column) and mobile
|
|
1241
|
+
(inline under the calendar) since slotsArea renders in both. */
|
|
1242
|
+
.bw-slots-empty {
|
|
1243
|
+
display: flex;
|
|
1244
|
+
flex: 1;
|
|
1245
|
+
align-items: center;
|
|
1246
|
+
justify-content: center;
|
|
1247
|
+
min-height: 120px;
|
|
1248
|
+
margin: 0;
|
|
1249
|
+
padding: 24px 12px;
|
|
1250
|
+
font-size: 14px;
|
|
1251
|
+
line-height: 1.5;
|
|
1252
|
+
color: var(--bw-text-secondary);
|
|
1253
|
+
text-align: center;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.bw-cal-prompt {
|
|
1257
|
+
padding-top: 8px;
|
|
1258
|
+
text-align: center;
|
|
524
1259
|
}
|
|
525
1260
|
|
|
526
1261
|
.bw-cal-header {
|
|
@@ -609,35 +1344,42 @@
|
|
|
609
1344
|
.bw-cal-grid {
|
|
610
1345
|
display: grid;
|
|
611
1346
|
grid-template-columns: repeat(7, 1fr);
|
|
1347
|
+
column-gap: 6px;
|
|
612
1348
|
}
|
|
613
1349
|
|
|
614
1350
|
.bw-cal-weekdays {
|
|
615
1351
|
text-align: center;
|
|
1352
|
+
margin-bottom: 4px;
|
|
616
1353
|
}
|
|
617
1354
|
|
|
618
1355
|
.bw-cal-weekdays span {
|
|
619
1356
|
padding: 6px 0;
|
|
620
|
-
font-size:
|
|
621
|
-
font-weight:
|
|
1357
|
+
font-size: 11px;
|
|
1358
|
+
font-weight: 600;
|
|
1359
|
+
letter-spacing: 0.08em;
|
|
1360
|
+
text-transform: uppercase;
|
|
622
1361
|
color: var(--bw-text-muted);
|
|
623
1362
|
}
|
|
624
1363
|
|
|
625
1364
|
.bw-cal-grid {
|
|
626
|
-
gap: 6px
|
|
1365
|
+
gap: 6px;
|
|
627
1366
|
}
|
|
628
1367
|
|
|
629
1368
|
.bw-cal-day {
|
|
1369
|
+
position: relative;
|
|
630
1370
|
display: flex;
|
|
631
1371
|
align-items: center;
|
|
632
1372
|
justify-content: center;
|
|
633
1373
|
height: 48px;
|
|
634
1374
|
border: 0;
|
|
635
|
-
border-radius:
|
|
1375
|
+
border-radius: var(--bw-radius-sm);
|
|
636
1376
|
background: transparent;
|
|
637
1377
|
color: var(--bw-text);
|
|
638
1378
|
cursor: default;
|
|
639
1379
|
font-size: 13px;
|
|
640
|
-
|
|
1380
|
+
font-variant-numeric: tabular-nums;
|
|
1381
|
+
transition: background var(--bw-duration) var(--bw-ease),
|
|
1382
|
+
color var(--bw-duration) var(--bw-ease);
|
|
641
1383
|
}
|
|
642
1384
|
|
|
643
1385
|
.bw-cal-day.is-outside {
|
|
@@ -662,6 +1404,26 @@
|
|
|
662
1404
|
font-weight: 600;
|
|
663
1405
|
}
|
|
664
1406
|
|
|
1407
|
+
/* Dot under today's number — subtle reference indicator. Hidden when
|
|
1408
|
+
the day is the active selection (the filled background already
|
|
1409
|
+
communicates focus). */
|
|
1410
|
+
.bw-cal-day.is-today::after {
|
|
1411
|
+
content: '';
|
|
1412
|
+
position: absolute;
|
|
1413
|
+
bottom: 6px;
|
|
1414
|
+
left: 50%;
|
|
1415
|
+
width: 4px;
|
|
1416
|
+
height: 4px;
|
|
1417
|
+
border-radius: var(--bw-radius-pill);
|
|
1418
|
+
background: var(--bw-text-muted);
|
|
1419
|
+
transform: translateX(-50%);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
.bw-cal-day.is-today.is-selected::after {
|
|
1423
|
+
background: var(--bw-primary-text);
|
|
1424
|
+
opacity: 0.7;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
665
1427
|
.bw-cal-day.is-blocked,
|
|
666
1428
|
.bw-cal-day.is-disabled {
|
|
667
1429
|
color: var(--bw-border);
|
|
@@ -669,26 +1431,85 @@
|
|
|
669
1431
|
|
|
670
1432
|
.bw-time-slots {
|
|
671
1433
|
display: grid;
|
|
672
|
-
|
|
1434
|
+
/* 3-column grid on mobile so each pill gets more horizontal room
|
|
1435
|
+
and longer locale time strings (e.g. "10:30 a. m.") don't crowd
|
|
1436
|
+
each other. Desktop overrides this to 1fr further down to render
|
|
1437
|
+
a vertical pill list. */
|
|
1438
|
+
grid-template-columns: repeat(3, 1fr);
|
|
673
1439
|
gap: 10px;
|
|
674
1440
|
margin-top: 8px;
|
|
675
1441
|
}
|
|
676
1442
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1443
|
+
/* Wrapper that re-mounts whenever service/staff/date/loading state
|
|
1444
|
+
changes (key-driven from JSX). The wrapper itself fades, and each
|
|
1445
|
+
slot pill staggers in via --bw-slot-i set inline so the list
|
|
1446
|
+
reads as "settling into place" instead of popping. */
|
|
1447
|
+
.bw-slots-fade {
|
|
1448
|
+
animation: bw-slots-fade-in 220ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.bw-slots-fade .bw-slot {
|
|
1452
|
+
animation: bw-slot-stagger-in 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1453
|
+
animation-delay: calc(var(--bw-slot-i, 0) * 22ms);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
@keyframes bw-slots-fade-in {
|
|
1457
|
+
from {
|
|
1458
|
+
opacity: 0;
|
|
1459
|
+
transform: translateY(4px);
|
|
1460
|
+
}
|
|
1461
|
+
to {
|
|
1462
|
+
opacity: 1;
|
|
1463
|
+
transform: translateY(0);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
@keyframes bw-slot-stagger-in {
|
|
1468
|
+
from {
|
|
1469
|
+
opacity: 0;
|
|
1470
|
+
transform: translateY(6px) scale(0.98);
|
|
1471
|
+
}
|
|
1472
|
+
to {
|
|
1473
|
+
opacity: 1;
|
|
1474
|
+
transform: translateY(0) scale(1);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1479
|
+
.bw-slots-fade,
|
|
1480
|
+
.bw-slots-fade .bw-slot {
|
|
1481
|
+
animation: none;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.bw-slot {
|
|
1486
|
+
display: flex;
|
|
1487
|
+
align-items: center;
|
|
1488
|
+
justify-content: center;
|
|
1489
|
+
padding: 12px 14px;
|
|
1490
|
+
min-width: 0;
|
|
1491
|
+
border: 1px solid var(--bw-border);
|
|
1492
|
+
border-radius: var(--bw-radius);
|
|
1493
|
+
background: transparent;
|
|
1494
|
+
cursor: pointer;
|
|
683
1495
|
text-align: center;
|
|
684
1496
|
white-space: nowrap;
|
|
685
1497
|
font-size: 13px;
|
|
1498
|
+
font-variant-numeric: tabular-nums;
|
|
686
1499
|
color: var(--bw-text);
|
|
687
|
-
transition:
|
|
1500
|
+
transition: border-color var(--bw-duration) var(--bw-ease),
|
|
1501
|
+
background var(--bw-duration) var(--bw-ease),
|
|
1502
|
+
color var(--bw-duration) var(--bw-ease),
|
|
1503
|
+
transform var(--bw-duration) var(--bw-ease);
|
|
688
1504
|
}
|
|
689
1505
|
|
|
690
1506
|
.bw-slot:hover {
|
|
691
|
-
border-color: var(--bw-text);
|
|
1507
|
+
border-color: var(--bw-text-secondary);
|
|
1508
|
+
background: var(--bw-hover);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.bw-slot:active {
|
|
1512
|
+
transform: scale(0.98);
|
|
692
1513
|
}
|
|
693
1514
|
|
|
694
1515
|
.bw-slot.is-active {
|
|
@@ -741,23 +1562,45 @@
|
|
|
741
1562
|
.bw-field input,
|
|
742
1563
|
.bw-field textarea {
|
|
743
1564
|
width: 100%;
|
|
744
|
-
padding:
|
|
1565
|
+
padding: 11px 16px;
|
|
745
1566
|
border: 1px solid var(--bw-border);
|
|
746
|
-
border-radius:
|
|
1567
|
+
border-radius: var(--bw-radius-md);
|
|
747
1568
|
background: transparent;
|
|
748
1569
|
color: var(--bw-text);
|
|
749
1570
|
font-size: 13px;
|
|
750
|
-
|
|
1571
|
+
font-family: inherit;
|
|
1572
|
+
transition: border-color var(--bw-duration) var(--bw-ease),
|
|
1573
|
+
box-shadow var(--bw-duration) var(--bw-ease);
|
|
751
1574
|
}
|
|
752
1575
|
|
|
753
1576
|
.bw-field textarea {
|
|
754
|
-
min-height:
|
|
1577
|
+
min-height: 96px;
|
|
755
1578
|
resize: none;
|
|
756
1579
|
}
|
|
757
1580
|
|
|
1581
|
+
.bw-field input:hover,
|
|
1582
|
+
.bw-field textarea:hover {
|
|
1583
|
+
border-color: var(--bw-text-secondary);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
758
1586
|
.bw-field input:focus,
|
|
759
1587
|
.bw-field textarea:focus {
|
|
760
1588
|
border-color: var(--bw-primary);
|
|
1589
|
+
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.bw-field.is-invalid input,
|
|
1593
|
+
.bw-field.is-invalid textarea,
|
|
1594
|
+
.bw-field.is-invalid select {
|
|
1595
|
+
border-color: var(--bw-error-text);
|
|
1596
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bw-error-text) 12%, transparent);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
.bw-field-error {
|
|
1600
|
+
margin-top: -4px;
|
|
1601
|
+
color: var(--bw-error-text);
|
|
1602
|
+
font-size: 12px;
|
|
1603
|
+
line-height: 1.35;
|
|
761
1604
|
}
|
|
762
1605
|
|
|
763
1606
|
.bw-field input::placeholder,
|
|
@@ -765,6 +1608,208 @@
|
|
|
765
1608
|
color: var(--bw-text-muted);
|
|
766
1609
|
}
|
|
767
1610
|
|
|
1611
|
+
/* ===========================================================================
|
|
1612
|
+
Custom IntakeSelect — replaces the native <select>. Trigger matches
|
|
1613
|
+
the .bw-field input chrome (border, radius, padding, font). Menu sits
|
|
1614
|
+
absolutely below, scrolls if taller than max-height, animates in.
|
|
1615
|
+
=========================================================================== */
|
|
1616
|
+
.bw-select {
|
|
1617
|
+
position: relative;
|
|
1618
|
+
width: 100%;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.bw-select-trigger {
|
|
1622
|
+
display: flex;
|
|
1623
|
+
align-items: center;
|
|
1624
|
+
justify-content: space-between;
|
|
1625
|
+
gap: 12px;
|
|
1626
|
+
width: 100%;
|
|
1627
|
+
padding: 11px 14px 11px 16px;
|
|
1628
|
+
border: 1px solid var(--bw-border);
|
|
1629
|
+
border-radius: var(--bw-radius-md);
|
|
1630
|
+
background: transparent;
|
|
1631
|
+
color: var(--bw-text);
|
|
1632
|
+
font-size: 13px;
|
|
1633
|
+
font-family: inherit;
|
|
1634
|
+
text-align: left;
|
|
1635
|
+
cursor: pointer;
|
|
1636
|
+
transition:
|
|
1637
|
+
border-color var(--bw-duration) var(--bw-ease),
|
|
1638
|
+
box-shadow var(--bw-duration) var(--bw-ease);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
.bw-select-trigger:hover {
|
|
1642
|
+
border-color: var(--bw-text-secondary);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
.bw-select-trigger:focus-visible,
|
|
1646
|
+
.bw-select-trigger.is-open {
|
|
1647
|
+
outline: 0;
|
|
1648
|
+
border-color: var(--bw-primary);
|
|
1649
|
+
box-shadow: 0 0 0 3px rgba(15, 15, 15, 0.05);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
.bw-select-value {
|
|
1653
|
+
flex: 1;
|
|
1654
|
+
min-width: 0;
|
|
1655
|
+
overflow: hidden;
|
|
1656
|
+
text-overflow: ellipsis;
|
|
1657
|
+
white-space: nowrap;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
.bw-select-value.is-placeholder {
|
|
1661
|
+
color: var(--bw-text-muted);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
.bw-select-chevron {
|
|
1665
|
+
flex-shrink: 0;
|
|
1666
|
+
display: inline-flex;
|
|
1667
|
+
align-items: center;
|
|
1668
|
+
justify-content: center;
|
|
1669
|
+
width: 16px;
|
|
1670
|
+
height: 16px;
|
|
1671
|
+
color: var(--bw-text-secondary);
|
|
1672
|
+
transition: transform var(--bw-duration) var(--bw-ease);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.bw-select-trigger.is-open .bw-select-chevron {
|
|
1676
|
+
transform: rotate(180deg);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.bw-select-chevron svg {
|
|
1680
|
+
width: 16px;
|
|
1681
|
+
height: 16px;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.bw-select-menu {
|
|
1685
|
+
position: absolute;
|
|
1686
|
+
top: calc(100% + 6px);
|
|
1687
|
+
left: 0;
|
|
1688
|
+
right: 0;
|
|
1689
|
+
/* Sits above the widget's own footer (z-index up to 102 on mobile)
|
|
1690
|
+
and any nearby chrome so flip-up + flip-down both render cleanly. */
|
|
1691
|
+
z-index: 110;
|
|
1692
|
+
/* Cap menu height to whichever is smaller — the 280px design max
|
|
1693
|
+
or 60% of the viewport — so on short phones the dropdown never
|
|
1694
|
+
dominates the screen and always leaves the trigger visible. */
|
|
1695
|
+
max-height: min(280px, 60svh);
|
|
1696
|
+
overflow-y: auto;
|
|
1697
|
+
margin: 0;
|
|
1698
|
+
padding: 6px;
|
|
1699
|
+
list-style: none;
|
|
1700
|
+
background: var(--bw-bg);
|
|
1701
|
+
border: 1px solid var(--bw-border);
|
|
1702
|
+
border-radius: var(--bw-radius-md);
|
|
1703
|
+
box-shadow: var(--bw-shadow-lift);
|
|
1704
|
+
/* 250ms ease-out-expo open animation per the widget's motion
|
|
1705
|
+
spec. Scale + fade + 4px lift mirrors the popup-animation rule
|
|
1706
|
+
used elsewhere in the Sable product. */
|
|
1707
|
+
animation: bw-select-open 250ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1708
|
+
outline: 0;
|
|
1709
|
+
scrollbar-width: thin;
|
|
1710
|
+
scrollbar-color: var(--bw-border) transparent;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
@keyframes bw-select-open {
|
|
1714
|
+
from {
|
|
1715
|
+
opacity: 0;
|
|
1716
|
+
transform: translateY(-4px) scale(0.97);
|
|
1717
|
+
}
|
|
1718
|
+
to {
|
|
1719
|
+
opacity: 1;
|
|
1720
|
+
transform: translateY(0) scale(1);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/* Flip-up variant: when there's no room below the trigger (select
|
|
1725
|
+
sits near the bottom of the widget AND the host page has a fixed
|
|
1726
|
+
footer below it), the menu opens upward instead. Computed at open
|
|
1727
|
+
time by IntakeSelect.computePlacement. */
|
|
1728
|
+
.bw-select-menu.is-above {
|
|
1729
|
+
top: auto;
|
|
1730
|
+
bottom: calc(100% + 6px);
|
|
1731
|
+
animation-name: bw-select-open-above;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
@keyframes bw-select-open-above {
|
|
1735
|
+
from {
|
|
1736
|
+
opacity: 0;
|
|
1737
|
+
transform: translateY(4px) scale(0.97);
|
|
1738
|
+
}
|
|
1739
|
+
to {
|
|
1740
|
+
opacity: 1;
|
|
1741
|
+
transform: translateY(0) scale(1);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
.bw-select-option {
|
|
1746
|
+
display: flex;
|
|
1747
|
+
align-items: center;
|
|
1748
|
+
justify-content: space-between;
|
|
1749
|
+
gap: 12px;
|
|
1750
|
+
padding: 9px 12px;
|
|
1751
|
+
border-radius: var(--bw-radius-sm);
|
|
1752
|
+
color: var(--bw-text);
|
|
1753
|
+
font-size: 14px;
|
|
1754
|
+
cursor: pointer;
|
|
1755
|
+
user-select: none;
|
|
1756
|
+
transition: background-color var(--bw-duration) var(--bw-ease);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.bw-select-option.is-focused {
|
|
1760
|
+
background: var(--bw-hover);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
.bw-select-option.is-active {
|
|
1764
|
+
color: var(--bw-text);
|
|
1765
|
+
font-weight: 500;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.bw-select-option-label {
|
|
1769
|
+
flex: 1;
|
|
1770
|
+
min-width: 0;
|
|
1771
|
+
overflow: hidden;
|
|
1772
|
+
text-overflow: ellipsis;
|
|
1773
|
+
white-space: nowrap;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
.bw-select-option-check {
|
|
1777
|
+
flex-shrink: 0;
|
|
1778
|
+
display: inline-flex;
|
|
1779
|
+
align-items: center;
|
|
1780
|
+
justify-content: center;
|
|
1781
|
+
width: 14px;
|
|
1782
|
+
height: 14px;
|
|
1783
|
+
color: var(--bw-primary);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
.bw-select-option-check svg {
|
|
1787
|
+
width: 14px;
|
|
1788
|
+
height: 14px;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1792
|
+
.bw-select-menu {
|
|
1793
|
+
animation: none;
|
|
1794
|
+
}
|
|
1795
|
+
.bw-select-chevron {
|
|
1796
|
+
transition: none;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
@media (max-width: 1024px) {
|
|
1801
|
+
/* Match the bumped 16px input font on mobile so the dropdown sits
|
|
1802
|
+
in the same visual family as the rest of the form fields. */
|
|
1803
|
+
.bw-select-trigger {
|
|
1804
|
+
font-size: 16px;
|
|
1805
|
+
padding: 12px 14px 12px 16px;
|
|
1806
|
+
}
|
|
1807
|
+
.bw-select-option {
|
|
1808
|
+
font-size: 16px;
|
|
1809
|
+
padding: 12px;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
768
1813
|
.bw-field--notes {
|
|
769
1814
|
position: relative;
|
|
770
1815
|
}
|
|
@@ -777,25 +1822,55 @@
|
|
|
777
1822
|
color: var(--bw-text-muted);
|
|
778
1823
|
}
|
|
779
1824
|
|
|
1825
|
+
.bw-submit-help {
|
|
1826
|
+
border: 1px solid color-mix(in srgb, var(--bw-error-text) 24%, var(--bw-border));
|
|
1827
|
+
border-radius: 10px;
|
|
1828
|
+
background: color-mix(in srgb, var(--bw-error-text) 8%, var(--bw-bg));
|
|
1829
|
+
color: var(--bw-text);
|
|
1830
|
+
padding: 10px 12px;
|
|
1831
|
+
font-size: 12px;
|
|
1832
|
+
line-height: 1.45;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.bw-submit-help-title {
|
|
1836
|
+
display: block;
|
|
1837
|
+
font-weight: 650;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
.bw-submit-help ul {
|
|
1841
|
+
margin: 6px 0 0;
|
|
1842
|
+
padding-left: 18px;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
.bw-submit-help li + li {
|
|
1846
|
+
margin-top: 2px;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
780
1849
|
.bw-required {
|
|
1850
|
+
/* 4px gap between the label and the asterisk so they don't kiss. */
|
|
1851
|
+
margin-left: 4px;
|
|
781
1852
|
color: var(--bw-error-text);
|
|
782
1853
|
font-weight: 400;
|
|
783
1854
|
}
|
|
784
1855
|
|
|
785
1856
|
.bw-summary {
|
|
786
|
-
padding
|
|
1857
|
+
padding: 18px;
|
|
1858
|
+
border: 1px solid var(--bw-border);
|
|
1859
|
+
border-radius: var(--bw-radius-lg);
|
|
1860
|
+
background: var(--bw-bg);
|
|
787
1861
|
}
|
|
788
1862
|
|
|
789
1863
|
.bw-summary-title {
|
|
790
1864
|
display: block;
|
|
791
|
-
margin-bottom:
|
|
792
|
-
font-size:
|
|
1865
|
+
margin-bottom: 14px;
|
|
1866
|
+
font-size: 13px;
|
|
793
1867
|
font-weight: 600;
|
|
1868
|
+
letter-spacing: -0.005em;
|
|
794
1869
|
color: var(--bw-text);
|
|
795
1870
|
}
|
|
796
1871
|
|
|
797
1872
|
.bw-summary-rows {
|
|
798
|
-
gap:
|
|
1873
|
+
gap: 10px;
|
|
799
1874
|
}
|
|
800
1875
|
|
|
801
1876
|
.bw-summary-row {
|
|
@@ -804,6 +1879,8 @@
|
|
|
804
1879
|
justify-content: space-between;
|
|
805
1880
|
gap: 16px;
|
|
806
1881
|
font-size: 13px;
|
|
1882
|
+
line-height: 1.45;
|
|
1883
|
+
font-variant-numeric: tabular-nums;
|
|
807
1884
|
}
|
|
808
1885
|
|
|
809
1886
|
.bw-summary-row span:first-child {
|
|
@@ -818,6 +1895,9 @@
|
|
|
818
1895
|
text-align: right;
|
|
819
1896
|
text-overflow: ellipsis;
|
|
820
1897
|
white-space: nowrap;
|
|
1898
|
+
/* Hold countdown + price + date numerals all benefit from tabular
|
|
1899
|
+
figures so they don't shift horizontally as digits change. */
|
|
1900
|
+
font-variant-numeric: tabular-nums;
|
|
821
1901
|
}
|
|
822
1902
|
|
|
823
1903
|
.bw-summary-total span:first-child,
|
|
@@ -848,6 +1928,63 @@
|
|
|
848
1928
|
line-height: 1.4;
|
|
849
1929
|
}
|
|
850
1930
|
|
|
1931
|
+
.bw-service-warning {
|
|
1932
|
+
display: flex;
|
|
1933
|
+
gap: 14px;
|
|
1934
|
+
padding: 18px;
|
|
1935
|
+
border: 2px solid #ef4444;
|
|
1936
|
+
border-radius: var(--bw-radius-lg);
|
|
1937
|
+
background: #fef2f2;
|
|
1938
|
+
color: #7f1d1d;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
.bw-service-warning-icon {
|
|
1942
|
+
display: flex;
|
|
1943
|
+
flex: 0 0 auto;
|
|
1944
|
+
align-items: center;
|
|
1945
|
+
justify-content: center;
|
|
1946
|
+
width: 42px;
|
|
1947
|
+
height: 42px;
|
|
1948
|
+
border-radius: 999px;
|
|
1949
|
+
background: #fee2e2;
|
|
1950
|
+
color: #b91c1c;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
.bw-service-warning-icon svg {
|
|
1954
|
+
width: 24px;
|
|
1955
|
+
height: 24px;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.bw-service-warning-copy {
|
|
1959
|
+
display: flex;
|
|
1960
|
+
min-width: 0;
|
|
1961
|
+
flex-direction: column;
|
|
1962
|
+
gap: 6px;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
.bw-service-warning-title {
|
|
1966
|
+
font-size: 16px;
|
|
1967
|
+
font-weight: 800;
|
|
1968
|
+
line-height: 1.25;
|
|
1969
|
+
color: #7f1d1d;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
.bw-service-warning p {
|
|
1973
|
+
margin: 0;
|
|
1974
|
+
font-size: 13px;
|
|
1975
|
+
line-height: 1.5;
|
|
1976
|
+
color: #991b1b;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
.bw-service-warning-link {
|
|
1980
|
+
align-self: flex-start;
|
|
1981
|
+
font-size: 13px;
|
|
1982
|
+
font-weight: 800;
|
|
1983
|
+
color: #7f1d1d;
|
|
1984
|
+
text-decoration: underline;
|
|
1985
|
+
text-underline-offset: 3px;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
851
1988
|
.bw-confirm-btn,
|
|
852
1989
|
.bw-footer-next,
|
|
853
1990
|
.bw-btn-primary {
|
|
@@ -859,36 +1996,105 @@
|
|
|
859
1996
|
background: var(--bw-primary);
|
|
860
1997
|
color: var(--bw-primary-text);
|
|
861
1998
|
cursor: pointer;
|
|
862
|
-
transition: opacity
|
|
1999
|
+
transition: opacity var(--bw-duration) var(--bw-ease),
|
|
2000
|
+
transform var(--bw-duration) var(--bw-ease),
|
|
2001
|
+
box-shadow var(--bw-duration) var(--bw-ease);
|
|
863
2002
|
}
|
|
864
2003
|
|
|
865
2004
|
.bw-confirm-btn {
|
|
866
2005
|
width: 100%;
|
|
867
|
-
height:
|
|
868
|
-
border-radius: var(--bw-radius);
|
|
2006
|
+
height: 52px;
|
|
2007
|
+
border-radius: var(--bw-radius-pill);
|
|
869
2008
|
font-size: 15px;
|
|
870
2009
|
font-weight: 500;
|
|
2010
|
+
letter-spacing: -0.005em;
|
|
871
2011
|
}
|
|
872
2012
|
|
|
873
2013
|
.bw-confirm-btn:hover:not(:disabled),
|
|
874
2014
|
.bw-footer-next:hover:not(:disabled),
|
|
875
2015
|
.bw-btn-primary:hover {
|
|
876
|
-
opacity: 0.
|
|
2016
|
+
opacity: 0.92;
|
|
2017
|
+
box-shadow: var(--bw-shadow-lift);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
.bw-confirm-btn:active:not(:disabled),
|
|
2021
|
+
.bw-footer-next:active:not(:disabled),
|
|
2022
|
+
.bw-btn-primary:active {
|
|
2023
|
+
transform: scale(0.99);
|
|
877
2024
|
}
|
|
878
2025
|
|
|
879
2026
|
.bw-confirm-btn:disabled,
|
|
880
|
-
.bw-
|
|
2027
|
+
.bw-confirm-btn.is-disabled,
|
|
2028
|
+
.bw-footer-next:disabled,
|
|
2029
|
+
.bw-footer-next.is-disabled {
|
|
881
2030
|
opacity: 0.35;
|
|
882
2031
|
cursor: default;
|
|
883
2032
|
}
|
|
884
2033
|
|
|
885
2034
|
.bw-done {
|
|
886
2035
|
display: flex;
|
|
2036
|
+
flex: 1;
|
|
887
2037
|
flex-direction: column;
|
|
888
2038
|
align-items: center;
|
|
2039
|
+
justify-content: center;
|
|
889
2040
|
gap: 16px;
|
|
2041
|
+
min-height: 80svh;
|
|
890
2042
|
padding: 80px 20px;
|
|
891
2043
|
text-align: center;
|
|
2044
|
+
animation: bw-done-fade-in 480ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
.bw-done-icon,
|
|
2048
|
+
.bw-done-title,
|
|
2049
|
+
.bw-done-text,
|
|
2050
|
+
.bw-done > .bw-btn-primary {
|
|
2051
|
+
animation: bw-done-stagger-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
.bw-done-icon {
|
|
2055
|
+
animation-delay: 80ms;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
.bw-done-title {
|
|
2059
|
+
animation-delay: 180ms;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
.bw-done-text {
|
|
2063
|
+
animation-delay: 260ms;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
.bw-done > .bw-btn-primary {
|
|
2067
|
+
animation-delay: 360ms;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
@keyframes bw-done-fade-in {
|
|
2071
|
+
from {
|
|
2072
|
+
opacity: 0;
|
|
2073
|
+
}
|
|
2074
|
+
to {
|
|
2075
|
+
opacity: 1;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
@keyframes bw-done-stagger-in {
|
|
2080
|
+
from {
|
|
2081
|
+
opacity: 0;
|
|
2082
|
+
transform: translateY(8px) scale(0.985);
|
|
2083
|
+
}
|
|
2084
|
+
to {
|
|
2085
|
+
opacity: 1;
|
|
2086
|
+
transform: translateY(0) scale(1);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2091
|
+
.bw-done,
|
|
2092
|
+
.bw-done-icon,
|
|
2093
|
+
.bw-done-title,
|
|
2094
|
+
.bw-done-text,
|
|
2095
|
+
.bw-done > .bw-btn-primary {
|
|
2096
|
+
animation: none;
|
|
2097
|
+
}
|
|
892
2098
|
}
|
|
893
2099
|
|
|
894
2100
|
.bw-done-icon {
|
|
@@ -903,6 +2109,24 @@
|
|
|
903
2109
|
color: var(--bw-primary-text);
|
|
904
2110
|
}
|
|
905
2111
|
|
|
2112
|
+
/* Cancelled-state disc: neutral background instead of the celebratory
|
|
2113
|
+
primary fill. Tone is informational, not affirmative — we don't
|
|
2114
|
+
want to "celebrate" a cancellation with a brand-color check badge. */
|
|
2115
|
+
.bw-done-icon--muted {
|
|
2116
|
+
background: var(--bw-border-light);
|
|
2117
|
+
color: var(--bw-text-secondary);
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* Override the shared 16px sizing for icons rendered inside chrome
|
|
2121
|
+
(.bw-done-icon, .bw-staff-avatar, etc.) — the success view's check
|
|
2122
|
+
reads small inside the 56px disc, so bump the glyph to 24px so it
|
|
2123
|
+
anchors the moment cleanly. */
|
|
2124
|
+
.bw-done-icon svg {
|
|
2125
|
+
width: 24px;
|
|
2126
|
+
height: 24px;
|
|
2127
|
+
stroke-width: 2;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
906
2130
|
.bw-done-title {
|
|
907
2131
|
margin: 0;
|
|
908
2132
|
font-size: 28px;
|
|
@@ -927,16 +2151,49 @@
|
|
|
927
2151
|
}
|
|
928
2152
|
|
|
929
2153
|
@media (max-width: 1024px) {
|
|
2154
|
+
/* Lock the frame to a single height across all steps so the widget
|
|
2155
|
+
doesn't bounce as the user moves between services / date / etc.
|
|
2156
|
+
Sized to fit the tallest step (date + time) with a little
|
|
2157
|
+
breathing room above and below. Body scrolls internally when a
|
|
2158
|
+
step exceeds it. */
|
|
930
2159
|
.bw {
|
|
931
|
-
|
|
2160
|
+
/* svh (small viewport height) doesn't jump when iOS Safari's URL
|
|
2161
|
+
bar toggles, unlike vh which uses the largest possible height
|
|
2162
|
+
and overflows when bars become visible. */
|
|
2163
|
+
min-height: 82svh;
|
|
2164
|
+
max-height: 82svh;
|
|
932
2165
|
}
|
|
933
2166
|
|
|
934
|
-
|
|
935
|
-
|
|
2167
|
+
/* Lock header/footer in the column so they never shrink under
|
|
2168
|
+
content pressure. Flex children default to flex-shrink: 1, which
|
|
2169
|
+
would let a tall body squeeze the title/buttons. Force them to
|
|
2170
|
+
size to their content and stay put — the body in between is the
|
|
2171
|
+
only thing that scrolls. */
|
|
2172
|
+
.bw-mobile-header,
|
|
2173
|
+
.bw-header,
|
|
2174
|
+
.bw-footer {
|
|
2175
|
+
flex: 0 0 auto;
|
|
936
2176
|
}
|
|
937
2177
|
|
|
938
|
-
.bw-
|
|
939
|
-
|
|
2178
|
+
.bw-body {
|
|
2179
|
+
overflow-y: auto;
|
|
2180
|
+
-webkit-overflow-scrolling: touch;
|
|
2181
|
+
overscroll-behavior: contain;
|
|
2182
|
+
/* Top fade only; the bottom taper is rendered as a gradient
|
|
2183
|
+
overlay above .bw-footer so content fades into the footer
|
|
2184
|
+
edge, not into the body frame's own bottom. */
|
|
2185
|
+
-webkit-mask-image: linear-gradient(
|
|
2186
|
+
to bottom,
|
|
2187
|
+
transparent 0,
|
|
2188
|
+
#000 24px,
|
|
2189
|
+
#000 100%
|
|
2190
|
+
);
|
|
2191
|
+
mask-image: linear-gradient(
|
|
2192
|
+
to bottom,
|
|
2193
|
+
transparent 0,
|
|
2194
|
+
#000 24px,
|
|
2195
|
+
#000 100%
|
|
2196
|
+
);
|
|
940
2197
|
}
|
|
941
2198
|
|
|
942
2199
|
.bw-mobile-header {
|
|
@@ -953,11 +2210,12 @@
|
|
|
953
2210
|
|
|
954
2211
|
.bw-header {
|
|
955
2212
|
padding: 20px 24px 20px;
|
|
2213
|
+
border-bottom: 1px solid var(--bw-border-light);
|
|
956
2214
|
}
|
|
957
2215
|
|
|
958
2216
|
.bw-header-row {
|
|
959
2217
|
display: flex;
|
|
960
|
-
align-items:
|
|
2218
|
+
align-items: flex-start;
|
|
961
2219
|
justify-content: space-between;
|
|
962
2220
|
gap: 16px;
|
|
963
2221
|
}
|
|
@@ -969,15 +2227,21 @@
|
|
|
969
2227
|
justify-content: flex-end;
|
|
970
2228
|
gap: 8px;
|
|
971
2229
|
width: 116px;
|
|
2230
|
+
/* Align dots with the title's first cap-height when the title
|
|
2231
|
+
wraps to multiple lines (e.g. "Reschedule Eric Lan's
|
|
2232
|
+
appointment"). Without this the dots would sit on the row's
|
|
2233
|
+
top edge above the title's optical line. */
|
|
2234
|
+
margin-top: 10px;
|
|
972
2235
|
}
|
|
973
2236
|
|
|
974
2237
|
.bw-dot {
|
|
975
2238
|
flex: 1 1 0;
|
|
976
2239
|
width: auto;
|
|
977
2240
|
min-width: 20px;
|
|
978
|
-
max-width:
|
|
979
|
-
|
|
980
|
-
|
|
2241
|
+
max-width: 32px;
|
|
2242
|
+
/* Bumped from 3px to 4px for a stronger active-step signal. */
|
|
2243
|
+
height: 4px;
|
|
2244
|
+
border-radius: 2px;
|
|
981
2245
|
background: var(--bw-border);
|
|
982
2246
|
transition: background 200ms ease;
|
|
983
2247
|
}
|
|
@@ -987,7 +2251,9 @@
|
|
|
987
2251
|
}
|
|
988
2252
|
|
|
989
2253
|
.bw-title {
|
|
990
|
-
|
|
2254
|
+
/* iOS Title 1 = 28pt. Was 24px (no-man's land between Title 1
|
|
2255
|
+
and Title 2). 28px gives better scale separation from body. */
|
|
2256
|
+
font-size: 28px;
|
|
991
2257
|
letter-spacing: -0.5px;
|
|
992
2258
|
}
|
|
993
2259
|
|
|
@@ -1011,50 +2277,56 @@
|
|
|
1011
2277
|
flex-direction: column;
|
|
1012
2278
|
flex: 1;
|
|
1013
2279
|
min-height: 0;
|
|
1014
|
-
padding: 0 24px;
|
|
2280
|
+
padding: 0 24px 24px;
|
|
1015
2281
|
}
|
|
1016
2282
|
|
|
1017
2283
|
.bw-col {
|
|
1018
2284
|
display: none;
|
|
1019
2285
|
}
|
|
1020
2286
|
|
|
2287
|
+
/* Mobile 4-step flow:
|
|
2288
|
+
Step 1: services (bw-col--left, bw-step-2 only)
|
|
2289
|
+
Step 2: calendar + provider chips + slots (bw-col--center)
|
|
2290
|
+
Step 3: form (bw-col--right, form fields visible)
|
|
2291
|
+
Step 4: review (bw-col--review with the full summary card,
|
|
2292
|
+
scrollable; footer only carries back + confirm buttons).
|
|
2293
|
+
Provider lives inside bw-cal-card on mobile so the visual
|
|
2294
|
+
order reads date → provider → time. The desktop dropdown in
|
|
2295
|
+
bw-step-1 is hidden on mobile (we use chips here instead). */
|
|
1021
2296
|
.bw-body[data-mobile-step="1"] .bw-col--left,
|
|
1022
|
-
.bw-body[data-mobile-step="2"] .bw-col--
|
|
1023
|
-
.bw-body[data-mobile-step="3"] .bw-col--
|
|
1024
|
-
.bw-body[data-mobile-step="4"] .bw-col--
|
|
2297
|
+
.bw-body[data-mobile-step="2"] .bw-col--center,
|
|
2298
|
+
.bw-body[data-mobile-step="3"] .bw-col--right,
|
|
2299
|
+
.bw-body[data-mobile-step="4"] .bw-col--review {
|
|
1025
2300
|
display: flex;
|
|
2301
|
+
padding-top: 16px;
|
|
2302
|
+
padding-bottom: 24px;
|
|
1026
2303
|
}
|
|
1027
2304
|
|
|
1028
|
-
.bw-body[data-mobile-step="
|
|
2305
|
+
.bw-body[data-mobile-step="3"] .bw-right-inner {
|
|
1029
2306
|
padding-bottom: 20px;
|
|
1030
2307
|
}
|
|
1031
2308
|
|
|
2309
|
+
/* Step 1: hide the desktop service-picker (inside bw-step-1) and
|
|
2310
|
+
hide the entire bw-step-1 wrapper. Show bw-step-2 (full-width
|
|
2311
|
+
services list). */
|
|
1032
2312
|
.bw-body[data-mobile-step="1"] .bw-step-1 {
|
|
1033
|
-
display: flex;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
.bw-body[data-mobile-step="1"] .bw-step-2 {
|
|
1037
|
-
display: none;
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
.bw-body[data-mobile-step="1"] .bw-service-picker {
|
|
1041
2313
|
display: none;
|
|
1042
2314
|
}
|
|
1043
2315
|
|
|
1044
|
-
.bw-body[data-mobile-step="
|
|
1045
|
-
display: none;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
.bw-body[data-mobile-step="2"] .bw-step-2 {
|
|
2316
|
+
.bw-body[data-mobile-step="1"] .bw-step-2 {
|
|
1049
2317
|
display: flex;
|
|
1050
2318
|
flex-direction: column;
|
|
1051
2319
|
}
|
|
1052
2320
|
|
|
1053
|
-
.bw-body[data-mobile-step="
|
|
1054
|
-
.bw-body[data-mobile-step="
|
|
2321
|
+
.bw-body[data-mobile-step="1"] .bw-step-2-heading,
|
|
2322
|
+
.bw-body[data-mobile-step="1"] .bw-step-2-divider {
|
|
1055
2323
|
display: none;
|
|
1056
2324
|
}
|
|
1057
2325
|
|
|
2326
|
+
/* Desktop dropdown lives in bw-col--left's bw-step-1; we don't show
|
|
2327
|
+
bw-col--left at all on mobile step 2, so this is implicitly
|
|
2328
|
+
hidden. The chip strip below handles provider selection. */
|
|
2329
|
+
|
|
1058
2330
|
.bw-svc-scroll {
|
|
1059
2331
|
display: none;
|
|
1060
2332
|
}
|
|
@@ -1078,31 +2350,105 @@
|
|
|
1078
2350
|
margin: 4px 0;
|
|
1079
2351
|
}
|
|
1080
2352
|
|
|
1081
|
-
.
|
|
1082
|
-
|
|
1083
|
-
|
|
2353
|
+
/* Mobile typography bumps. Per productdesign.md typography scale
|
|
2354
|
+
mobile column: list-row primary reads as body-s (13px) at the
|
|
2355
|
+
smallest, body-m (15px) for drawer-style titles, caption-l (12px)
|
|
2356
|
+
for list-row secondary. The widget is essentially a one-screen
|
|
2357
|
+
booking surface, so primary read targets get a one-step bump on
|
|
2358
|
+
mobile so they stay accessible at arm's length. */
|
|
2359
|
+
.bw-svc-name,
|
|
2360
|
+
.bw-svc-row--full .bw-svc-name,
|
|
2361
|
+
.bw-svc-price {
|
|
2362
|
+
font-size: 15px;
|
|
1084
2363
|
}
|
|
1085
2364
|
|
|
1086
|
-
.bw-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
padding: 16px 24px 34px;
|
|
1092
|
-
border-top: 1px solid var(--bw-border-light);
|
|
2365
|
+
.bw-svc-meta,
|
|
2366
|
+
.bw-svc-row--full .bw-svc-meta,
|
|
2367
|
+
.bw-svc-desc {
|
|
2368
|
+
font-size: 15px;
|
|
2369
|
+
line-height: 1.5;
|
|
1093
2370
|
}
|
|
1094
2371
|
|
|
1095
|
-
.bw-
|
|
1096
|
-
|
|
2372
|
+
.bw-svc-category {
|
|
2373
|
+
font-size: 14px;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
.bw-staff-trigger-name,
|
|
2377
|
+
.bw-staff-option-name {
|
|
2378
|
+
font-size: 15px;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.bw-staff-trigger-desc,
|
|
2382
|
+
.bw-staff-option-desc {
|
|
2383
|
+
font-size: 12px;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.bw-staff-initials {
|
|
2387
|
+
font-size: 13px;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/* Mobile thumbnails: bump both the inline (left col) and the
|
|
2391
|
+
full-width (mobile step 1) variants up one notch. The mobile
|
|
2392
|
+
full-width row has more horizontal breathing room and the
|
|
2393
|
+
thumbnail gets to anchor the row visually. */
|
|
2394
|
+
.bw-svc-image {
|
|
2395
|
+
width: 88px;
|
|
2396
|
+
height: 88px;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
.bw-svc-image--lg {
|
|
2400
|
+
width: 104px;
|
|
2401
|
+
height: 104px;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
.bw-svc-image-badge {
|
|
2405
|
+
width: 20px;
|
|
2406
|
+
height: 20px;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.bw-svc-image-badge svg {
|
|
2410
|
+
width: 12px;
|
|
2411
|
+
height: 12px;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
.bw-form .bw-summary,
|
|
2415
|
+
.bw-confirm-btn {
|
|
2416
|
+
display: none;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
.bw-footer {
|
|
2420
|
+
position: relative;
|
|
2421
|
+
display: flex;
|
|
1097
2422
|
flex-direction: column;
|
|
1098
|
-
gap:
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
2423
|
+
gap: 20px;
|
|
2424
|
+
/* .bw-footer is a sibling of .bw-content, not .bw-body, so the
|
|
2425
|
+
frame-to-frame mobile gap has to live on the footer flex item.
|
|
2426
|
+
Bottom padding uses env(safe-area-inset-bottom) so notched iOS
|
|
2427
|
+
devices clear the home indicator naturally, while Touch ID
|
|
2428
|
+
devices + Android don't waste 34px of space. Floors at 24px so
|
|
2429
|
+
there's always real breathing room below the Confirm button.
|
|
2430
|
+
Host site MUST include `viewport-fit=cover` in its viewport
|
|
2431
|
+
meta for env(safe-area-inset-*) to be non-zero on iOS. */
|
|
2432
|
+
margin-top: 16px;
|
|
2433
|
+
padding: 16px 24px
|
|
2434
|
+
max(24px, calc(env(safe-area-inset-bottom) + 16px));
|
|
2435
|
+
border-top: 1px solid var(--bw-border-light);
|
|
1102
2436
|
}
|
|
1103
2437
|
|
|
1104
|
-
|
|
1105
|
-
|
|
2438
|
+
/* Gradient overlay sits above the footer (in the 16px gap and a
|
|
2439
|
+
touch into the body's frame above it) so scrolling content fades
|
|
2440
|
+
into the footer edge instead of slicing or tapering inside the
|
|
2441
|
+
body's own frame. */
|
|
2442
|
+
.bw-footer::before {
|
|
2443
|
+
content: '';
|
|
2444
|
+
position: absolute;
|
|
2445
|
+
inset: -32px 0 100%;
|
|
2446
|
+
pointer-events: none;
|
|
2447
|
+
background: linear-gradient(
|
|
2448
|
+
to bottom,
|
|
2449
|
+
rgba(255, 255, 255, 0) 0%,
|
|
2450
|
+
var(--bw-bg) 100%
|
|
2451
|
+
);
|
|
1106
2452
|
}
|
|
1107
2453
|
|
|
1108
2454
|
.bw-footer-btns {
|
|
@@ -1132,6 +2478,25 @@
|
|
|
1132
2478
|
font-size: 15px;
|
|
1133
2479
|
font-weight: 500;
|
|
1134
2480
|
}
|
|
2481
|
+
|
|
2482
|
+
.bw-footer-payment-slot {
|
|
2483
|
+
display: flex;
|
|
2484
|
+
flex: 1;
|
|
2485
|
+
min-width: 0;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.bw-footer-payment-slot:empty {
|
|
2489
|
+
display: none;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
.bw-footer-payment-slot .bw-confirm-btn {
|
|
2493
|
+
display: flex;
|
|
2494
|
+
flex: 1;
|
|
2495
|
+
height: 48px;
|
|
2496
|
+
border-radius: var(--bw-radius-lg);
|
|
2497
|
+
font-size: 15px;
|
|
2498
|
+
font-weight: 500;
|
|
2499
|
+
}
|
|
1135
2500
|
}
|
|
1136
2501
|
|
|
1137
2502
|
@media (max-width: 480px) {
|
|
@@ -1144,7 +2509,7 @@
|
|
|
1144
2509
|
gap: 6px;
|
|
1145
2510
|
}
|
|
1146
2511
|
|
|
1147
|
-
.bw-body[data-mobile-step="
|
|
2512
|
+
.bw-body[data-mobile-step="3"] .bw-right-inner {
|
|
1148
2513
|
padding-bottom: 16px;
|
|
1149
2514
|
}
|
|
1150
2515
|
|
|
@@ -1153,7 +2518,7 @@
|
|
|
1153
2518
|
}
|
|
1154
2519
|
|
|
1155
2520
|
.bw-body {
|
|
1156
|
-
padding: 0 20px;
|
|
2521
|
+
padding: 0 20px 24px;
|
|
1157
2522
|
}
|
|
1158
2523
|
|
|
1159
2524
|
.bw-footer {
|
|
@@ -1180,16 +2545,633 @@
|
|
|
1180
2545
|
|
|
1181
2546
|
.bw-slot {
|
|
1182
2547
|
min-height: 40px;
|
|
1183
|
-
padding: 12px
|
|
2548
|
+
padding: 12px 12px;
|
|
1184
2549
|
font-size: 12px;
|
|
1185
2550
|
}
|
|
1186
2551
|
|
|
1187
2552
|
.bw-field input,
|
|
1188
2553
|
.bw-field textarea {
|
|
1189
|
-
|
|
2554
|
+
/* 16px on mobile prevents iOS Safari's auto-zoom-on-focus,
|
|
2555
|
+
which otherwise leaves the form stuck zoomed-in mid-flow. */
|
|
2556
|
+
font-size: 16px;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.bw-field label {
|
|
2560
|
+
/* Bumped from 12px to 13px so labels read clearly without
|
|
2561
|
+
crowding the 16px input below them. */
|
|
2562
|
+
font-size: 13px;
|
|
1190
2563
|
}
|
|
1191
2564
|
|
|
1192
2565
|
.bw-summary-row {
|
|
2566
|
+
font-size: 13px;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
.bw-slot {
|
|
2570
|
+
/* Time pills are a primary tap target — bump from 13px
|
|
2571
|
+
(footnote-tier) to 15px (subheadline). 3-col mobile grid
|
|
2572
|
+
still fits "10:30 a. m." comfortably at 15px. */
|
|
2573
|
+
font-size: 15px;
|
|
2574
|
+
padding: 13px 12px;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
.bw-cal-weekdays span {
|
|
2578
|
+
/* Day-of-week header bumped from 12px to 13px for readability. */
|
|
2579
|
+
font-size: 13px;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
/* ===========================================================================
|
|
2584
|
+
Desktop-only right-pane swap (slots → details) and sizing fixes.
|
|
2585
|
+
Mobile is intentionally untouched here — the pane wrappers exist in the
|
|
2586
|
+
DOM on mobile but receive no styling, so they collapse into transparent
|
|
2587
|
+
block containers and the form / cal-card / step-1 keep their original
|
|
2588
|
+
mobile flex behavior from commit 6ca438f.
|
|
2589
|
+
=========================================================================== */
|
|
2590
|
+
|
|
2591
|
+
/* Slot pill structural styling that's safe on both breakpoints because
|
|
2592
|
+
the .bw-slots-desktop wrapper is hidden on mobile (display: none below)
|
|
2593
|
+
and the .bw-slots-mobile wrapper is the only path that renders the
|
|
2594
|
+
inline 4-up grid on mobile via display: contents. */
|
|
2595
|
+
.bw-slots-mobile {
|
|
2596
|
+
display: none;
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
.bw-slots-heading {
|
|
2600
|
+
display: flex;
|
|
2601
|
+
align-items: baseline;
|
|
2602
|
+
justify-content: space-between;
|
|
2603
|
+
margin-bottom: 14px;
|
|
2604
|
+
font-size: 14px;
|
|
2605
|
+
font-weight: 500;
|
|
2606
|
+
color: var(--bw-text);
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
.bw-slots-count {
|
|
2610
|
+
font-size: 12px;
|
|
2611
|
+
color: var(--bw-text-muted);
|
|
2612
|
+
font-variant-numeric: tabular-nums;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
.bw-back-btn {
|
|
2616
|
+
display: inline-flex;
|
|
2617
|
+
align-items: center;
|
|
2618
|
+
gap: 6px;
|
|
2619
|
+
margin-bottom: 16px;
|
|
2620
|
+
padding: 6px 0;
|
|
2621
|
+
background: transparent;
|
|
2622
|
+
border: 0;
|
|
2623
|
+
font-size: 12px;
|
|
2624
|
+
color: var(--bw-text-muted);
|
|
2625
|
+
cursor: pointer;
|
|
2626
|
+
transition: color var(--bw-duration) var(--bw-ease);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
.bw-back-btn:hover {
|
|
2630
|
+
color: var(--bw-text);
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.bw-back-btn svg {
|
|
2634
|
+
width: 14px;
|
|
2635
|
+
height: 14px;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
@media (min-width: 1025px) {
|
|
2639
|
+
/* Lock the desktop frame to a stable height so it doesn't snap to a
|
|
2640
|
+
shorter fit-content size when the widget swaps into the success
|
|
2641
|
+
view (.bw-done has less content than the normal flow). Picked to
|
|
2642
|
+
comfortably fit the calendar + slots + form layout. */
|
|
2643
|
+
.bw {
|
|
2644
|
+
min-height: 720px;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
/* Desktop body grid: cols sit at the top of their cell instead of
|
|
2648
|
+
stretching to the row's max content height — the visual empty
|
|
2649
|
+
space below the cal-card and right-pane goes away. */
|
|
2650
|
+
.bw-body {
|
|
2651
|
+
align-items: start;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
/* Desktop cal-card hugs its grid + timezone instead of stretching
|
|
2655
|
+
to fill the row, and the timezone sits flush below the day grid
|
|
2656
|
+
(no auto margin pushing it down). */
|
|
2657
|
+
.bw-cal-card {
|
|
2658
|
+
flex: 0 0 auto;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
.bw-timezone {
|
|
2662
|
+
margin-top: 0;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/* Slots column on desktop: vertical pill list above the form, not
|
|
2666
|
+
a 4-up grid below the calendar. */
|
|
2667
|
+
.bw-slots-desktop .bw-time-slots {
|
|
2668
|
+
grid-template-columns: 1fr;
|
|
2669
|
+
gap: 8px;
|
|
2670
|
+
margin-top: 0;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
.bw-slots-desktop .bw-slot {
|
|
2674
|
+
padding: 14px 16px;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
/* Match the skeleton pill dimensions to the real desktop pill so
|
|
2678
|
+
the load → loaded swap doesn't reflow the column. Real pill =
|
|
2679
|
+
14px padding + 13px font + line-height ≈ 50px tall. */
|
|
2680
|
+
.bw-slots-desktop .bw-skel--slot {
|
|
2681
|
+
height: 50px;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/* --bw-cal-h is set on the .bw root via ResizeObserver in the
|
|
2685
|
+
widget — it tracks the live height of the .bw-cal-card in the
|
|
2686
|
+
middle column. Use it to cap the services list (left) and the
|
|
2687
|
+
slots list (right) so all three columns share the same visual
|
|
2688
|
+
height. Both internal scrollers fade their top + bottom edges
|
|
2689
|
+
so cut-off content reads as scrollable, not clipped. */
|
|
2690
|
+
.bw-svc-scroll-wrap {
|
|
2691
|
+
max-height: var(--bw-cal-h, 480px);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
.bw-pane--slots,
|
|
2695
|
+
.bw-pane--details {
|
|
2696
|
+
/* Lock both panes in the body's right column to the same max
|
|
2697
|
+
height as the calendar so the body row's intrinsic height
|
|
2698
|
+
doesn't change between viewState 'slots' and 'details'. Without
|
|
2699
|
+
this, the form pane grows taller than the slots pane and
|
|
2700
|
+
.bw-content's grid-stack cell jumps in height on step 2. */
|
|
2701
|
+
max-height: var(--bw-cal-h, 480px);
|
|
2702
|
+
overflow: hidden;
|
|
2703
|
+
display: flex;
|
|
2704
|
+
flex-direction: column;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
/* Sticky "Available times" heading on desktop. Lives as a sibling
|
|
2708
|
+
of .bw-slots-desktop (the scroller) so it doesn't scroll with
|
|
2709
|
+
the time pills. Sits flush at the top of the right column. */
|
|
2710
|
+
.bw-pane--slots .bw-slots-heading--sticky {
|
|
2711
|
+
flex: 0 0 auto;
|
|
2712
|
+
margin-bottom: 4px;
|
|
2713
|
+
padding-bottom: 8px;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
.bw-pane--slots .bw-slots-desktop {
|
|
2717
|
+
flex: 1;
|
|
2718
|
+
min-height: 0;
|
|
2719
|
+
overflow-y: auto;
|
|
2720
|
+
-webkit-mask-image: linear-gradient(
|
|
2721
|
+
to bottom,
|
|
2722
|
+
transparent 0,
|
|
2723
|
+
#000 18px,
|
|
2724
|
+
#000 calc(100% - 18px),
|
|
2725
|
+
transparent 100%
|
|
2726
|
+
);
|
|
2727
|
+
mask-image: linear-gradient(
|
|
2728
|
+
to bottom,
|
|
2729
|
+
transparent 0,
|
|
2730
|
+
#000 18px,
|
|
2731
|
+
#000 calc(100% - 18px),
|
|
2732
|
+
transparent 100%
|
|
2733
|
+
);
|
|
2734
|
+
scrollbar-width: thin;
|
|
2735
|
+
scrollbar-color: var(--bw-border) transparent;
|
|
2736
|
+
/* Reserve space for the scrollbar so it never overlays the
|
|
2737
|
+
pills' right edge. Combined with the right padding below this
|
|
2738
|
+
gives the bar its own lane regardless of whether content
|
|
2739
|
+
overflows. */
|
|
2740
|
+
scrollbar-gutter: stable;
|
|
2741
|
+
/* Padding so first/last pills sit inside the fade zone, matching
|
|
2742
|
+
the services scroller pattern. Right padding gives the pills
|
|
2743
|
+
breathing room from the scrollbar lane. */
|
|
2744
|
+
padding-block: 12px 16px;
|
|
2745
|
+
padding-right: 6px;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
.bw-pane--slots .bw-slots-desktop::-webkit-scrollbar {
|
|
2749
|
+
width: 6px;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
.bw-pane--slots .bw-slots-desktop::-webkit-scrollbar-thumb {
|
|
2753
|
+
border-radius: var(--bw-radius-pill);
|
|
2754
|
+
background: var(--bw-border);
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
.bw-pane--slots .bw-slots-desktop::-webkit-scrollbar-thumb:hover {
|
|
2758
|
+
background: var(--bw-text-muted);
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
/* Right column two-step stage: slots pane → (crossfade) → details
|
|
2762
|
+
pane. Both panes share the stage area; the hidden one is taken
|
|
2763
|
+
out of normal flow so the column sizes only to the active pane.
|
|
2764
|
+
Animation timing matches Sable's productdesign popup rule:
|
|
2765
|
+
250ms expo ease-out on enter, 150ms standard ease-out on exit. */
|
|
2766
|
+
.bw-right-stage {
|
|
2767
|
+
position: relative;
|
|
2768
|
+
display: flex;
|
|
2769
|
+
flex-direction: column;
|
|
2770
|
+
min-height: 0;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
.bw-pane {
|
|
2774
|
+
transition:
|
|
2775
|
+
opacity 250ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
2776
|
+
transform 250ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2777
|
+
will-change: opacity, transform;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
.bw-pane.is-active {
|
|
2781
|
+
position: relative;
|
|
2782
|
+
opacity: 1;
|
|
2783
|
+
transform: scale(1);
|
|
2784
|
+
pointer-events: auto;
|
|
2785
|
+
z-index: 1;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
.bw-pane.is-hidden {
|
|
2789
|
+
position: absolute;
|
|
2790
|
+
inset: 0;
|
|
2791
|
+
opacity: 0;
|
|
2792
|
+
transform: scale(0.97);
|
|
2793
|
+
pointer-events: none;
|
|
2794
|
+
transition-duration: 150ms;
|
|
2795
|
+
transition-timing-function: ease-out;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/* Mobile-only flips for the new pane wrappers: only enough to hide
|
|
2800
|
+
the desktop slots column copy and force the slots-mobile wrapper
|
|
2801
|
+
to render its children inline (the original 4-up grid below the
|
|
2802
|
+
calendar). The pane--details wrapper has no styling on mobile, so
|
|
2803
|
+
it's a transparent block container and the form renders exactly
|
|
2804
|
+
like it did pre-pane-restructure. */
|
|
2805
|
+
@media (max-width: 1024px) {
|
|
2806
|
+
.bw-pane--slots {
|
|
2807
|
+
display: none;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
.bw-back-btn {
|
|
2811
|
+
display: none;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
/* Slots-mobile is now its own card frame on step 2 (combined with
|
|
2815
|
+
.bw-mobile-card class). Override default display: none so the
|
|
2816
|
+
card chrome shows; .bw-mobile-card already sets flex column. */
|
|
2817
|
+
.bw-slots-mobile {
|
|
2818
|
+
display: flex;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
/* Details view is desktop-only; mobile uses the existing step-4
|
|
2822
|
+
right column for the form. */
|
|
2823
|
+
.bw-details-view {
|
|
2824
|
+
display: none;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
/* Mobile step transitions: when a step becomes active, the relevant
|
|
2828
|
+
content fades + slides up subtly. Same 250ms expo ease-out timing
|
|
2829
|
+
as desktop transitions so the product feels consistent across
|
|
2830
|
+
breakpoints. We target the step-specific content (not the col
|
|
2831
|
+
wrapper for steps 1/2 since col--left stays visible across
|
|
2832
|
+
them) so animations don't re-fire when the active col is shared
|
|
2833
|
+
between adjacent steps. */
|
|
2834
|
+
/* 4-step flow. Step 1 shares bw-col--left with no other step.
|
|
2835
|
+
Steps 3 + 4 both live in bw-col--right but differ in content,
|
|
2836
|
+
so we re-fire the enter animation on data-mobile-step changes
|
|
2837
|
+
between them. */
|
|
2838
|
+
.bw-body[data-mobile-step='1'] .bw-col--left,
|
|
2839
|
+
.bw-body[data-mobile-step='2'] .bw-col--center,
|
|
2840
|
+
.bw-body[data-mobile-step='3'] .bw-col--right,
|
|
2841
|
+
.bw-body[data-mobile-step='4'] .bw-col--review {
|
|
2842
|
+
animation: bw-mobile-step-in 250ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
@keyframes bw-mobile-step-in {
|
|
2846
|
+
from {
|
|
2847
|
+
opacity: 0;
|
|
2848
|
+
transform: translateY(6px);
|
|
2849
|
+
}
|
|
2850
|
+
to {
|
|
2851
|
+
opacity: 1;
|
|
2852
|
+
transform: translateY(0);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
/* Desktop-only details view: 2-column summary | form layout shown
|
|
2858
|
+
when viewState === 'details'. Mobile keeps the step-based flow
|
|
2859
|
+
(bw-details-view is display: none on mobile, set above). */
|
|
2860
|
+
.bw-details-view {
|
|
2861
|
+
display: none;
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
/* The .bw-content wrapper holds bw-body and bw-details-view. On
|
|
2865
|
+
mobile it's a transparent flex column passthrough. On desktop it
|
|
2866
|
+
becomes a positioning context so the inactive view can overlay
|
|
2867
|
+
during the crossfade without contributing its height. */
|
|
2868
|
+
.bw-content {
|
|
2869
|
+
display: flex;
|
|
2870
|
+
flex-direction: column;
|
|
2871
|
+
flex: 1;
|
|
2872
|
+
min-height: 0;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
@media (min-width: 1025px) {
|
|
2876
|
+
/* Stack the two panes in the same grid cell so they share space,
|
|
2877
|
+
but keep the inactive pane out of normal flow. Otherwise the
|
|
2878
|
+
services/calendar step reserves the much taller details form
|
|
2879
|
+
before the user has selected a time. */
|
|
2880
|
+
.bw-content {
|
|
2881
|
+
position: relative;
|
|
2882
|
+
display: grid;
|
|
2883
|
+
grid-template-areas: 'stack';
|
|
2884
|
+
align-items: start;
|
|
2885
|
+
flex: 0 1 auto;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
.bw-body,
|
|
2889
|
+
.bw-details-view {
|
|
2890
|
+
grid-area: stack;
|
|
2891
|
+
transition:
|
|
2892
|
+
opacity 250ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
2893
|
+
transform 250ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2894
|
+
will-change: opacity, transform;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
.bw-body {
|
|
2898
|
+
position: relative;
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
.bw-details-view {
|
|
2902
|
+
position: absolute;
|
|
2903
|
+
inset: 0;
|
|
2904
|
+
display: grid;
|
|
2905
|
+
grid-template-columns: 5fr 6fr;
|
|
2906
|
+
gap: 56px;
|
|
2907
|
+
padding: 32px 48px 48px;
|
|
2908
|
+
max-width: 1440px;
|
|
2909
|
+
width: 100%;
|
|
2910
|
+
margin: 0 auto;
|
|
2911
|
+
/* Default hidden state — overlaid on top of body. */
|
|
2912
|
+
opacity: 0;
|
|
2913
|
+
transform: scale(0.99);
|
|
2914
|
+
pointer-events: none;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
.bw[data-view-state='details'] .bw-body {
|
|
2918
|
+
position: absolute;
|
|
2919
|
+
inset: 0;
|
|
2920
|
+
opacity: 0;
|
|
2921
|
+
transform: scale(0.99);
|
|
2922
|
+
pointer-events: none;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
.bw[data-view-state='details'] .bw-details-view {
|
|
2926
|
+
position: relative;
|
|
2927
|
+
inset: auto;
|
|
2928
|
+
opacity: 1;
|
|
2929
|
+
transform: scale(1);
|
|
2930
|
+
pointer-events: auto;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
.bw-details-summary {
|
|
2934
|
+
display: flex;
|
|
2935
|
+
flex-direction: column;
|
|
2936
|
+
gap: 16px;
|
|
2937
|
+
padding-right: 8px;
|
|
2938
|
+
/* Sticky so the booking summary stays alongside the form when
|
|
2939
|
+
it gets long (e.g. paid services with payment block, or many
|
|
2940
|
+
intake fields). `align-self: start` keeps the grid cell from
|
|
2941
|
+
stretching to row height, which would block sticky behavior.
|
|
2942
|
+
Host sites can override --bw-sticky-summary-top to clear a
|
|
2943
|
+
fixed top nav — encomiendas' nav is 72px so a host override
|
|
2944
|
+
to ~96px makes sense; the default 24px works for hosts with
|
|
2945
|
+
no fixed nav. */
|
|
2946
|
+
position: sticky;
|
|
2947
|
+
top: var(--bw-sticky-summary-top, 24px);
|
|
2948
|
+
align-self: start;
|
|
2949
|
+
/* Cap height so very-tall summaries (long service description,
|
|
2950
|
+
all reschedule rows visible) don't overflow the viewport;
|
|
2951
|
+
internal scroll if needed. */
|
|
2952
|
+
max-height: calc(100svh - var(--bw-sticky-summary-top, 24px) - 24px);
|
|
2953
|
+
overflow-y: auto;
|
|
2954
|
+
scrollbar-width: thin;
|
|
2955
|
+
scrollbar-color: var(--bw-border) transparent;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
.bw-details-eyebrow {
|
|
2959
|
+
margin: 0;
|
|
1193
2960
|
font-size: 12px;
|
|
2961
|
+
font-weight: 500;
|
|
2962
|
+
letter-spacing: 0.04em;
|
|
2963
|
+
color: var(--bw-text-muted);
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
.bw-details-service {
|
|
2967
|
+
margin: 0;
|
|
2968
|
+
font-size: 24px;
|
|
2969
|
+
font-weight: 500;
|
|
2970
|
+
letter-spacing: -0.4px;
|
|
2971
|
+
line-height: 1.2;
|
|
2972
|
+
color: var(--bw-text);
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
.bw-details-desc {
|
|
2976
|
+
margin: 0;
|
|
2977
|
+
font-size: 13px;
|
|
2978
|
+
line-height: 1.55;
|
|
2979
|
+
color: var(--bw-text-secondary);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
.bw-details-meta {
|
|
2983
|
+
display: flex;
|
|
2984
|
+
flex-direction: column;
|
|
2985
|
+
gap: 14px;
|
|
2986
|
+
margin-top: 8px;
|
|
2987
|
+
padding-top: 20px;
|
|
2988
|
+
border-top: 1px solid var(--bw-border-light);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
.bw-details-meta-row {
|
|
2992
|
+
display: flex;
|
|
2993
|
+
align-items: flex-start;
|
|
2994
|
+
gap: 12px;
|
|
2995
|
+
font-size: 13px;
|
|
2996
|
+
color: var(--bw-text);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
.bw-details-meta-icon {
|
|
3000
|
+
display: inline-flex;
|
|
3001
|
+
flex-shrink: 0;
|
|
3002
|
+
align-items: center;
|
|
3003
|
+
justify-content: center;
|
|
3004
|
+
width: 18px;
|
|
3005
|
+
height: 18px;
|
|
3006
|
+
color: var(--bw-text-muted);
|
|
1194
3007
|
}
|
|
3008
|
+
|
|
3009
|
+
.bw-details-meta-icon svg {
|
|
3010
|
+
width: 14px;
|
|
3011
|
+
height: 14px;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
.bw-details-meta-text {
|
|
3015
|
+
display: flex;
|
|
3016
|
+
flex-direction: column;
|
|
3017
|
+
gap: 2px;
|
|
3018
|
+
min-width: 0;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
.bw-details-meta-label {
|
|
3022
|
+
font-size: 11px;
|
|
3023
|
+
color: var(--bw-text-muted);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
.bw-details-meta-value {
|
|
3027
|
+
font-size: 13px;
|
|
3028
|
+
color: var(--bw-text);
|
|
3029
|
+
font-variant-numeric: tabular-nums;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
.bw-details-meta-row--strike .bw-details-meta-value {
|
|
3033
|
+
text-decoration: line-through;
|
|
3034
|
+
opacity: 0.55;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
.bw-details-hold {
|
|
3038
|
+
color: var(--bw-text);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.bw-details-form {
|
|
3042
|
+
display: flex;
|
|
3043
|
+
flex-direction: column;
|
|
3044
|
+
gap: 16px;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
/* Back link sits at the top of the summary pane, in the eyebrow
|
|
3048
|
+
position — replacing the previous static "Reschedule" / "Booking"
|
|
3049
|
+
label as the first thing the user reads on the left. The static
|
|
3050
|
+
eyebrow still renders just below as the section label for the
|
|
3051
|
+
summary stack. */
|
|
3052
|
+
.bw-details-summary .bw-back-btn--details {
|
|
3053
|
+
align-self: flex-start;
|
|
3054
|
+
margin-bottom: 16px;
|
|
3055
|
+
padding: 0;
|
|
3056
|
+
color: var(--bw-text-secondary);
|
|
3057
|
+
font-size: 14px;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
.bw-details-summary .bw-back-btn--details:hover {
|
|
3061
|
+
color: var(--bw-text);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
.bw-form--details {
|
|
3065
|
+
gap: 18px;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/* Confirm sits alone at the bottom-right of the form column. */
|
|
3069
|
+
.bw-form--details .bw-confirm-btn {
|
|
3070
|
+
align-self: flex-end;
|
|
3071
|
+
margin-top: 8px;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
.bw-intake {
|
|
3076
|
+
display: flex;
|
|
3077
|
+
flex-direction: column;
|
|
3078
|
+
gap: 14px;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
.bw-intake-section {
|
|
3082
|
+
display: flex;
|
|
3083
|
+
flex-direction: column;
|
|
3084
|
+
gap: 10px;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
.bw-intake-title {
|
|
3088
|
+
font-size: 12px;
|
|
3089
|
+
font-weight: 700;
|
|
3090
|
+
color: var(--bw-text);
|
|
3091
|
+
text-transform: uppercase;
|
|
3092
|
+
letter-spacing: 0.08em;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
.bw-field--wide {
|
|
3096
|
+
grid-column: 1 / -1;
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
.bw .bw-field select {
|
|
3100
|
+
width: 100%;
|
|
3101
|
+
min-height: 40px;
|
|
3102
|
+
border: 1px solid var(--bw-border);
|
|
3103
|
+
border-radius: 10px;
|
|
3104
|
+
background: var(--bw-bg);
|
|
3105
|
+
color: var(--bw-text);
|
|
3106
|
+
padding: 0 12px;
|
|
3107
|
+
font: inherit;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
.bw-help {
|
|
3111
|
+
margin: 4px 0 0;
|
|
3112
|
+
font-size: 12px;
|
|
3113
|
+
color: var(--bw-text-muted);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.bw-checkbox-field label {
|
|
3117
|
+
display: flex;
|
|
3118
|
+
align-items: flex-start;
|
|
3119
|
+
gap: 8px;
|
|
3120
|
+
font-size: 13px;
|
|
3121
|
+
line-height: 1.4;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
.bw-checkbox-field input {
|
|
3125
|
+
width: 16px;
|
|
3126
|
+
height: 16px;
|
|
3127
|
+
min-width: 16px;
|
|
3128
|
+
flex: 0 0 16px;
|
|
3129
|
+
margin-top: 1px;
|
|
3130
|
+
padding: 0;
|
|
3131
|
+
accent-color: var(--bw-primary);
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
.bw-repeatable {
|
|
3135
|
+
display: flex;
|
|
3136
|
+
flex-direction: column;
|
|
3137
|
+
gap: 10px;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
.bw-repeatable-item {
|
|
3141
|
+
display: flex;
|
|
3142
|
+
flex-direction: column;
|
|
3143
|
+
gap: 10px;
|
|
3144
|
+
border: 1px solid var(--bw-border);
|
|
3145
|
+
border-radius: 12px;
|
|
3146
|
+
padding: 12px;
|
|
3147
|
+
background: color-mix(in srgb, var(--bw-bg) 85%, transparent);
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
.bw-repeatable-head {
|
|
3151
|
+
display: flex;
|
|
3152
|
+
align-items: center;
|
|
3153
|
+
justify-content: space-between;
|
|
3154
|
+
gap: 10px;
|
|
3155
|
+
font-size: 13px;
|
|
3156
|
+
font-weight: 700;
|
|
3157
|
+
color: var(--bw-text);
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
.bw-link-btn,
|
|
3161
|
+
.bw-secondary-btn {
|
|
3162
|
+
border: 0;
|
|
3163
|
+
background: transparent;
|
|
3164
|
+
color: var(--bw-primary);
|
|
3165
|
+
font: inherit;
|
|
3166
|
+
font-size: 13px;
|
|
3167
|
+
font-weight: 700;
|
|
3168
|
+
cursor: pointer;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
.bw-secondary-btn {
|
|
3172
|
+
align-self: flex-start;
|
|
3173
|
+
border: 1px solid var(--bw-border);
|
|
3174
|
+
border-radius: 999px;
|
|
3175
|
+
padding: 8px 12px;
|
|
3176
|
+
background: var(--bw-bg);
|
|
1195
3177
|
}
|