@asksable/site-connector 0.1.6 → 0.2.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/dist/styles.css CHANGED
@@ -1,20 +1,35 @@
1
1
  .bw {
2
+ /* Theme tokens — overridable per host site (color + font). */
2
3
  --bw-font: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
3
4
  --bw-bg: #ffffff;
4
5
  --bw-text: #1a1a1a;
5
6
  --bw-text-secondary: #6b6b6b;
6
- --bw-text-muted: #999999;
7
+ /* Bumped from #999 (2.85:1, fails WCAG AA) to #737373 (~4.74:1,
8
+ passes AA for normal text). Still distinct from --bw-text-secondary
9
+ (#6b6b6b, 5.74:1) so the visual hierarchy holds. */
10
+ --bw-text-muted: #737373;
7
11
  --bw-border: #e8e8e8;
8
12
  --bw-border-light: #f0f0f0;
9
- --bw-radius: 8px;
10
- --bw-radius-lg: 12px;
11
13
  --bw-primary: #1a1a1a;
12
14
  --bw-primary-text: #ffffff;
13
15
  --bw-hover: #f8f8f8;
14
16
  --bw-error-bg: #fef2f2;
15
17
  --bw-error-text: #b91c1c;
16
- --bw-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
17
- --bw-shadow-badge: 0 1px 4px rgba(0, 0, 0, 0.12);
18
+ /* Structural tokens Sable design rhythm; tenants generally
19
+ don't override these. Mirrors the radius scale documented in
20
+ productdesign.md (cards 12/16, inputs 22, buttons 28). */
21
+ --bw-radius-xs: 4px;
22
+ --bw-radius-sm: 8px;
23
+ --bw-radius-md: 12px;
24
+ --bw-radius: 12px;
25
+ --bw-radius-lg: 16px;
26
+ --bw-radius-xl: 22px;
27
+ --bw-radius-pill: 9999px;
28
+ --bw-shadow: 0 1px 2px rgba(15, 15, 15, 0.04), 0 8px 24px -12px rgba(15, 15, 15, 0.08);
29
+ --bw-shadow-badge: 0 1px 4px rgba(15, 15, 15, 0.12);
30
+ --bw-shadow-lift: 0 1px 2px rgba(15, 15, 15, 0.06), 0 12px 32px -16px rgba(15, 15, 15, 0.12);
31
+ --bw-ease: cubic-bezier(0.16, 1, 0.3, 1);
32
+ --bw-duration: 220ms;
18
33
  position: relative;
19
34
  display: flex;
20
35
  flex-direction: column;
@@ -22,9 +37,20 @@
22
37
  background: var(--bw-bg);
23
38
  color: var(--bw-text);
24
39
  font-family: var(--bw-font);
40
+ font-feature-settings: "ss01", "cv11";
41
+ -webkit-font-smoothing: antialiased;
42
+ -moz-osx-font-smoothing: grayscale;
43
+ text-wrap: pretty;
25
44
  overflow: hidden;
26
45
  }
27
46
 
47
+ .bw h1,
48
+ .bw h2,
49
+ .bw h3,
50
+ .bw h4 {
51
+ text-wrap: balance;
52
+ }
53
+
28
54
  .bw,
29
55
  .bw * {
30
56
  box-sizing: border-box;
@@ -89,7 +115,11 @@
89
115
  }
90
116
 
91
117
  .bw-skel--slot {
92
- height: 40px;
118
+ /* Default mobile pill height: matches .bw-slot { padding: 12px 0;
119
+ font-size: 13px } ≈ 44px. Desktop overrides this below to match
120
+ the taller .bw-slots-desktop .bw-slot { padding: 14px 16px } pill. */
121
+ height: 44px;
122
+ border-radius: var(--bw-radius);
93
123
  }
94
124
 
95
125
  .bw-skel-list {
@@ -114,6 +144,176 @@
114
144
  padding: 0 24px 24px;
115
145
  }
116
146
 
147
+ /* === Initial-load skeleton chrome ===
148
+ Each skeleton primitive mirrors a real widget element so the
149
+ chrome doesn't reflow when actual content arrives. */
150
+
151
+ .bw-skel--label {
152
+ width: 110px;
153
+ height: 18px;
154
+ margin-bottom: 16px;
155
+ border-radius: var(--bw-radius-sm);
156
+ }
157
+
158
+ /* Service group spacing matches .bw-svc-group + .bw-svc-group rules */
159
+ .bw-skel-svc-group {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 14px;
163
+ }
164
+
165
+ .bw-skel-svc-group + .bw-skel-svc-group {
166
+ margin-top: 28px;
167
+ }
168
+
169
+ .bw-skel--svc-category {
170
+ width: 90px;
171
+ height: 14px;
172
+ border-radius: var(--bw-radius-sm);
173
+ }
174
+
175
+ /* Service row: 80x80 image + flex column with name/desc/meta lines.
176
+ Mirrors the rendered .bw-svc-row layout exactly. */
177
+ .bw-skel-svc-row {
178
+ display: flex;
179
+ align-items: flex-start;
180
+ gap: 16px;
181
+ padding: 8px 0;
182
+ }
183
+
184
+ .bw-skel--svc-image {
185
+ flex-shrink: 0;
186
+ width: 80px;
187
+ height: 80px;
188
+ border-radius: var(--bw-radius-md);
189
+ }
190
+
191
+ .bw-skel-svc-info {
192
+ display: flex;
193
+ flex: 1;
194
+ min-width: 0;
195
+ flex-direction: column;
196
+ gap: 8px;
197
+ padding-top: 2px;
198
+ }
199
+
200
+ .bw-skel--svc-name {
201
+ width: 70%;
202
+ height: 16px;
203
+ border-radius: var(--bw-radius-sm);
204
+ }
205
+
206
+ .bw-skel--svc-desc {
207
+ width: 100%;
208
+ height: 14px;
209
+ border-radius: var(--bw-radius-sm);
210
+ }
211
+
212
+ .bw-skel--svc-meta {
213
+ width: 50%;
214
+ height: 14px;
215
+ margin-top: 4px;
216
+ border-radius: var(--bw-radius-sm);
217
+ }
218
+
219
+ /* Calendar card: month header row, 7-col day-of-week strip, 6-row
220
+ day grid (42 cells), timezone chip below. */
221
+ .bw-skel-cal-card {
222
+ /* The real .bw-cal-card supplies its own padding/border/radius;
223
+ just lay the skeleton primitives inside it. */
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 12px;
227
+ }
228
+
229
+ .bw-skel-cal-header {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: space-between;
233
+ gap: 16px;
234
+ margin-bottom: 4px;
235
+ }
236
+
237
+ .bw-skel--cal-month {
238
+ width: 130px;
239
+ height: 22px;
240
+ border-radius: var(--bw-radius-sm);
241
+ }
242
+
243
+ .bw-skel-cal-navs {
244
+ display: flex;
245
+ gap: 8px;
246
+ }
247
+
248
+ .bw-skel--cal-nav {
249
+ width: 32px;
250
+ height: 32px;
251
+ border-radius: var(--bw-radius-pill);
252
+ }
253
+
254
+ .bw-skel-cal-dows {
255
+ display: grid;
256
+ grid-template-columns: repeat(7, 1fr);
257
+ gap: 4px;
258
+ }
259
+
260
+ .bw-skel--cal-dow {
261
+ height: 14px;
262
+ border-radius: var(--bw-radius-sm);
263
+ }
264
+
265
+ .bw-skel-cal-grid {
266
+ display: grid;
267
+ grid-template-columns: repeat(7, 1fr);
268
+ gap: 4px;
269
+ }
270
+
271
+ .bw-skel--cal-day {
272
+ height: 44px;
273
+ border-radius: var(--bw-radius-sm);
274
+ }
275
+
276
+ .bw-skel--cal-tz {
277
+ width: 140px;
278
+ height: 16px;
279
+ margin: 12px auto 0;
280
+ border-radius: var(--bw-radius-sm);
281
+ }
282
+
283
+ /* Slots column heading + pills. */
284
+ .bw-skel--slots-heading {
285
+ width: 60%;
286
+ height: 18px;
287
+ margin-bottom: 16px;
288
+ border-radius: var(--bw-radius-sm);
289
+ }
290
+
291
+ /* Skeleton body uses the same grid as the real .bw-body so column
292
+ widths match without reflow. */
293
+ .bw-skel-body {
294
+ display: grid;
295
+ grid-template-columns: 28% 1fr 22%;
296
+ gap: 32px;
297
+ padding: 32px 48px 48px;
298
+ align-items: start;
299
+ }
300
+
301
+ @media (max-width: 1024px) {
302
+ .bw-skel-body {
303
+ display: flex;
304
+ flex-direction: column;
305
+ padding: 16px 24px 24px;
306
+ gap: 24px;
307
+ }
308
+
309
+ /* On mobile only show the services column at first paint —
310
+ subsequent steps reveal as user advances. */
311
+ .bw-skel-body .bw-col--center,
312
+ .bw-skel-body .bw-col--right {
313
+ display: none;
314
+ }
315
+ }
316
+
117
317
  .bw-loading,
