@ticketboothapp/booking 1.2.189 → 1.2.191

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/package.json +3 -1
  2. package/src/components/BackgroundVideo.tsx +6 -3
  3. package/src/components/booking/AddOnsSection.module.css +129 -1
  4. package/src/components/booking/AddOnsSection.tsx +21 -25
  5. package/src/components/booking/AdminChangeBookingContent.tsx +1 -0
  6. package/src/components/booking/AdminChangeBookingFlow.tsx +1 -0
  7. package/src/components/booking/AdminChangeReceiptComparison.module.css +16 -0
  8. package/src/components/booking/AdminChangeReceiptComparison.tsx +14 -4
  9. package/src/components/booking/BookingDialog.module.css +43 -0
  10. package/src/components/booking/BookingFlowCollage.tsx +43 -16
  11. package/src/components/booking/Calendar.module.css +132 -13
  12. package/src/components/booking/Calendar.tsx +46 -20
  13. package/src/components/booking/CancellationPolicySelector.module.css +33 -4
  14. package/src/components/booking/CancellationPolicySelector.tsx +30 -28
  15. package/src/components/booking/ChangeBookingDialog.tsx +116 -55
  16. package/src/components/booking/ChangeBookingFlow.tsx +2 -0
  17. package/src/components/booking/ChangeBookingItineraryPanel.tsx +3 -0
  18. package/src/components/booking/CheckoutForm.module.css +7 -0
  19. package/src/components/booking/CollapsibleAddOnItem.module.css +4 -3
  20. package/src/components/booking/ItineraryBox.module.css +14 -0
  21. package/src/components/booking/ItineraryBox.tsx +75 -7
  22. package/src/components/booking/MealDrinkAddOnSelector.tsx +14 -16
  23. package/src/components/booking/NewBookingFlow.tsx +1 -0
  24. package/src/components/booking/PickupLocationDialog.tsx +19 -23
  25. package/src/components/booking/PickupLocationSelector.tsx +20 -25
  26. package/src/components/booking/PriceSummary.module.css +19 -0
  27. package/src/components/booking/PriceSummary.tsx +10 -7
  28. package/src/components/booking/PrivateShuttlePassengerSection.tsx +7 -1
  29. package/src/components/booking/ReturnTimeSelector.module.css +23 -4
  30. package/src/components/booking/ReturnTimeSelector.tsx +5 -5
  31. package/src/components/booking/StandardBookingItineraryPanel.tsx +3 -0
  32. package/src/components/booking/TicketSelector.module.css +30 -3
  33. package/src/components/booking/TicketSelector.tsx +6 -6
  34. package/src/components/booking/TourDescription.module.css +6 -1
  35. package/src/components/booking/TourDescription.tsx +25 -16
  36. package/src/components/booking/change-booking-compare.module.css +154 -0
  37. package/src/components/booking/change-booking-compare.tsx +141 -1
  38. package/src/constants/images.ts +1 -1
  39. package/src/hooks/useIsBookingLaunchLive.ts +4 -1
  40. package/src/index.ts +11 -0
  41. package/src/lib/booking/i18n/messages/en.json +2 -0
  42. package/src/lib/booking/i18n/messages/fr.json +2 -0
  43. package/src/lib/booking/pickup-location-disclaimer.ts +67 -0
  44. package/src/lib/booking-api.ts +1 -0
  45. package/src/lib/booking-constants.ts +1 -1
  46. package/src/strings/en.json +140 -68
  47. package/src/strings/es.json +181 -68
  48. package/src/strings/fr.json +181 -68
  49. package/test/pickup-location-disclaimer.test.ts +44 -0
  50. package/CHANGE_BOOKING_BE_HANDOFF.md +0 -160
@@ -33,6 +33,7 @@ import {
33
33
  } from '../../lib/booking/map-utils';
34
34
  import type { SearchedLocation, NearbyLocation } from '../../lib/booking/pickup-location-types';
35
35
  import { useTranslations } from '../../lib/booking/i18n';
36
+ import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
36
37
  import { useBookingApp } from '../../contexts/BookingAppContext';
37
38
  import styles from './PickupLocationSelector.module.css';