118
318
  .bw-empty {
119
319
  display: flex;
@@ -174,7 +374,11 @@
174
374
 
175
375
  .bw-body {
176
376
  display: grid;
177
- grid-template-columns: 22% 1fr 22%;
377
+ /* Left col widened from 22% to 28% so the service rows have room
378
+ to render the duration + description on their secondary line
379
+ without crunching. The middle (calendar) absorbs the difference;
380
+ the right (slots/details) stays at 22%. */
381
+ grid-template-columns: 28% 1fr 22%;
178
382
  gap: 32px;
179
383
  padding: 32px 48px 48px;
180
384
  min-height: 0;
@@ -203,8 +407,8 @@
203
407
 
204
408
  .bw-label {
205
409
  display: block;
206
- margin-bottom: 10px;
207
- font-size: 12px;
410
+ margin-bottom: 14px;
411
+ font-size: 14px;
208
412
  font-weight: 500;
209
413
  color: var(--bw-text);
210
414
  }
@@ -215,6 +419,35 @@
215
419
  background: var(--bw-border);
216
420
  }
217
421
 
422
+ /* Service groups — categories. We use generous vertical rhythm
423
+ (28px gap) and a small caption-style heading instead of a hard
424
+ divider line, so the category break reads through whitespace +
425
+ typographic shift, not via a blend-in 1px border. */
426
+ .bw-svc-group {
427
+ display: flex;
428
+ flex-direction: column;
429
+ gap: 2px;
430
+ }
431
+
432
+ .bw-svc-group + .bw-svc-group {
433
+ margin-top: 28px;
434
+ }
435
+
436
+ /* Category heading: smaller than the section labels (14/500) but
437
+ heavier weight so the section break still reads cleanly. The
438
+ wider top gap above (28px between groups) carries most of the
439
+ visual separation. */
440
+ .bw-svc-category {
441
+ margin: 0 0 12px;
442
+ padding: 0;
443
+ font-size: 13px;
444
+ font-weight: 600;
445
+ line-height: 1.4;
446
+ letter-spacing: 0.01em;
447
+ color: var(--bw-text-secondary);
448
+ text-transform: none;
449
+ }
450
+
218
451
  .bw-staff-dropdown {
219
452
  position: relative;
220
453
  margin-bottom: 10px;
@@ -223,20 +456,30 @@
223
456
  .bw-staff-trigger {
224
457
  display: flex;
225
458
  align-items: center;
226
- gap: 10px;
459
+ gap: 12px;
227
460
  width: 100%;
228
- padding: 9px 13px;
229
- border: 2px solid var(--bw-border);
461
+ padding: 10px 14px;
462
+ border: 1px solid var(--bw-border);
230
463
  border-radius: var(--bw-radius);
231
464
  background: transparent;
232
465
  cursor: pointer;
233
466
  text-align: left;
234
- transition: border-color 250ms ease;
467
+ transition: border-color var(--bw-duration) var(--bw-ease),
468
+ box-shadow var(--bw-duration) var(--bw-ease),
469
+ background var(--bw-duration) var(--bw-ease);
470
+ }
471
+
472
+ .bw-staff-trigger:hover {
473
+ border-color: var(--bw-text-secondary);
235
474
  }
236
475
 
237
- .bw-staff-trigger:hover,
238
- .bw-staff-trigger.has-value {
476
+ .bw-staff-trigger:focus-visible {
239
477
  border-color: var(--bw-primary);
478
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06);
479
+ }
480
+
481
+ .bw-staff-trigger[disabled] {
482
+ cursor: default;
240
483
  }
241
484
 
242
485
  .bw-staff-trigger-content,
@@ -252,6 +495,7 @@
252
495
  }
253
496
 
254
497
  .bw-staff-avatar {
498
+ position: relative;
255
499
  display: flex;
256
500
  flex-shrink: 0;
257
501
  align-items: center;
@@ -261,6 +505,15 @@
261
505
  border-radius: 999px;
262
506
  background: var(--bw-border-light);
263
507
  color: var(--bw-text-secondary);
508
+ overflow: hidden;
509
+ }
510
+
511
+ .bw-staff-avatar-img {
512
+ position: absolute;
513
+ inset: 0;
514
+ width: 100%;
515
+ height: 100%;
516
+ object-fit: cover;
264
517
  }
265
518
 
266
519
  .bw-staff-avatar svg,
@@ -299,6 +552,9 @@
299
552
  font-weight: 500;
300
553
  line-height: 1.3;
301
554
  color: var(--bw-text);
555
+ white-space: nowrap;
556
+ overflow: hidden;
557
+ text-overflow: ellipsis;
302
558
  }
303
559
 
304
560
  .bw-staff-trigger-desc,
@@ -333,8 +589,9 @@
333
589
  z-index: 9;
334
590
  display: flex;
335
591
  flex-direction: column;
592
+ gap: 2px;
336
593
  width: 100%;
337
- padding: 4px;
594
+ padding: 6px;
338
595
  border: 1px solid var(--bw-border);
339
596
  border-radius: var(--bw-radius);
340
597
  background: var(--bw-bg);
@@ -353,10 +610,11 @@
353
610
  width: 100%;
354
611
  padding: 10px 12px;
355
612
  border: 0;
356
- border-radius: 6px;
613
+ border-radius: var(--bw-radius-sm);
357
614
  background: transparent;
358
615
  cursor: pointer;
359
616
  text-align: left;
617
+ transition: background var(--bw-duration) var(--bw-ease);
360
618
  }
361
619
 
362
620
  .bw-staff-option:hover,
@@ -364,52 +622,131 @@
364
622
  background: var(--bw-hover);
365
623
  }
366
624
 
625
+ /* When the picker is in expanded mode (full list visible), let it
626
+ stretch to fill the column so the scroll list extends down to the
627
+ bottom of the left column instead of capping at an arbitrary
628
+ height. Detected via :has() — the .bw-svc-scroll only renders in
629
+ expanded mode; in collapsed mode the picker contains only the
630
+ selected card, which sizes to its content (so the provider section
631
+ below sits flush, not pushed to the bottom). */
632
+ .bw-service-picker:has(.bw-svc-scroll) {
633
+ display: flex;
634
+ flex-direction: column;
635
+ flex: 1;
636
+ min-height: 0;
637
+ }
638
+
367
639
  .bw-svc-scroll {
368
- display: grid;
369
- grid-template-rows: 1fr;
640
+ display: flex;
641
+ flex-direction: column;
642
+ flex: 1;
643
+ min-height: 0;
370
644
  }
371
645
 
372
646
  .bw-svc-scroll-wrap {
647
+ position: relative;
648
+ flex: 1;
373
649
  min-height: 0;
374
- max-height: 320px;
375
650
  overflow-y: auto;
651
+ /* Soft fade at the top + bottom so the scroll edge looks
652
+ intentional instead of cutting service rows in half. Matches
653
+ the editorial "warm paper, not cold glass" feel — depth from
654
+ softness, not a hard line. */
655
+ -webkit-mask-image: linear-gradient(
656
+ to bottom,
657
+ transparent 0,
658
+ #000 18px,
659
+ #000 calc(100% - 18px),
660
+ transparent 100%
661
+ );
662
+ mask-image: linear-gradient(
663
+ to bottom,
664
+ transparent 0,
665
+ #000 18px,
666
+ #000 calc(100% - 18px),
667
+ transparent 100%
668
+ );
669
+ scroll-padding-block: 16px;
670
+ /* Native scrollbar tightened so it doesn't dominate the
671
+ softened edge. Webkit-only; Firefox uses default. */
672
+ scrollbar-width: thin;
673
+ scrollbar-color: var(--bw-border) transparent;
674
+ }
675
+
676
+ .bw-svc-scroll-wrap::-webkit-scrollbar {
677
+ width: 6px;
678
+ }
679
+
680
+ .bw-svc-scroll-wrap::-webkit-scrollbar-thumb {
681
+ border-radius: var(--bw-radius-pill);
682
+ background: var(--bw-border);
683
+ }
684
+
685
+ .bw-svc-scroll-wrap::-webkit-scrollbar-thumb:hover {
686
+ background: var(--bw-text-muted);
376
687
  }
377
688
 
378
689
  .bw-svc-scroll-inner {
379
690
  display: flex;
380
691
  flex-direction: column;
381
- padding-bottom: 10px;
692
+ padding-block: 12px 24px;
382
693
  }
383
694
 
695
+ /* Service row — refactored to fix the narrow-width hierarchy issues:
696
+ - Top-aligned items so price reads with the first line of the name
697
+ - Name allowed to wrap with text-wrap: pretty (no orphans)
698
+ - Description line-clamps to 2 lines instead of single-line ellipsis
699
+ - Concentric inner radius (row 12px - 14px padding around 20px check
700
+ leaves the optical center on the first line of text) */
384
701
  .bw-svc-row {
385
702
  display: flex;
386
- align-items: center;
387
- gap: 10px;
703
+ align-items: flex-start;
704
+ gap: 12px;
388
705
  width: 100%;
389
- padding: 10px 12px;
706
+ padding: 14px;
390
707
  border: 0;
391
- border-radius: 6px;
708
+ border-radius: var(--bw-radius);
392
709
  background: transparent;
393
710
  cursor: pointer;
394
711
  text-align: left;
395
- transition: background 150ms ease;
712
+ transition: background var(--bw-duration) var(--bw-ease),
713
+ box-shadow var(--bw-duration) var(--bw-ease),
714
+ transform var(--bw-duration) var(--bw-ease);
396
715
  }
397
716
 
398
717
  .bw-svc-row:hover {
399
718
  background: var(--bw-hover);
400
719
  }
401
720
 
721
+ .bw-svc-row:focus-visible {
722
+ background: var(--bw-hover);
723
+ box-shadow: inset 0 0 0 1.5px var(--bw-text);
724
+ }
725
+
726
+ .bw-svc-row:active {
727
+ transform: scale(0.98);
728
+ }
729
+
730
+ /* Checkbox: aligned to first line of name (1.4 line-height on 14px =
731
+ ~20px line, matches check height so they share a baseline). */
402
732
  .bw-check {
403
733
  display: flex;
404
734
  flex-shrink: 0;
405
735
  align-items: center;
406
736
  justify-content: center;
407
- width: 18px;
408
- height: 18px;
409
- border: 1.5px solid #d0d0d0;
410
- border-radius: 4px;
737
+ width: 20px;
738
+ height: 20px;
739
+ margin-top: 1px;
740
+ border: 1.5px solid var(--bw-border);
741
+ border-radius: var(--bw-radius-xs);
411
742
  color: transparent;
412
- transition: all 150ms ease;
743
+ transition: border-color var(--bw-duration) var(--bw-ease),
744
+ background var(--bw-duration) var(--bw-ease),
745
+ color var(--bw-duration) var(--bw-ease);
746
+ }
747
+
748
+ .bw-svc-row:hover .bw-check {
749
+ border-color: var(--bw-text-secondary);
413
750
  }
414
751
 
415
752
  .bw-check.is-checked {
@@ -419,38 +756,156 @@
419
756
  }
420
757
 
421
758
  .bw-check--lg {
422
- width: 20px;
423
- height: 20px;
424
- border-radius: 5px;
759
+ width: 22px;
760
+ height: 22px;
761
+ margin-top: 0;
762
+ border-radius: var(--bw-radius-xs);
763
+ }
764
+
765
+ /* Service thumbnail (Fresha/Square pattern). Replaces the checkbox
766
+ slot when a service has an image. Selected state shows a small
767
+ primary-colored badge in the bottom-right corner with the same
768
+ check icon — keeps the affordance consistent with image-less
769
+ rows. Concentric radius (md = 12px) matches surrounding chrome. */
770
+ .bw-svc-image {
771
+ position: relative;
772
+ flex-shrink: 0;
773
+ width: 80px;
774
+ height: 80px;
775
+ margin-top: 0;
776
+ border-radius: var(--bw-radius-md);
777
+ overflow: hidden;
778
+ background: var(--bw-border-light);
779
+ outline: 1px solid rgba(0, 0, 0, 0.06);
780
+ outline-offset: -1px;
781
+ transition: outline-color var(--bw-duration) var(--bw-ease);
782
+ }
783
+
784
+ .bw-svc-image img {
785
+ display: block;
786
+ width: 100%;
787
+ height: 100%;
788
+ object-fit: cover;
789
+ object-position: center;
790
+ }
791
+
792
+ .bw-svc-row:hover .bw-svc-image {
793
+ outline-color: rgba(0, 0, 0, 0.12);
794
+ }
795
+
796
+ .bw-svc-image.is-checked {
797
+ outline-color: var(--bw-primary);
798
+ outline-width: 1.5px;
799
+ }
800
+
801
+ .bw-svc-image-badge {
802
+ position: absolute;
803
+ right: 4px;
804
+ bottom: 4px;
805
+ display: flex;
806
+ align-items: center;
807
+ justify-content: center;
808
+ width: 18px;
809
+ height: 18px;
810
+ border-radius: var(--bw-radius-pill);
811
+ background: var(--bw-primary);
812
+ color: var(--bw-primary-text);
813
+ box-shadow: var(--bw-shadow-badge);
814
+ }
815
+
816
+ .bw-svc-image-badge svg {
817
+ width: 11px;
818
+ height: 11px;
819
+ }
820
+
821
+ .bw-svc-image--lg {
822
+ width: 96px;
823
+ height: 96px;
824
+ }
825
+
826
+ /* Meta footer row: groups duration + price on a single line under
827
+ the description with a dot separator. Replaces the old top-right
828
+ floating price so the row reads as a tidy stack instead of an
829
+ L-shape. */
830
+ .bw-svc-meta-row {
831
+ display: inline-flex;
832
+ align-items: baseline;
833
+ gap: 8px;
834
+ margin-top: 4px;
835
+ font-variant-numeric: tabular-nums;
836
+ color: var(--bw-text-secondary);
837
+ }
838
+
839
+ .bw-svc-meta-row .bw-svc-meta {
840
+ margin: 0;
841
+ }
842
+
843
+ .bw-svc-meta-row .bw-svc-price {
844
+ margin: 0;
845
+ flex-shrink: 0;
846
+ color: var(--bw-text);
847
+ }
848
+
849
+ .bw-svc-meta-dot {
850
+ color: var(--bw-text-muted);
851
+ user-select: none;
852
+ }
853
+
854
+ .bw-svc-info {
855
+ display: flex;
856
+ flex-direction: column;
857
+ gap: 4px;
858
+ min-width: 0;
859
+ flex: 1 1 auto;
425
860
  }
426
861
 
862
+ /* Name: 14px, allowed to wrap, pretty wrap to avoid one-word
863
+ orphans, hard cap at 2 lines so very long names truncate
864
+ cleanly instead of dominating the row. */
427
865
  .bw-svc-name {
428
- font-size: 12px;
429
- font-weight: 500;
430
- line-height: 1.3;
866
+ font-size: 14px;
867
+ font-weight: 600;
868
+ line-height: 1.35;
869
+ letter-spacing: -0.005em;
431
870
  color: var(--bw-text);
871
+ text-wrap: pretty;
872
+ display: -webkit-box;
873
+ -webkit-line-clamp: 2;
874
+ -webkit-box-orient: vertical;
875
+ overflow: hidden;
432
876
  }
433
877
 
878
+ /* Meta + description sit together as a soft secondary line. Meta
879
+ (duration) renders inline, then description picks up after a
880
+ middle dot. Wrapping is allowed; we line-clamp to 2 so multi-
881
+ sentence descriptions don't blow up the row height. */
434
882
  .bw-svc-meta {
435
- font-size: 11px;
436
- line-height: 1.3;
437
- color: var(--bw-text-muted);
883
+ font-size: 13px;
884
+ line-height: 1.45;
885
+ color: var(--bw-text-secondary);
886
+ font-variant-numeric: tabular-nums;
438
887
  }
439
888
 
440
889
  .bw-svc-desc {
441
- font-size: 11px;
442
- line-height: 1.4;
443
- color: var(--bw-text-muted);
444
- overflow: hidden;
445
- text-overflow: ellipsis;
446
- white-space: nowrap;
890
+ font-size: 13px;
891
+ line-height: 1.45;
892
+ color: var(--bw-text-secondary);
893
+ text-wrap: pretty;
447
894
  }
448
895
 
896
+ /* Price: same scale as name so the first-line baseline locks them
897
+ together optically. Regular weight (not 600) so price doesn't
898
+ compete for attention with the name — name leads, price reads
899
+ as a quiet endcap. */
449
900
  .bw-svc-price {
450
901
  flex-shrink: 0;
902
+ margin-top: 1px;
451
903
  font-size: 14px;
452
- font-weight: 600;
904
+ font-weight: 500;
905
+ line-height: 1.35;
453
906
  color: var(--bw-text);
907
+ font-variant-numeric: tabular-nums;
908
+ letter-spacing: -0.005em;
454
909
  }
455
910
 
456
911
  .bw-step-2-heading {
@@ -486,7 +941,7 @@
486
941
  }
487
942
 
488
943
  .bw-svc-row--full .bw-svc-meta {
489
- font-size: 12px;
944
+ font-size: 13px;
490
945
  }
491
946
 
492
947
  .bw-col--center {
@@ -503,9 +958,10 @@
503
958
  flex: 1;
504
959
  flex-direction: column;
505
960
  gap: 24px;
506
- padding: 32px;
961
+ padding: 24px 32px 32px;
507
962
  border: 1px solid var(--bw-border);
508
- border-radius: var(--bw-radius);
963
+ border-radius: var(--bw-radius-lg);
964
+ background: var(--bw-bg);
509
965
  }
510
966
 
511
967
  .bw-cal-prompt,
@@ -518,19 +974,235 @@
518
974
  color: var(--bw-text-muted);
519
975
  }
520
976
 