38
39
 
@@ -77,9 +78,6 @@ const SUGGESTION_HIDE_DELAY = 200; // ms
77
78
  const DEFAULT_MAP_ZOOM = 10;
78
79
  const SEARCHED_LOCATION_ZOOM = 14;
79
80
  const MAX_ZOOM = 15;
80
- const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
81
- const SAMSON_MALL_PARKING_DISCLAIMER =
82
- 'No public parking at this pickup location. This pickup location is meant to serve guests staying at the HI Lake Louise Alpine Hostel, Lake Louise Inn, or Lake Louise Campground and can walk to Samson Mall for pickup due to parking restrictions in the town of Lake Louise.';
83
81
 
84
82
  // Filter definitions
85
83
  const FILTERS = [
@@ -114,8 +112,21 @@ function hasFreeParking(location: PickupLocation): boolean {
114
112
  return location.name.toLowerCase().includes('free parking');
115
113
  }
116
114
 
117
- function hasSamsonMallParkingDisclaimer(location: Pick<PickupLocation, 'id' | 'name'>): boolean {
118
- return location.id === SAMSON_MALL_PICKUP_LOCATION_ID || location.name.trim().toLowerCase() === 'samson mall';
115
+ function PickupLocationDisclaimerNote({
116
+ location,
117
+ className,
118
+ }: {
119
+ location: Pick<PickupLocation, 'id' | 'name' | 'disclaimer'>;
120
+ className: string;
121
+ }) {
122
+ const disclaimer = getPickupLocationDisclaimer(location);
123
+ if (!disclaimer) return null;
124
+
125
+ return (
126
+ <p className={className} role="note">
127
+ {disclaimer.message}
128
+ </p>
129
+ );
119
130
  }
120
131
 
121
132
  // Filter locations based on selected filters (AND logic)
@@ -1213,11 +1224,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1213
1224
  )}
1214
1225
  </div>
1215
1226
  <p className="text-xs text-stone-600 truncate">{location.address}</p>