521
- .bw-cal-prompt {
522
- padding-top: 8px;
523
- text-align: center;
977
+ /* Transition the service-picker swap (full list <-> selected card)
978
+ and the provider section's reveal. Each branch animates in on
979
+ mount via the shared bw-slots-fade-in keyframe so the picker
980
+ feels like a settling state-machine, not a hard swap. */
981
+ .bw-svc-selected,
982
+ .bw-svc-scroll,
983
+ .bw-provider-section {
984
+ animation: bw-slots-fade-in 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
524
985
  }
525
986
 
526
- .bw-cal-header {
527
- display: flex;
528
- align-items: center;
529
- justify-content: space-between;
987
+ @media (prefers-reduced-motion: reduce) {
988
+ .bw-svc-selected,
989
+ .bw-svc-scroll,
990
+ .bw-provider-section {
991
+ animation: none;
992
+ }
530
993
  }
531
994
 
532
- .bw-month-dropdown {
533
- position: relative;
995
+ /* Service picker — collapsed selected state. Once a service is
996
+ picked, the long list collapses to a read-only card showing just
997
+ the selected service plus a "Change" link. Below this card the
998
+ provider section reveals (per the service-first flow). The card
999
+ reuses .bw-svc-row chrome but with --readonly disabling hover/cursor. */
1000
+ .bw-svc-selected {
1001
+ display: flex;
1002
+ flex-direction: column;
1003
+ gap: 12px;
1004
+ }
1005
+
1006
+ .bw-svc-selected-header {
1007
+ display: flex;
1008
+ align-items: baseline;
1009
+ justify-content: space-between;
1010
+ gap: 12px;
1011
+ }
1012
+
1013
+ .bw-svc-selected-header .bw-label {
1014
+ margin-bottom: 0;
1015
+ }
1016
+
1017
+ .bw-svc-change {
1018
+ padding: 0;
1019
+ border: 0;
1020
+ background: transparent;
1021
+ color: var(--bw-text-secondary);
1022
+ font-size: 13px;
1023
+ font-weight: 500;
1024
+ cursor: pointer;
1025
+ transition: color var(--bw-duration) var(--bw-ease);
1026
+ }
1027
+
1028
+ .bw-svc-change:hover {
1029
+ color: var(--bw-text);
1030
+ text-decoration: underline;
1031
+ text-underline-offset: 3px;
1032
+ }
1033
+
1034
+ .bw-svc-row--readonly {
1035
+ cursor: default;
1036
+ }
1037
+
1038
+ .bw-svc-row--readonly:hover {
1039
+ background: transparent;
1040
+ }
1041
+
1042
+ .bw-svc-row--readonly:hover .bw-check {
1043
+ border-color: var(--bw-primary);
1044
+ }
1045
+
1046
+ /* === Payment panel (design proposal) ===
1047
+ Renders inside the details form when paymentMode === 'full' or
1048
+ 'deposit'. Two stacks: a soft cararra-tinted summary card with
1049
+ line items + total + charge breakdown (mirrors the reference
1050
+ pattern but in Sable's tone), then a card-details slot for the
1051
+ eventual Stripe PaymentElement, then a small "secured by Stripe"
1052
+ reassurance line. Stub fields are placeholders only — real impl
1053
+ replaces .bw-pay-card-slot's children with <PaymentElement />. */
1054
+ .bw-pay {
1055
+ display: flex;
1056
+ flex-direction: column;
1057
+ gap: 18px;
1058
+ margin-top: 4px;
1059
+ }
1060
+
1061
+ .bw-pay-summary {
1062
+ display: flex;
1063
+ flex-direction: column;
1064
+ padding: 18px 20px;
1065
+ border: 1px solid var(--bw-border);
1066
+ border-radius: var(--bw-radius-lg);
1067
+ background: var(--bw-border-light);
1068
+ }
1069
+
1070
+ .bw-pay-summary-header {
1071
+ margin-bottom: 14px;
1072
+ font-size: 13px;
1073
+ font-weight: 600;
1074
+ color: var(--bw-text);
1075
+ }
1076
+
1077
+ .bw-pay-summary-row {
1078
+ display: flex;
1079
+ align-items: baseline;
1080
+ justify-content: space-between;
1081
+ gap: 16px;
1082
+ padding: 4px 0;
1083
+ font-size: 13px;
1084
+ line-height: 1.45;
1085
+ color: var(--bw-text);
1086
+ font-variant-numeric: tabular-nums;
1087
+ }
1088
+
1089
+ .bw-pay-summary-row--muted {
1090
+ color: var(--bw-text-secondary);
1091
+ }
1092
+
1093
+ .bw-pay-summary-row--total {
1094
+ font-weight: 600;
1095
+ }
1096
+
1097
+ .bw-pay-summary-row--strong {
1098
+ margin-top: 2px;
1099
+ font-size: 14px;
1100
+ font-weight: 600;
1101
+ color: var(--bw-text);
1102
+ }
1103
+
1104
+ .bw-pay-summary-val {
1105
+ flex-shrink: 0;
1106
+ }
1107
+
1108
+ .bw-pay-summary-divider {
1109
+ height: 1px;
1110
+ margin: 10px 0;
1111
+ background: var(--bw-border);
1112
+ }
1113
+
1114
+ .bw-pay-card {
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ gap: 10px;
1118
+ }
1119
+
1120
+ .bw-pay-card .bw-label {
1121
+ margin-bottom: 0;
1122
+ }
1123
+
1124
+ /* Card slot: a soft surface where Stripe's PaymentElement will
1125
+ mount. Until then, render a static stub of the fields so the
1126
+ spacing + chrome can be validated. */
1127
+ .bw-pay-card-slot {
1128
+ padding: 14px;
1129
+ border: 1px solid var(--bw-border);
1130
+ border-radius: var(--bw-radius-lg);
1131
+ background: var(--bw-bg);
1132
+ }
1133
+
1134
+ .bw-pay-card-stub {
1135
+ display: flex;
1136
+ flex-direction: column;
1137
+ gap: 12px;
1138
+ }
1139
+
1140
+ .bw-pay-card-stub-row {
1141
+ display: flex;
1142
+ flex-direction: column;
1143
+ gap: 4px;
1144
+ padding: 10px 12px;
1145
+ border: 1px solid var(--bw-border-light);
1146
+ border-radius: var(--bw-radius);
1147
+ background: var(--bw-border-light);
1148
+ }
1149
+
1150
+ .bw-pay-card-stub-label {
1151
+ font-size: 11px;
1152
+ font-weight: 500;
1153
+ color: var(--bw-text-muted);
1154
+ }
1155
+
1156
+ .bw-pay-card-stub-value {
1157
+ font-size: 13px;
1158
+ color: var(--bw-text-secondary);
1159
+ font-variant-numeric: tabular-nums;
1160
+ letter-spacing: 0.04em;
1161
+ }
1162
+
1163
+ .bw-pay-card-stub-grid {
1164
+ display: grid;
1165
+ grid-template-columns: 1fr 1fr 1fr;
1166
+ gap: 8px;
1167
+ }
1168
+
1169
+ .bw-pay-secure {
1170
+ margin: 0;
1171
+ font-size: 12px;
1172
+ color: var(--bw-text-muted);
1173
+ }
1174
+
1175
+ /* Empty-state when no service is selected. Centered in the slots
1176
+ column so the right pane reads as "waiting for input" instead
1177
+ of just blank. Used in both desktop (right column) and mobile
1178
+ (inline under the calendar) since slotsArea renders in both. */
1179
+ .bw-slots-empty {
1180
+ display: flex;
1181
+ flex: 1;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ min-height: 120px;
1185
+ margin: 0;
1186
+ padding: 24px 12px;
1187
+ font-size: 14px;
1188
+ line-height: 1.5;
1189
+ color: var(--bw-text-secondary);
1190
+ text-align: center;
1191
+ }
1192
+
1193
+ .bw-cal-prompt {
1194
+ padding-top: 8px;
1195
+ text-align: center;
1196
+ }
1197
+
1198
+ .bw-cal-header {
1199
+ display: flex;
1200
+ align-items: center;
1201
+ justify-content: space-between;
1202
+ }
1203
+
1204
+ .bw-month-dropdown {
1205
+ position: relative;
534
1206
  }
535
1207
 
536
1208
  .bw-month-btn {
@@ -609,35 +1281,42 @@
609
1281
  .bw-cal-grid {
610
1282
  display: grid;
611
1283
  grid-template-columns: repeat(7, 1fr);
1284
+ column-gap: 6px;
612
1285
  }
613
1286
 
614
1287
  .bw-cal-weekdays {
615
1288
  text-align: center;
1289
+ margin-bottom: 4px;
616
1290
  }
617
1291
 
618
1292
  .bw-cal-weekdays span {
619
1293
  padding: 6px 0;
620
- font-size: 12px;
621
- font-weight: 500;
1294
+ font-size: 11px;
1295
+ font-weight: 600;
1296
+ letter-spacing: 0.08em;
1297
+ text-transform: uppercase;
622
1298
  color: var(--bw-text-muted);
623
1299
  }
624
1300
 
625
1301
  .bw-cal-grid {
626
- gap: 6px 0;
1302
+ gap: 6px;
627
1303
  }
628
1304
 
629
1305
  .bw-cal-day {
1306
+ position: relative;
630
1307
  display: flex;
631
1308
  align-items: center;
632
1309
  justify-content: center;
633
1310
  height: 48px;
634
1311
  border: 0;
635
- border-radius: 6px;
1312
+ border-radius: var(--bw-radius-sm);
636
1313
  background: transparent;
637
1314
  color: var(--bw-text);
638
1315
  cursor: default;
639
1316
  font-size: 13px;
640
- transition: background 300ms ease, color 300ms ease;
1317
+ font-variant-numeric: tabular-nums;
1318
+ transition: background var(--bw-duration) var(--bw-ease),
1319
+ color var(--bw-duration) var(--bw-ease);
641
1320
  }
642
1321
 
643
1322
  .bw-cal-day.is-outside {
@@ -662,6 +1341,26 @@
662
1341
  font-weight: 600;
663
1342
  }
664
1343
 
1344
+ /* Dot under today's number — subtle reference indicator. Hidden when
1345
+ the day is the active selection (the filled background already
1346
+ communicates focus). */
1347
+ .bw-cal-day.is-today::after {
1348
+ content: '';
1349
+ position: absolute;
1350
+ bottom: 6px;
1351
+ left: 50%;
1352
+ width: 4px;
1353
+ height: 4px;
1354
+ border-radius: var(--bw-radius-pill);
1355
+ background: var(--bw-text-muted);
1356
+ transform: translateX(-50%);
1357
+ }
1358
+
1359
+ .bw-cal-day.is-today.is-selected::after {
1360
+ background: var(--bw-primary-text);
1361
+ opacity: 0.7;
1362
+ }
1363
+
665
1364
  .bw-cal-day.is-blocked,
666
1365
  .bw-cal-day.is-disabled {
667
1366
  color: var(--bw-border);
@@ -674,6 +1373,48 @@
674
1373
  margin-top: 8px;
675
1374
  }
676
1375
 
1376
+ /* Wrapper that re-mounts whenever service/staff/date/loading state
1377
+ changes (key-driven from JSX). The wrapper itself fades, and each
1378
+ slot pill staggers in via --bw-slot-i set inline so the list
1379
+ reads as "settling into place" instead of popping. */
1380
+ .bw-slots-fade {
1381
+ animation: bw-slots-fade-in 220ms cubic-bezier(0.16, 1, 0.3, 1) both;
1382
+ }
1383
+
1384
+ .bw-slots-fade .bw-slot {
1385
+ animation: bw-slot-stagger-in 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
1386
+ animation-delay: calc(var(--bw-slot-i, 0) * 22ms);
1387
+ }
1388
+
1389
+ @keyframes bw-slots-fade-in {
1390
+ from {
1391
+ opacity: 0;
1392
+ transform: translateY(4px);
1393
+ }
1394
+ to {
1395
+ opacity: 1;
1396
+ transform: translateY(0);
1397
+ }
1398
+ }
1399
+
1400
+ @keyframes bw-slot-stagger-in {
1401
+ from {
1402
+ opacity: 0;
1403
+ transform: translateY(6px) scale(0.98);
1404
+ }
1405
+ to {
1406
+ opacity: 1;
1407
+ transform: translateY(0) scale(1);
1408
+ }
1409
+ }
1410
+
1411
+ @media (prefers-reduced-motion: reduce) {
1412
+ .bw-slots-fade,
1413
+ .bw-slots-fade .bw-slot {
1414
+ animation: none;
1415
+ }
1416
+ }
1417
+
677
1418
  .bw-slot {
678
1419
  padding: 12px 0;
679
1420
  border: 1px solid var(--bw-border);
@@ -683,12 +1424,21 @@
683
1424
  text-align: center;
684
1425
  white-space: nowrap;
685
1426
  font-size: 13px;
1427
+ font-variant-numeric: tabular-nums;
686
1428
  color: var(--bw-text);
687
- transition: all 150ms ease;
1429
+ transition: border-color var(--bw-duration) var(--bw-ease),
1430
+ background var(--bw-duration) var(--bw-ease),
1431
+ color var(--bw-duration) var(--bw-ease),
1432
+ transform var(--bw-duration) var(--bw-ease);
688
1433
  }
689
1434
 
690
1435
  .bw-slot:hover {
691
- border-color: var(--bw-text);
1436
+ border-color: var(--bw-text-secondary);
1437
+ background: var(--bw-hover);
1438
+ }
1439
+
1440
+ .bw-slot:active {
1441
+ transform: scale(0.98);
692
1442
  }
693
1443
 
694
1444
  .bw-slot.is-active {
@@ -741,23 +1491,45 @@
741
1491
  .bw-field input,
742
1492
  .bw-field textarea {
743
1493
  width: 100%;
744
- padding: 10px 14px;
1494
+ padding: 11px 16px;
745
1495
  border: 1px solid var(--bw-border);
746
- border-radius: 6px;
1496
+ border-radius: var(--bw-radius-md);
747
1497
  background: transparent;
748
1498
  color: var(--bw-text);
749
1499
  font-size: 13px;
750
- transition: border-color 150ms ease;
1500
+ font-family: inherit;
1501
+ transition: border-color var(--bw-duration) var(--bw-ease),
1502
+ box-shadow var(--bw-duration) var(--bw-ease);
751
1503
  }
752
1504
 
753
1505
  .bw-field textarea {
754
- min-height: 80px;
1506
+ min-height: 96px;
755
1507
  resize: none;
756
1508
  }
757
1509
 
1510
+ .bw-field input:hover,
1511
+ .bw-field textarea:hover {
1512
+ border-color: var(--bw-text-secondary);
1513
+ }
1514
+
758
1515
  .bw-field input:focus,
759
1516
  .bw-field textarea:focus {
760
1517
  border-color: var(--bw-primary);
1518
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06);
1519
+ }
1520
+
1521
+ .bw-field.is-invalid input,
1522
+ .bw-field.is-invalid textarea,
1523
+ .bw-field.is-invalid select {
1524
+ border-color: var(--bw-error-text);
1525
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--bw-error-text) 12%, transparent);
1526
+ }
1527
+
1528
+ .bw-field-error {
1529
+ margin-top: -4px;
1530
+ color: var(--bw-error-text);
1531
+ font-size: 12px;
1532
+ line-height: 1.35;
761
1533
  }
762
1534
 
763
1535
  .bw-field input::placeholder,
@@ -777,25 +1549,53 @@
777
1549
  color: var(--bw-text-muted);
778
1550
  }
779
1551
 
1552
+ .bw-submit-help {
1553
+ border: 1px solid color-mix(in srgb, var(--bw-error-text) 24%, var(--bw-border));
1554
+ border-radius: 10px;
1555
+ background: color-mix(in srgb, var(--bw-error-text) 8%, var(--bw-bg));
1556
+ color: var(--bw-text);
1557
+ padding: 10px 12px;
1558
+ font-size: 12px;
1559
+ line-height: 1.45;
1560
+ }
1561
+
1562
+ .bw-submit-help-title {
1563
+ display: block;
1564
+ font-weight: 650;
1565
+ }
1566
+
1567
+ .bw-submit-help ul {
1568
+ margin: 6px 0 0;
1569
+ padding-left: 18px;
1570
+ }
1571
+
1572
+ .bw-submit-help li + li {
1573
+ margin-top: 2px;
1574
+ }
1575
+
780
1576
  .bw-required {
781
1577
  color: var(--bw-error-text);
782
1578
  font-weight: 400;
783
1579
  }
784
1580
 
785
1581
  .bw-summary {
786
- padding-top: 16px;
1582
+ padding: 18px;
1583
+ border: 1px solid var(--bw-border);
1584
+ border-radius: var(--bw-radius-lg);
1585
+ background: var(--bw-bg);
787
1586
  }
788
1587
 
789
1588
  .bw-summary-title {
790
1589
  display: block;
791
- margin-bottom: 12px;
792
- font-size: 14px;
1590
+ margin-bottom: 14px;
1591
+ font-size: 13px;
793
1592
  font-weight: 600;
1593
+ letter-spacing: -0.005em;
794
1594
  color: var(--bw-text);
795
1595
  }
796
1596
 
797
1597
  .bw-summary-rows {
798
- gap: 8px;
1598
+ gap: 10px;
799
1599
  }
800
1600
 
801
1601
  .bw-summary-row {
@@ -804,6 +1604,8 @@
804
1604
  justify-content: space-between;
805
1605
  gap: 16px;
806
1606
  font-size: 13px;
1607
+ line-height: 1.45;
1608
+ font-variant-numeric: tabular-nums;
807
1609
  }
808
1610
 
809
1611
  .bw-summary-row span:first-child {
@@ -848,6 +1650,63 @@
848
1650
  line-height: 1.4;
849
1651
  }
850
1652
 
1653
+ .bw-service-warning {
1654
+ display: flex;
1655
+ gap: 14px;
1656
+ padding: 18px;
1657
+ border: 2px solid #ef4444;
1658
+ border-radius: var(--bw-radius-lg);
1659
+ background: #fef2f2;
1660
+ color: #7f1d1d;
1661
+ }
1662
+
1663
+ .bw-service-warning-icon {
1664
+ display: flex;
1665
+ flex: 0 0 auto;
1666
+ align-items: center;
1667
+ justify-content: center;
1668
+ width: 42px;
1669
+ height: 42px;
1670
+ border-radius: 999px;
1671
+ background: #fee2e2;
1672
+ color: #b91c1c;
1673
+ }
1674
+
1675
+ .bw-service-warning-icon svg {
1676
+ width: 24px;
1677
+ height: 24px;
1678
+ }
1679
+
1680
+ .bw-service-warning-copy {
1681
+ display: flex;
1682
+ min-width: 0;
1683
+ flex-direction: column;
1684
+ gap: 6px;
1685
+ }
1686
+
1687
+ .bw-service-warning-title {
1688
+ font-size: 16px;
1689
+ font-weight: 800;
1690
+ line-height: 1.25;
1691
+ color: #7f1d1d;
1692
+ }
1693
+
1694
+ .bw-service-warning p {
1695
+ margin: 0;
1696
+ font-size: 13px;
1697
+ line-height: 1.5;
1698
+ color: #991b1b;
1699
+ }
1700
+
1701
+ .bw-service-warning-link {
1702
+ align-self: flex-start;
1703
+ font-size: 13px;
1704
+ font-weight: 800;
1705
+ color: #7f1d1d;
1706
+ text-decoration: underline;
1707
+ text-underline-offset: 3px;
1708
+ }
1709
+
851
1710
  .bw-confirm-btn,
852
1711
  .bw-footer-next,
853
1712
  .bw-btn-primary {
@@ -859,21 +1718,31 @@
859
1718
  background: var(--bw-primary);
860
1719
  color: var(--bw-primary-text);
861
1720
  cursor: pointer;
862
- transition: opacity 150ms ease;
1721
+ transition: opacity var(--bw-duration) var(--bw-ease),
1722
+ transform var(--bw-duration) var(--bw-ease),
1723
+ box-shadow var(--bw-duration) var(--bw-ease);
863
1724
  }
864
1725
 
865
1726
  .bw-confirm-btn {
866
1727
  width: 100%;
867
- height: 48px;
868
- border-radius: var(--bw-radius);
1728
+ height: 52px;
1729
+ border-radius: var(--bw-radius-pill);
869
1730
  font-size: 15px;
870
1731
  font-weight: 500;
1732
+ letter-spacing: -0.005em;
871
1733
  }
872
1734
 
873
1735
  .bw-confirm-btn:hover:not(:disabled),
874
1736
  .bw-footer-next:hover:not(:disabled),
875
1737
  .bw-btn-primary:hover {
876
- opacity: 0.9;
1738
+ opacity: 0.92;
1739
+ box-shadow: var(--bw-shadow-lift);
1740
+ }
1741
+
1742
+ .bw-confirm-btn:active:not(:disabled),
1743
+ .bw-footer-next:active:not(:disabled),
1744
+ .bw-btn-primary:active {
1745
+ transform: scale(0.99);
877
1746
  }
878
1747
 
879
1748
  .bw-confirm-btn:disabled,
@@ -903,6 +1772,24 @@
903
1772
  color: var(--bw-primary-text);
904
1773
  }
905
1774
 
1775
+ /* Cancelled-state disc: neutral background instead of the celebratory
1776
+ primary fill. Tone is informational, not affirmative — we don't
1777
+ want to "celebrate" a cancellation with a brand-color check badge. */
1778
+ .bw-done-icon--muted {
1779
+ background: var(--bw-border-light);
1780
+ color: var(--bw-text-secondary);
1781
+ }
1782
+
1783
+ /* Override the shared 16px sizing for icons rendered inside chrome
1784
+ (.bw-done-icon, .bw-staff-avatar, etc.) — the success view's check
1785
+ reads small inside the 56px disc, so bump the glyph to 24px so it
1786
+ anchors the moment cleanly. */
1787
+ .bw-done-icon svg {
1788
+ width: 24px;
1789
+ height: 24px;
1790
+ stroke-width: 2;
1791
+ }
1792
+
906
1793
  .bw-done-title {
907
1794
  margin: 0;
908
1795
  font-size: 28px;
@@ -927,8 +1814,52 @@
927
1814
  }
928
1815
 
929
1816
  @media (max-width: 1024px) {
1817
+ /* Lock mobile widget to a fixed 90vh height so the frame doesn't
1818
+ bounce between steps. Step 2 (calendar + slot list) is the
1819
+ tallest natural step and fits at 90vh on typical phones; step 1
1820
+ and step 3 fill the same height with the active col content at
1821
+ top + footer pinned at bottom. The body becomes the scroll
1822
+ container below — content longer than the body height (rare:
1823
+ long service list on step 1, busy slot day on step 2) scrolls
1824
+ internally instead of growing the widget. */
930
1825
  .bw {
931
- min-height: 80vh;
1826
+ min-height: 90vh;
1827
+ max-height: 90vh;
1828
+ }
1829
+
1830
+ /* Lock header/footer in the column so they never shrink under
1831
+ content pressure. Flex children default to flex-shrink: 1, which
1832
+ would let a tall body squeeze the title/buttons. Force them to
1833
+ size to their content and stay put — the body in between is the
1834
+ only thing that scrolls. */
1835
+ .bw-mobile-header,
1836
+ .bw-header,
1837
+ .bw-footer {
1838
+ flex: 0 0 auto;
1839
+ }
1840
+
1841
+ .bw-body {
1842
+ overflow-y: auto;
1843
+ -webkit-overflow-scrolling: touch;
1844
+ overscroll-behavior: contain;
1845
+ /* Soft fade at both edges of the scroll area so content reads as
1846
+ scrollable past the header above and the footer below instead
1847
+ of cut off. Padding inside the body keeps the first/last rows
1848
+ from sitting right at the fade line. */
1849
+ -webkit-mask-image: linear-gradient(
1850
+ to bottom,
1851
+ transparent 0,
1852
+ #000 24px,
1853
+ #000 calc(100% - 24px),
1854
+ transparent 100%
1855
+ );
1856
+ mask-image: linear-gradient(
1857
+ to bottom,
1858
+ transparent 0,
1859
+ #000 24px,
1860
+ #000 calc(100% - 24px),
1861
+ transparent 100%
1862
+ );
932
1863
  }
933
1864
 
934
1865
  .bw-skel-desktop {
@@ -953,11 +1884,12 @@
953
1884
 
954
1885
  .bw-header {
955
1886
  padding: 20px 24px 20px;
1887
+ border-bottom: 1px solid var(--bw-border-light);
956
1888
  }
957
1889
 
958
1890
  .bw-header-row {
959
1891
  display: flex;
960
- align-items: center;
1892
+ align-items: flex-start;
961
1893
  justify-content: space-between;
962
1894
  gap: 16px;
963
1895
  }
@@ -969,6 +1901,11 @@
969
1901
  justify-content: flex-end;
970
1902
  gap: 8px;
971
1903
  width: 116px;
1904
+ /* Align dots with the title's first cap-height when the title
1905
+ wraps to multiple lines (e.g. "Reschedule Eric Lan's
1906
+ appointment"). Without this the dots would sit on the row's
1907
+ top edge above the title's optical line. */
1908
+ margin-top: 10px;
972
1909
  }
973
1910
 
974
1911
  .bw-dot {
@@ -1018,40 +1955,60 @@
1018
1955
  display: none;
1019
1956
  }
1020
1957
 
1958
+ /* Mobile 4-step flow:
1959
+ Step 1: services (bw-col--left, bw-step-2 only — provider hidden)
1960
+ Step 2: provider (bw-col--left, bw-step-1 only — services hidden)
1961
+ Step 3: calendar + slots (bw-col--center)
1962
+ Step 4: form (bw-col--right) */
1021
1963
  .bw-body[data-mobile-step="1"] .bw-col--left,
1022
1964
  .bw-body[data-mobile-step="2"] .bw-col--left,
1023
1965
  .bw-body[data-mobile-step="3"] .bw-col--center,
1024
1966
  .bw-body[data-mobile-step="4"] .bw-col--right {
1025
1967
  display: flex;
1968
+ padding-top: 16px;
1969
+ padding-bottom: 20px;
1026
1970
  }
1027
1971
 
1028
1972
  .bw-body[data-mobile-step="4"] .bw-right-inner {
1029
1973
  padding-bottom: 20px;
1030
1974
  }
1031
1975
 
1976
+ /* Step 1: hide the desktop service-picker (inside bw-step-1) and
1977
+ hide the entire bw-step-1 wrapper. Show bw-step-2 (full-width
1978
+ services list). */
1032
1979
  .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
1980
  display: none;
1038
1981
  }