1216
- {hasSamsonMallParkingDisclaimer(location) && (
1217
- <p className={styles.parkingDisclaimer} role="note">
1218
- {SAMSON_MALL_PARKING_DISCLAIMER}
1219
- </p>
1220
- )}
1227
+ <PickupLocationDisclaimerNote location={location} className={styles.parkingDisclaimer} />
1221
1228
  {isNearby && searchedLocation && !exactMatchLocationId && !cityFilter && (
1222
1229
  <div className="flex items-center gap-2 mt-1 text-xs text-stone-500">
1223
1230
  <span>📍 {formatDistance((location as NearbyLocation).distance, useImperial)}</span>
@@ -1318,11 +1325,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1318
1325
  <p className="text-sm text-stone-600 mb-2">
1319
1326
  {location.address}
1320
1327
  </p>
1321
- {hasSamsonMallParkingDisclaimer(location) && (
1322
- <p className={styles.mapParkingDisclaimer}>
1323
- {SAMSON_MALL_PARKING_DISCLAIMER}
1324
- </p>
1325
- )}
1328
+ <PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
1326
1329
  <div className="text-xs text-stone-500 space-y-1">
1327
1330
  <p>
1328
1331
  📍 {formatDistance(location.distance, useImperial)} {t('pickup.away')}
@@ -1376,11 +1379,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1376
1379
  <p className="text-sm text-stone-600 mb-2">
1377
1380
  {location.address}
1378
1381
  </p>
1379
- {hasSamsonMallParkingDisclaimer(location) && (
1380
- <p className={styles.mapParkingDisclaimer}>
1381
- {SAMSON_MALL_PARKING_DISCLAIMER}
1382
- </p>
1383
- )}
1382
+ <PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
1384
1383
  {location.notes && (
1385
1384
  <p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
1386
1385
  {location.notes}
@@ -1438,11 +1437,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1438
1437
  <p className="text-sm text-stone-600 mb-2">
1439
1438
  {location.address}
1440
1439
  </p>
1441
- {hasSamsonMallParkingDisclaimer(location) && (
1442
- <p className={styles.mapParkingDisclaimer}>
1443
- {SAMSON_MALL_PARKING_DISCLAIMER}
1444
- </p>
1445
- )}
1440
+ <PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
1446
1441
  {location.notes && (
1447
1442
  <p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
1448
1443
  {location.notes}
@@ -5,3 +5,22 @@
5
5
  .ruleAbove {
6
6
  border-top: 1px solid var(--booking-stone-200, #e7e5e4);
7
7
  }
8
+
9
+ @media (max-width: 639px) {
10
+ .compactMobileTotal .totalRow {
11
+ align-items: flex-start;
12
+ font-size: 1rem !important;
13
+ line-height: 1.3 !important;
14
+ }
15
+
16
+ .compactMobileTotal .totalLabel {
17
+ overflow: visible !important;
18
+ white-space: normal !important;
19
+ text-overflow: clip !important;
20
+ }
21
+
22
+ .compactMobileTotal .totalAmount {
23
+ font-size: 1rem !important;
24
+ line-height: 1.3 !important;
25
+ }
26
+ }
@@ -84,6 +84,8 @@ export interface PriceSummaryProps {
84
84
  totalLabel?: string;
85
85
  /** Prefix a positive total with + when the row represents a signed change rather than an absolute total. */
86
86
  showPositiveTotalSign?: boolean;
87
+ /** Keep the total row compact and allow its label to wrap on narrow mobile screens. */
88
+ compactMobileTotal?: boolean;
87
89
  /** Render after the total row (e.g. provider price delta + keep-original-price) */
88
90
  extraAfterTotal?: ReactNode;
89
91
  /** Optional map of editable line input values by line key. */
@@ -188,6 +190,7 @@ export function PriceSummary({
188
190
  hideSubtotal = false,
189
191
  totalLabel,
190
192
  showPositiveTotalSign = false,
193
+ compactMobileTotal = false,
191
194
  extraAfterTotal,
192
195
  lineAmountInputs,
193
196
  lineLabelInputs,
@@ -218,7 +221,7 @@ export function PriceSummary({
218
221
  !embedSubtotalBeforeTaxInLines && extraBeforeSubtotal ? extraBeforeSubtotal : null;
219
222
 
220
223
  return (
221
- <div className={`space-y-2 min-w-0 ${className}`}>
224
+ <div className={`space-y-2 min-w-0 ${compactMobileTotal ? styles.compactMobileTotal : ''} ${className}`}>
222
225
  {lines.map((row, index) => {
223
226
  const isBeforeSubtotalBoundary = firstTaxLineIndex < 0 || index < firstTaxLineIndex;
224
227
  if (row.kind === 'ticket') {
@@ -375,11 +378,11 @@ export function PriceSummary({
375
378
  <div className="space-y-0">
376
379
  {depositMode ? (
377
380
  <>
378
- <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
379
- <span className="font-semibold text-stone-900 min-w-0 truncate">
381
+ <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
382
+ <span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
380
383
  {t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total'}
381
384
  </span>
382
- <span className="flex-shrink-0 whitespace-nowrap font-semibold text-stone-900">
385
+ <span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
383
386
  {formatCurrencyAmount(depositMode.fullTotalAmount, currency, locale)}
384
387
  </span>
385
388
  </div>
@@ -399,12 +402,12 @@ export function PriceSummary({
399
402
  )}
400
403
  </>
401
404
  ) : (
402
- <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
403
- <span className="font-semibold text-stone-900 min-w-0 truncate">
405
+ <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
406
+ <span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
404
407
  {totalLabel ??
405
408
  (t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total')}
406
409
  </span>
407
- <span className="flex-shrink-0 whitespace-nowrap font-semibold text-stone-900">
410
+ <span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
408
411
  {showPositiveTotalSign && total > 0.005 ? '+' : ''}
409
412
  {formatCurrencyAmount(total, currency, locale)}
410
413
  </span>
@@ -39,7 +39,13 @@ export function PrivateShuttlePassengerSection({
39
39
  Number of Passengers
40
40
  </label>
41
41
  <div className={styles.passengerBox}>
42
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
42
+ <div
43
+ className={
44
+ isSpecialRequestMode
45
+ ? 'flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between'
46
+ : 'flex items-center justify-between gap-4'
47
+ }
48
+ >
43
49
  <div>
44
50
  <div className="font-medium text-stone-900">Passengers</div>
45
51
  <div className="text-sm text-stone-600">
@@ -100,13 +100,17 @@
100
100
  }
101
101
 
102
102
  .content {
103
- display: flex;
103
+ display: grid;
104
+ grid-template-columns: minmax(0, 1fr) auto;
105
+ grid-template-rows: auto auto;
104
106
  align-items: center;
105
- justify-content: space-between;
107
+ column-gap: 1rem;
106
108
  }
107
109
 
108
110
  .details {
109
- flex: 1;
111
+ min-width: 0;
112
+ grid-column: 1;
113
+ grid-row: 1;
110
114
  }
111
115
 
112
116
  .time {
@@ -141,6 +145,8 @@
141
145
  }
142
146
 
143
147
  .staySummary {
148
+ grid-column: 1;
149
+ grid-row: 2;
144
150
  font-size: 0.75rem;
145
151
  color: var(--booking-stone-500, #78716c);
146
152
  margin-top: 0.25rem;
@@ -155,9 +161,22 @@
155
161
  }
156
162
 
157
163
  .price {
164
+ grid-column: 2;
165
+ grid-row: 1 / 3;
166
+ align-self: center;
158
167
  font-size: 0.875rem;
159
168
  font-weight: 600;
160
- margin-left: 1rem;
169
+ white-space: nowrap;
170
+ }
171
+
172
+ @media (max-width: 639px) {
173
+ .staySummary {
174
+ grid-column: 1 / -1;
175
+ }
176
+
177
+ .price {
178
+ grid-row: 1;
179
+ }
161
180
  }
162
181
 
163
182
  .priceIncluded {
@@ -115,11 +115,6 @@ export function ReturnTimeSelector({
115
115
  {t('calendar.spotsAvailable', { count: returnVacancies })}
116
116
  </div>
117
117
  ))}
118
- {staySummary && (
119
- <div className={styles.staySummary}>
120
- {staySummary}
121
- </div>
122
- )}
123
118
  {isSoldOut && (
124
119
  <div className={styles.soldOut}>
125
120
  {t('booking.soldOut')}
@@ -142,6 +137,11 @@ export function ReturnTimeSelector({
142
137
  `${formatCurrencyAmount(priceAdjustmentPerPerson, currency, locale as 'en' | 'fr')} per person`
143
138
  )}
144
139
  </div>
140
+ {staySummary && (
141
+ <div className={styles.staySummary}>
142
+ {staySummary}
143
+ </div>
144
+ )}
145
145
  </div>
146
146
  </button>
147
147
  );
@@ -14,6 +14,7 @@ import { ItineraryPlaceholder } from './ItineraryPlaceholder';
14
14
  type TranslationFn = (key: string, params?: Record<string, string>) => string;
15
15
 
16
16
  export interface StandardBookingItineraryPanelProps {
17
+ productId: string;
17
18
  selectedDate: string;
18
19
  hideItineraryBox: boolean;
19
20
  activeOptions: ProductOption[];
@@ -42,6 +43,7 @@ export interface StandardBookingItineraryPanelProps {
42
43
  }
43
44
 
44
45
  export function StandardBookingItineraryPanel({
46
+ productId,
45
47
  selectedDate,
46
48
  hideItineraryBox,
47
49
  activeOptions,
@@ -104,6 +106,7 @@ export function StandardBookingItineraryPanel({
104
106
 
105
107
  return (
106
108
  <ItineraryBox
109
+ productId={productId}
107
110
  selectedDate={selectedDate}
108
111
  formattedDate={formattedDate}
109
112
  itineraryItems={itineraryItems}
@@ -24,14 +24,22 @@
24
24
  }
25
25
 
26
26
  .row {
27
- display: flex;
27
+ display: grid;
28
+ grid-template-columns: minmax(0, 1fr) auto;
29
+ grid-template-rows: auto auto;
28
30
  align-items: center;
29
- justify-content: space-between;
31
+ column-gap: 1rem;
30
32
  padding: 1rem;
31
33
  background: var(--booking-stone-50, #fafaf9);
32
34
  border-radius: 0.5rem;
33
35
  }
34
36
 
37
+ .summary {
38
+ grid-column: 1;
39
+ grid-row: 1;
40
+ min-width: 0;
41
+ }
42
+
35
43
  .category {
36
44
  font-weight: 500;
37
45
  color: var(--booking-stone-900, #1c1917);
@@ -50,11 +58,12 @@
50
58
  }
51
59
 
52
60
  .finePrint {
61
+ grid-column: 1 / -1;
62
+ grid-row: 2;
53
63
  margin-top: 0.25rem;
54
64
  font-size: 0.7rem;
55
65
  line-height: 1.3;
56
66
  color: var(--booking-stone-500, #78716c);
57
- max-width: 16rem;
58
67
  }
59
68
 
60
69
  .priceStrikethrough {
@@ -68,11 +77,29 @@
68
77
  }
69
78
 
70
79
  .controls {
80
+ grid-column: 2;
81
+ grid-row: 1;
71
82
  display: flex;
72
83
  align-items: center;
73
84
  gap: 0.75rem;
74
85
  }
75
86
 
87
+ @media (max-width: 639px) {
88
+ .row {
89
+ row-gap: 0.75rem;
90
+ }
91
+
92
+ .finePrint {
93
+ margin-top: 0;
94
+ font-size: 0.75rem;
95
+ line-height: 1.375;
96
+ }
97
+
98
+ .controls {
99
+ grid-row: 1;
100
+ }
101
+ }
102
+
76
103
  .qtyBtn {
77
104
  width: 2.5rem;
78
105
  height: 2.5rem;
@@ -142,7 +142,7 @@ export function TicketSelector({
142
142
 
143
143
  return (
144
144
  <div key={rate.category} className={styles.row}>
145
- <div>
145
+ <div className={styles.summary}>
146
146
  <p className={styles.category}>
147
147
  {rate.category}
148
148
  {rate.category === 'CHILD' && (
@@ -169,11 +169,6 @@ export function TicketSelector({
169
169
  formatCurrencyAmount(effectiveUnitPrice, currency, locale as 'en' | 'fr')
170
170
  )}
171
171
  </p>
172
- {ticketFinePrintByCategory?.[rate.category] && (
173
- <p className={styles.finePrint}>
174
- {ticketFinePrintByCategory[rate.category]}
175
- </p>
176
- )}
177
172
  </div>
178
173
  <div className={styles.controls}>
179
174
  <button
@@ -195,6 +190,11 @@ export function TicketSelector({
195
190
  +
196
191
  </button>
197
192
  </div>
193
+ {ticketFinePrintByCategory?.[rate.category] && (
194
+ <p className={styles.finePrint}>
195
+ {ticketFinePrintByCategory[rate.category]}
196
+ </p>
197
+ )}
198
198
  </div>
199
199
  );
200
200
  })}
@@ -17,7 +17,7 @@
17
17
  border-radius: 0.5rem;
18
18
  border: none;
19
19
  cursor: pointer;
20
- font-family: 'Poppins', sans-serif !important;
20
+ font-family: 'Figtree', sans-serif !important;
21
21
  font-size: 1.125rem;
22
22
  font-weight: 700;
23
23
  text-transform: lowercase;
@@ -26,6 +26,10 @@
26
26
  transition: opacity 0.2s;
27
27
  }
28
28
 
29
+ .root .mainToggleStatic {
30
+ position: relative;
31
+ }
32
+
29
33
  .root .mainToggle:hover {
30
34
  opacity: 0.9;
31
35
  }
@@ -168,6 +172,7 @@
168
172
  background: none;
169
173
  border: none;
170
174
  cursor: pointer;
175
+ font-family: 'Figtree', sans-serif !important;
171
176
  font-size: 0.9375rem;
172
177
  font-weight: 700 !important;
173
178
  color: var(--accent-orange) !important;
@@ -41,6 +41,10 @@ export interface TourDescriptionProps {
41
41
  sections?: TourDescriptionSection[];
42
42
  /** Start expanded (e.g. when in partial/pre-launch state) */
43
43
  defaultExpanded?: boolean;
44
+ /** Keep the main toggle visible while scrolling inside booking dialogs. */
45
+ stickyToggle?: boolean;
46
+ /** Show the description content without a collapse control. */
47
+ alwaysExpanded?: boolean;
44
48
  }
45
49
 
46
50
  /** Items starting with • - * are bullet list; otherwise join as HTML (multi-line format) */
@@ -123,6 +127,8 @@ export function TourDescription({
123
127
  review: reviewProp,
124
128
  sections: sectionsProp,
125
129
  defaultExpanded = false,
130
+ stickyToggle = true,
131
+ alwaysExpanded = false,
126
132
  }: TourDescriptionProps) {
127
133
  const { catalog, slots } = useBookingHost();
128
134
  const PlusIcon = slots.PlusIcon;
@@ -154,6 +160,7 @@ export function TourDescription({
154
160
  const displayReview = review ?? null;
155
161
  const displaySections =
156
162
  sections.length > 0 ? sections : [];
163
+ const contentIsExpanded = alwaysExpanded || isExpanded;
157
164
 
158
165
  /** Group paragraphs: consecutive bullet items become a single <ul> block */
159
166
  const paragraphBlocks = useMemo(() => {
@@ -178,23 +185,25 @@ export function TourDescription({
178
185
 
179
186
  return (
180
187
  <div className={styles.root}>
181
- <button
182
- type="button"
183
- className={styles.mainToggle}
184
- onClick={() => setIsExpanded((e) => !e)}
185
- aria-expanded={isExpanded}
186
- >
187
- <span>{toggleLabel ?? t('booking.seeFullTourDescription')}</span>
188
- <span className={`${styles.toggleIcon} ${isExpanded ? styles.toggleIconExpanded : ''}`} aria-hidden>
189
- {isExpanded ? (
190
- <MinusIcon />
191
- ) : (
192
- <PlusIcon />
193
- )}
194
- </span>
195
- </button>
188
+ {!alwaysExpanded && (
189
+ <button
190
+ type="button"
191
+ className={`${styles.mainToggle} ${stickyToggle ? '' : styles.mainToggleStatic}`}
192
+ onClick={() => setIsExpanded((e) => !e)}
193
+ aria-expanded={isExpanded}
194
+ >
195
+ <span>{toggleLabel ?? t('booking.seeFullTourDescription')}</span>
196
+ <span className={`${styles.toggleIcon} ${isExpanded ? styles.toggleIconExpanded : ''}`} aria-hidden>
197
+ {isExpanded ? (
198
+ <MinusIcon />
199
+ ) : (
200
+ <PlusIcon />
201
+ )}
202
+ </span>
203
+ </button>
204
+ )}
196
205
 
197
- <div className={`${styles.contentWrapper} ${isExpanded ? styles.contentExpanded : ''}`}>
206
+ <div className={`${styles.contentWrapper} ${contentIsExpanded ? styles.contentExpanded : ''}`}>
198
207
  <div className={styles.content}>
199
208
  {paragraphBlocks.map((block, i) =>
200
209
  block.type === 'paragraph' ? (
@@ -95,3 +95,157 @@
95
95
  line-height: 1.2;
96
96
  color: var(--primary-text, var(--booking-text, #1c1917));
97
97
  }
98
+
99
+ .mobileDigest {
100
+ display: none;
101
+ }
102
+
103
+ @media (max-width: 767px) {
104
+ .kickerMobileHidden {
105
+ display: none;
106
+ }
107
+
108
+ .mobileDigest {
109
+ display: block;
110
+ padding: 0 0.25rem;
111
+ font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
112
+ }
113
+
114
+ .mobileDigest .mobileDigestToggle {
115
+ display: block;
116
+ width: 100%;
117
+ min-height: 2.75rem;
118
+ margin: 0;
119
+ padding: 0.625rem 0.75rem;
120
+ border: 0;
121
+ border-radius: 0.75rem;
122
+ background: transparent;
123
+ color: var(--primary-text, var(--booking-text, #1c1917));
124
+ font: inherit;
125
+ text-align: left;
126
+ cursor: pointer;
127
+ appearance: none;
128
+ }
129
+
130
+ .mobileDigest .mobileDigestToggle:focus-visible {
131
+ outline: 2px solid var(--accent-orange, #ff4d00);
132
+ outline-offset: 2px;
133
+ }
134
+
135
+ .mobileDigestExpanded .mobileDigestToggle {
136
+ border-radius: 0.75rem 0.75rem 0 0;
137
+ }
138
+
139
+ .mobileDigestHeading,
140
+ .mobileDigestAction,
141
+ .mobileDigestLine,
142
+ .mobileDigestTotalLine {
143
+ display: flex;
144
+ align-items: baseline;
145
+ }
146
+
147
+ .mobileDigestHeading {
148
+ justify-content: space-between;
149
+ gap: 0.75rem;
150
+ }
151
+
152
+ .mobileDigestKicker {
153
+ font-size: 0.625rem;
154
+ font-weight: 700;
155
+ letter-spacing: 0.07em;
156
+ line-height: 1.2;
157
+ text-transform: uppercase;
158
+ color: var(--grey-text, var(--booking-text-muted, #78716c));
159
+ }
160
+
161
+ .mobileDigestAction {
162
+ flex-shrink: 0;
163
+ gap: 0.125rem;
164
+ font-size: 0.75rem;
165
+ font-weight: 600;
166
+ line-height: 1.2;
167
+ color: var(--accent-orange, #ff4d00);
168
+ }
169
+
170
+ .mobileDigestChevron {
171
+ flex-shrink: 0;
172
+ align-self: center;
173
+ transition: transform 160ms ease;
174
+ }
175
+
176
+ .mobileDigestChevronExpanded {
177
+ transform: rotate(180deg);
178
+ }
179
+
180
+ .mobileDigestChanges {
181
+ display: grid;
182
+ gap: 0.125rem;
183
+ margin-top: 0.375rem;
184
+ font-size: 0.8125rem;
185
+ line-height: 1.3;
186
+ }
187
+
188
+ .mobileDigestLine {
189
+ min-width: 0;
190
+ }
191
+
192
+ .mobileDigestArrow {
193
+ margin: 0 0.35rem;
194
+ color: var(--grey-text, var(--booking-text-muted, #78716c));
195
+ }
196
+
197
+ .mobileDigestNewValue {
198
+ font-weight: 700;
199
+ }
200
+
201
+ .mobileDigestMuted {
202
+ color: var(--grey-text, var(--booking-text-muted, #78716c));
203
+ }
204
+
205
+ .mobileDigestTotalLine {
206
+ justify-content: space-between;
207
+ gap: 0.75rem;
208
+ padding-top: 0.125rem;
209
+ font-variant-numeric: tabular-nums;
210
+ }
211
+
212
+ .deltaIncrease,
213
+ .deltaDecrease,
214
+ .deltaSame {
215
+ flex-shrink: 0;
216
+ font-weight: 700;
217
+ }
218
+
219
+ .deltaIncrease {
220
+ color: var(--accent-orange, #ff4d00);
221
+ }
222
+
223
+ .deltaDecrease {
224
+ color: var(--booking-emerald-700, #047857);
225
+ }
226
+
227
+ .deltaSame {
228
+ color: var(--grey-text, var(--booking-text-muted, #78716c));
229
+ }
230
+
231
+ .mobileDigestBaseline {
232
+ display: block;
233
+ margin-top: 0.375rem;
234
+ font-size: 0.8125rem;
235
+ font-weight: 500;
236
+ line-height: 1.3;
237
+ font-variant-numeric: tabular-nums;
238
+ }
239
+
240
+ .visuallyHidden {
241
+ position: absolute;
242
+ width: 1px;
243
+ height: 1px;
244
+ padding: 0;
245
+ margin: -1px;
246
+ overflow: hidden;
247
+ clip: rect(0, 0, 0, 0);
248
+ white-space: nowrap;
249
+ border: 0;
250
+ }
251
+ }