1039
1982
 
1040
- .bw-body[data-mobile-step="1"] .bw-service-picker {
1983
+ .bw-body[data-mobile-step="1"] .bw-step-2 {
1984
+ display: flex;
1985
+ flex-direction: column;
1986
+ }
1987
+
1988
+ .bw-body[data-mobile-step="1"] .bw-step-2-heading,
1989
+ .bw-body[data-mobile-step="1"] .bw-step-2-divider {
1041
1990
  display: none;
1042
1991
  }
1043
1992
 
1993
+ /* Step 2: provider only. Show bw-step-1 (which renders the
1994
+ provider section conditional on selectedService — guaranteed
1995
+ true at step 2). Hide bw-step-2 services + the desktop
1996
+ service-picker stub inside bw-step-1. */
1044
1997
  .bw-body[data-mobile-step="2"] .bw-step-1 {
1045
- display: none;
1998
+ display: flex;
1999
+ flex: 0 0 auto;
1046
2000
  }
1047
2001
 
1048
2002
  .bw-body[data-mobile-step="2"] .bw-step-2 {
1049
- display: flex;
1050
- flex-direction: column;
2003
+ display: none;
1051
2004
  }
1052
2005
 
1053
- .bw-body[data-mobile-step="2"] .bw-step-2-heading,
1054
- .bw-body[data-mobile-step="2"] .bw-step-2-divider {
2006
+ .bw-body[data-mobile-step="2"] .bw-step-1 .bw-service-picker {
2007
+ display: none;
2008
+ }
2009
+
2010
+ .bw-body[data-mobile-step="2"] .bw-step-1 .bw-provider-section .bw-section-divider {
2011
+ /* Mobile rhythm uses whitespace, not 1px lines */
1055
2012
  display: none;
1056
2013
  }
1057
2014
 
@@ -1078,6 +2035,67 @@
1078
2035
  margin: 4px 0;
1079
2036
  }
1080
2037
 
2038
+ /* Mobile typography bumps. Per productdesign.md typography scale
2039
+ mobile column: list-row primary reads as body-s (13px) at the
2040
+ smallest, body-m (15px) for drawer-style titles, caption-l (12px)
2041
+ for list-row secondary. The widget is essentially a one-screen
2042
+ booking surface, so primary read targets get a one-step bump on
2043
+ mobile so they stay accessible at arm's length. */
2044
+ .bw-svc-name,
2045
+ .bw-svc-row--full .bw-svc-name,
2046
+ .bw-svc-price {
2047
+ font-size: 15px;
2048
+ }
2049
+
2050
+ .bw-svc-meta,
2051
+ .bw-svc-row--full .bw-svc-meta,
2052
+ .bw-svc-desc {
2053
+ font-size: 15px;
2054
+ line-height: 1.5;
2055
+ }
2056
+
2057
+ .bw-svc-category {
2058
+ font-size: 14px;
2059
+ }
2060
+
2061
+ .bw-staff-trigger-name,
2062
+ .bw-staff-option-name {
2063
+ font-size: 15px;
2064
+ }
2065
+
2066
+ .bw-staff-trigger-desc,
2067
+ .bw-staff-option-desc {
2068
+ font-size: 12px;
2069
+ }
2070
+
2071
+ .bw-staff-initials {
2072
+ font-size: 13px;
2073
+ }
2074
+
2075
+ /* Mobile thumbnails: bump both the inline (left col) and the
2076
+ full-width (mobile step 1) variants up one notch. The mobile
2077
+ full-width row has more horizontal breathing room and the
2078
+ thumbnail gets to anchor the row visually. */
2079
+ .bw-svc-image {
2080
+ width: 88px;
2081
+ height: 88px;
2082
+ }
2083
+
2084
+ .bw-svc-image--lg {
2085
+ width: 104px;
2086
+ height: 104px;
2087
+ }
2088
+
2089
+ .bw-svc-image-badge {
2090
+ width: 20px;
2091
+ height: 20px;
2092
+ }
2093
+
2094
+ .bw-svc-image-badge svg {
2095
+ width: 12px;
2096
+ height: 12px;
2097
+ }
2098
+
1081
2099
  .bw-form .bw-summary,
1082
2100
  .bw-confirm-btn {
1083
2101
  display: none;
@@ -1132,6 +2150,25 @@
1132
2150
  font-size: 15px;
1133
2151
  font-weight: 500;
1134
2152
  }
2153
+
2154
+ .bw-footer-payment-slot {
2155
+ display: flex;
2156
+ flex: 1;
2157
+ min-width: 0;
2158
+ }
2159
+
2160
+ .bw-footer-payment-slot:empty {
2161
+ display: none;
2162
+ }
2163
+
2164
+ .bw-footer-payment-slot .bw-confirm-btn {
2165
+ display: flex;
2166
+ flex: 1;
2167
+ height: 48px;
2168
+ border-radius: var(--bw-radius-lg);
2169
+ font-size: 15px;
2170
+ font-weight: 500;
2171
+ }
1135
2172
  }
1136
2173
 
1137
2174
  @media (max-width: 480px) {
@@ -1144,7 +2181,7 @@
1144
2181
  gap: 6px;
1145
2182
  }
1146
2183
 
1147
- .bw-body[data-mobile-step="4"] .bw-right-inner {
2184
+ .bw-body[data-mobile-step="3"] .bw-right-inner {
1148
2185
  padding-bottom: 16px;
1149
2186
  }
1150
2187
 
@@ -1193,3 +2230,548 @@
1193
2230
  font-size: 12px;
1194
2231
  }
1195
2232
  }
2233
+
2234
+ /* ===========================================================================
2235
+ Desktop-only right-pane swap (slots → details) and sizing fixes.
2236
+ Mobile is intentionally untouched here — the pane wrappers exist in the
2237
+ DOM on mobile but receive no styling, so they collapse into transparent
2238
+ block containers and the form / cal-card / step-1 keep their original
2239
+ mobile flex behavior from commit 6ca438f.
2240
+ =========================================================================== */
2241
+
2242
+ /* Slot pill structural styling that's safe on both breakpoints because
2243
+ the .bw-slots-desktop wrapper is hidden on mobile (display: none below)
2244
+ and the .bw-slots-mobile wrapper is the only path that renders the
2245
+ inline 4-up grid on mobile via display: contents. */
2246
+ .bw-slots-mobile {
2247
+ display: none;
2248
+ }
2249
+
2250
+ .bw-slots-heading {
2251
+ display: flex;
2252
+ align-items: baseline;
2253
+ justify-content: space-between;
2254
+ margin-bottom: 14px;
2255
+ font-size: 14px;
2256
+ font-weight: 500;
2257
+ color: var(--bw-text);
2258
+ }
2259
+
2260
+ .bw-slots-count {
2261
+ font-size: 12px;
2262
+ color: var(--bw-text-muted);
2263
+ font-variant-numeric: tabular-nums;
2264
+ }
2265
+
2266
+ .bw-back-btn {
2267
+ display: inline-flex;
2268
+ align-items: center;
2269
+ gap: 6px;
2270
+ margin-bottom: 16px;
2271
+ padding: 6px 0;
2272
+ background: transparent;
2273
+ border: 0;
2274
+ font-size: 12px;
2275
+ color: var(--bw-text-muted);
2276
+ cursor: pointer;
2277
+ transition: color var(--bw-duration) var(--bw-ease);
2278
+ }
2279
+
2280
+ .bw-back-btn:hover {
2281
+ color: var(--bw-text);
2282
+ }
2283
+
2284
+ .bw-back-btn svg {
2285
+ width: 14px;
2286
+ height: 14px;
2287
+ }
2288
+
2289
+ @media (min-width: 1025px) {
2290
+ /* Desktop body grid: cols sit at the top of their cell instead of
2291
+ stretching to the row's max content height — the visual empty
2292
+ space below the cal-card and right-pane goes away. */
2293
+ .bw-body {
2294
+ align-items: start;
2295
+ }
2296
+
2297
+ /* Desktop cal-card hugs its grid + timezone instead of stretching
2298
+ to fill the row, and the timezone sits flush below the day grid
2299
+ (no auto margin pushing it down). */
2300
+ .bw-cal-card {
2301
+ flex: 0 0 auto;
2302
+ }
2303
+
2304
+ .bw-timezone {
2305
+ margin-top: 0;
2306
+ }
2307
+
2308
+ /* Slots column on desktop: vertical pill list above the form, not
2309
+ a 4-up grid below the calendar. */
2310
+ .bw-slots-desktop .bw-time-slots {
2311
+ grid-template-columns: 1fr;
2312
+ gap: 8px;
2313
+ margin-top: 0;
2314
+ }
2315
+
2316
+ .bw-slots-desktop .bw-slot {
2317
+ padding: 14px 16px;
2318
+ }
2319
+
2320
+ /* Match the skeleton pill dimensions to the real desktop pill so
2321
+ the load → loaded swap doesn't reflow the column. Real pill =
2322
+ 14px padding + 13px font + line-height ≈ 50px tall. */
2323
+ .bw-slots-desktop .bw-skel--slot {
2324
+ height: 50px;
2325
+ }
2326
+
2327
+ /* --bw-cal-h is set on the .bw root via ResizeObserver in the
2328
+ widget — it tracks the live height of the .bw-cal-card in the
2329
+ middle column. Use it to cap the services list (left) and the
2330
+ slots list (right) so all three columns share the same visual
2331
+ height. Both internal scrollers fade their top + bottom edges
2332
+ so cut-off content reads as scrollable, not clipped. */
2333
+ .bw-svc-scroll-wrap {
2334
+ max-height: var(--bw-cal-h, 480px);
2335
+ }
2336
+
2337
+ .bw-pane--slots {
2338
+ max-height: var(--bw-cal-h, 480px);
2339
+ overflow: hidden;
2340
+ display: flex;
2341
+ flex-direction: column;
2342
+ }
2343
+
2344
+ .bw-pane--slots .bw-slots-desktop {
2345
+ flex: 1;
2346
+ min-height: 0;
2347
+ overflow-y: auto;
2348
+ -webkit-mask-image: linear-gradient(
2349
+ to bottom,
2350
+ transparent 0,
2351
+ #000 18px,
2352
+ #000 calc(100% - 18px),
2353
+ transparent 100%
2354
+ );
2355
+ mask-image: linear-gradient(
2356
+ to bottom,
2357
+ transparent 0,
2358
+ #000 18px,
2359
+ #000 calc(100% - 18px),
2360
+ transparent 100%
2361
+ );
2362
+ scrollbar-width: thin;
2363
+ scrollbar-color: var(--bw-border) transparent;
2364
+ /* Padding so first/last pills sit inside the fade zone, matching
2365
+ the services scroller pattern. */
2366
+ padding-block: 12px 16px;
2367
+ }
2368
+
2369
+ .bw-pane--slots .bw-slots-desktop::-webkit-scrollbar {
2370
+ width: 6px;
2371
+ }
2372
+
2373
+ .bw-pane--slots .bw-slots-desktop::-webkit-scrollbar-thumb {
2374
+ border-radius: var(--bw-radius-pill);
2375
+ background: var(--bw-border);
2376
+ }
2377
+
2378
+ .bw-pane--slots .bw-slots-desktop::-webkit-scrollbar-thumb:hover {
2379
+ background: var(--bw-text-muted);
2380
+ }
2381
+
2382
+ /* Right column two-step stage: slots pane → (crossfade) → details
2383
+ pane. Both panes share the stage area; the hidden one is taken
2384
+ out of normal flow so the column sizes only to the active pane.
2385
+ Animation timing matches Sable's productdesign popup rule:
2386
+ 250ms expo ease-out on enter, 150ms standard ease-out on exit. */
2387
+ .bw-right-stage {
2388
+ position: relative;
2389
+ display: flex;
2390
+ flex-direction: column;
2391
+ min-height: 0;
2392
+ }
2393
+
2394
+ .bw-pane {
2395
+ transition:
2396
+ opacity 250ms cubic-bezier(0.16, 1, 0.3, 1),
2397
+ transform 250ms cubic-bezier(0.16, 1, 0.3, 1);
2398
+ will-change: opacity, transform;
2399
+ }
2400
+
2401
+ .bw-pane.is-active {
2402
+ position: relative;
2403
+ opacity: 1;
2404
+ transform: scale(1);
2405
+ pointer-events: auto;
2406
+ z-index: 1;
2407
+ }
2408
+
2409
+ .bw-pane.is-hidden {
2410
+ position: absolute;
2411
+ inset: 0;
2412
+ opacity: 0;
2413
+ transform: scale(0.97);
2414
+ pointer-events: none;
2415
+ transition-duration: 150ms;
2416
+ transition-timing-function: ease-out;
2417
+ }
2418
+ }
2419
+
2420
+ /* Mobile-only flips for the new pane wrappers: only enough to hide
2421
+ the desktop slots column copy and force the slots-mobile wrapper
2422
+ to render its children inline (the original 4-up grid below the
2423
+ calendar). The pane--details wrapper has no styling on mobile, so
2424
+ it's a transparent block container and the form renders exactly
2425
+ like it did pre-pane-restructure. */
2426
+ @media (max-width: 1024px) {
2427
+ .bw-pane--slots {
2428
+ display: none;
2429
+ }
2430
+
2431
+ .bw-back-btn {
2432
+ display: none;
2433
+ }
2434
+
2435
+ .bw-slots-mobile {
2436
+ display: contents;
2437
+ }
2438
+
2439
+ /* Details view is desktop-only; mobile uses the existing step-4
2440
+ right column for the form. */
2441
+ .bw-details-view {
2442
+ display: none;
2443
+ }
2444
+
2445
+ /* Mobile step transitions: when a step becomes active, the relevant
2446
+ content fades + slides up subtly. Same 250ms expo ease-out timing
2447
+ as desktop transitions so the product feels consistent across
2448
+ breakpoints. We target the step-specific content (not the col
2449
+ wrapper for steps 1/2 since col--left stays visible across
2450
+ them) so animations don't re-fire when the active col is shared
2451
+ between adjacent steps. */
2452
+ /* 4-step flow: steps 1+2 both share .bw-col--left, so we target
2453
+ the inner content (bw-step-2 for services, bw-step-1 for
2454
+ provider) so each step transitions independently. Steps 3+4
2455
+ have dedicated cols. */
2456
+ .bw-body[data-mobile-step='1'] .bw-step-2,
2457
+ .bw-body[data-mobile-step='2'] .bw-step-1,
2458
+ .bw-body[data-mobile-step='3'] .bw-col--center,
2459
+ .bw-body[data-mobile-step='4'] .bw-col--right {
2460
+ animation: bw-mobile-step-in 250ms cubic-bezier(0.16, 1, 0.3, 1);
2461
+ }
2462
+
2463
+ @keyframes bw-mobile-step-in {
2464
+ from {
2465
+ opacity: 0;
2466
+ transform: translateY(6px);
2467
+ }
2468
+ to {
2469
+ opacity: 1;
2470
+ transform: translateY(0);
2471
+ }
2472
+ }
2473
+ }
2474
+
2475
+ /* Desktop-only details view: 2-column summary | form layout shown
2476
+ when viewState === 'details'. Mobile keeps the step-based flow
2477
+ (bw-details-view is display: none on mobile, set above). */
2478
+ .bw-details-view {
2479
+ display: none;
2480
+ }
2481
+
2482
+ /* The .bw-content wrapper holds bw-body and bw-details-view. On
2483
+ mobile it's a transparent flex column passthrough. On desktop it
2484
+ becomes a positioning context so the inactive view can overlay
2485
+ during the crossfade without contributing its height. */
2486
+ .bw-content {
2487
+ display: flex;
2488
+ flex-direction: column;
2489
+ flex: 1;
2490
+ min-height: 0;
2491
+ }
2492
+
2493
+ @media (min-width: 1025px) {
2494
+ /* Stack the two panes in the same grid cell so they share space,
2495
+ but keep the inactive pane out of normal flow. Otherwise the
2496
+ services/calendar step reserves the much taller details form
2497
+ before the user has selected a time. */
2498
+ .bw-content {
2499
+ position: relative;
2500
+ display: grid;
2501
+ grid-template-areas: 'stack';
2502
+ align-items: start;
2503
+ flex: 0 1 auto;
2504
+ }
2505
+
2506
+ .bw-body,
2507
+ .bw-details-view {
2508
+ grid-area: stack;
2509
+ transition:
2510
+ opacity 250ms cubic-bezier(0.16, 1, 0.3, 1),
2511
+ transform 250ms cubic-bezier(0.16, 1, 0.3, 1);
2512
+ will-change: opacity, transform;
2513
+ }
2514
+
2515
+ .bw-body {
2516
+ position: relative;
2517
+ }
2518
+
2519
+ .bw-details-view {
2520
+ position: absolute;
2521
+ inset: 0;
2522
+ display: grid;
2523
+ grid-template-columns: 5fr 6fr;
2524
+ gap: 56px;
2525
+ padding: 32px 48px 48px;
2526
+ max-width: 1440px;
2527
+ width: 100%;
2528
+ margin: 0 auto;
2529
+ /* Default hidden state — overlaid on top of body. */
2530
+ opacity: 0;
2531
+ transform: scale(0.99);
2532
+ pointer-events: none;
2533
+ }
2534
+
2535
+ .bw[data-view-state='details'] .bw-body {
2536
+ position: absolute;
2537
+ inset: 0;
2538
+ opacity: 0;
2539
+ transform: scale(0.99);
2540
+ pointer-events: none;
2541
+ }
2542
+
2543
+ .bw[data-view-state='details'] .bw-details-view {
2544
+ position: relative;
2545
+ inset: auto;
2546
+ opacity: 1;
2547
+ transform: scale(1);
2548
+ pointer-events: auto;
2549
+ }
2550
+
2551
+ .bw-details-summary {
2552
+ display: flex;
2553
+ flex-direction: column;
2554
+ gap: 16px;
2555
+ padding-right: 8px;
2556
+ }
2557
+
2558
+ .bw-details-eyebrow {
2559
+ margin: 0;
2560
+ font-size: 12px;
2561
+ font-weight: 500;
2562
+ letter-spacing: 0.04em;
2563
+ color: var(--bw-text-muted);
2564
+ }
2565
+
2566
+ .bw-details-service {
2567
+ margin: 0;
2568
+ font-size: 24px;
2569
+ font-weight: 500;
2570
+ letter-spacing: -0.4px;
2571
+ line-height: 1.2;
2572
+ color: var(--bw-text);
2573
+ }
2574
+
2575
+ .bw-details-desc {
2576
+ margin: 0;
2577
+ font-size: 13px;
2578
+ line-height: 1.55;
2579
+ color: var(--bw-text-secondary);
2580
+ }
2581
+
2582
+ .bw-details-meta {
2583
+ display: flex;
2584
+ flex-direction: column;
2585
+ gap: 14px;
2586
+ margin-top: 8px;
2587
+ padding-top: 20px;
2588
+ border-top: 1px solid var(--bw-border-light);
2589
+ }
2590
+
2591
+ .bw-details-meta-row {
2592
+ display: flex;
2593
+ align-items: flex-start;
2594
+ gap: 12px;
2595
+ font-size: 13px;
2596
+ color: var(--bw-text);
2597
+ }
2598
+
2599
+ .bw-details-meta-icon {
2600
+ display: inline-flex;
2601
+ flex-shrink: 0;
2602
+ align-items: center;
2603
+ justify-content: center;
2604
+ width: 18px;
2605
+ height: 18px;
2606
+ color: var(--bw-text-muted);
2607
+ }
2608
+
2609
+ .bw-details-meta-icon svg {
2610
+ width: 14px;
2611
+ height: 14px;
2612
+ }
2613
+
2614
+ .bw-details-meta-text {
2615
+ display: flex;
2616
+ flex-direction: column;
2617
+ gap: 2px;
2618
+ min-width: 0;
2619
+ }
2620
+
2621
+ .bw-details-meta-label {
2622
+ font-size: 11px;
2623
+ color: var(--bw-text-muted);
2624
+ }
2625
+
2626
+ .bw-details-meta-value {
2627
+ font-size: 13px;
2628
+ color: var(--bw-text);
2629
+ font-variant-numeric: tabular-nums;
2630
+ }
2631
+
2632
+ .bw-details-meta-row--strike .bw-details-meta-value {
2633
+ text-decoration: line-through;
2634
+ opacity: 0.55;
2635
+ }
2636
+
2637
+ .bw-details-hold {
2638
+ color: var(--bw-text);
2639
+ }
2640
+
2641
+ .bw-details-form {
2642
+ display: flex;
2643
+ flex-direction: column;
2644
+ gap: 16px;
2645
+ }
2646
+
2647
+ /* Back link sits at the top of the summary pane, in the eyebrow
2648
+ position — replacing the previous static "Reschedule" / "Booking"
2649
+ label as the first thing the user reads on the left. The static
2650
+ eyebrow still renders just below as the section label for the
2651
+ summary stack. */
2652
+ .bw-details-summary .bw-back-btn--details {
2653
+ align-self: flex-start;
2654
+ margin-bottom: 16px;
2655
+ padding: 0;
2656
+ color: var(--bw-text-secondary);
2657
+ font-size: 14px;
2658
+ }
2659
+
2660
+ .bw-details-summary .bw-back-btn--details:hover {
2661
+ color: var(--bw-text);
2662
+ }
2663
+
2664
+ .bw-form--details {
2665
+ gap: 18px;
2666
+ }
2667
+
2668
+ /* Confirm sits alone at the bottom-right of the form column. */
2669
+ .bw-form--details .bw-confirm-btn {
2670
+ align-self: flex-end;
2671
+ margin-top: 8px;
2672
+ }
2673
+ }
2674
+
2675
+ .bw-intake {
2676
+ display: flex;
2677
+ flex-direction: column;
2678
+ gap: 14px;
2679
+ }
2680
+
2681
+ .bw-intake-section {
2682
+ display: flex;
2683
+ flex-direction: column;
2684
+ gap: 10px;
2685
+ }
2686
+
2687
+ .bw-intake-title {
2688
+ font-size: 12px;
2689
+ font-weight: 700;
2690
+ color: var(--bw-text);
2691
+ text-transform: uppercase;
2692
+ letter-spacing: 0.08em;
2693
+ }
2694
+
2695
+ .bw-field--wide {
2696
+ grid-column: 1 / -1;
2697
+ }
2698
+
2699
+ .bw .bw-field select {
2700
+ width: 100%;
2701
+ min-height: 40px;
2702
+ border: 1px solid var(--bw-border);
2703
+ border-radius: 10px;
2704
+ background: var(--bw-bg);
2705
+ color: var(--bw-text);
2706
+ padding: 0 12px;
2707
+ font: inherit;
2708
+ }
2709
+
2710
+ .bw-help {
2711
+ margin: 4px 0 0;
2712
+ font-size: 12px;
2713
+ color: var(--bw-text-muted);
2714
+ }
2715
+
2716
+ .bw-checkbox-field label {
2717
+ display: flex;
2718
+ align-items: flex-start;
2719
+ gap: 8px;
2720
+ font-size: 13px;
2721
+ line-height: 1.4;
2722
+ }
2723
+
2724
+ .bw-checkbox-field input {
2725
+ width: 16px;
2726
+ height: 16px;
2727
+ min-width: 16px;
2728
+ flex: 0 0 16px;
2729
+ margin-top: 1px;
2730
+ padding: 0;
2731
+ accent-color: var(--bw-primary);
2732
+ }
2733
+
2734
+ .bw-repeatable {
2735
+ display: flex;
2736
+ flex-direction: column;
2737
+ gap: 10px;
2738
+ }
2739
+
2740
+ .bw-repeatable-item {
2741
+ display: flex;
2742
+ flex-direction: column;
2743
+ gap: 10px;
2744
+ border: 1px solid var(--bw-border);
2745
+ border-radius: 12px;
2746
+ padding: 12px;
2747
+ background: color-mix(in srgb, var(--bw-bg) 85%, transparent);
2748
+ }
2749
+
2750
+ .bw-repeatable-head {
2751
+ display: flex;
2752
+ align-items: center;
2753
+ justify-content: space-between;
2754
+ gap: 10px;
2755
+ font-size: 13px;
2756
+ font-weight: 700;
2757
+ color: var(--bw-text);
2758
+ }
2759
+
2760
+ .bw-link-btn,
2761
+ .bw-secondary-btn {
2762
+ border: 0;
2763
+ background: transparent;
2764
+ color: var(--bw-primary);
2765
+ font: inherit;
2766
+ font-size: 13px;
2767
+ font-weight: 700;
2768
+ cursor: pointer;
2769
+ }
2770
+
2771
+ .bw-secondary-btn {
2772
+ align-self: flex-start;
2773
+ border: 1px solid var(--bw-border);
2774
+ border-radius: 999px;
2775
+ padding: 8px 12px;
2776
+ background: var(--bw-bg);
2777
+ }