@ticketboothapp/booking 1.2.190 → 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 (30) hide show
  1. package/package.json +3 -1
  2. package/src/components/BackgroundVideo.tsx +6 -3
  3. package/src/components/booking/AddOnsSection.module.css +6 -2
  4. package/src/components/booking/AddOnsSection.tsx +4 -4
  5. package/src/components/booking/AdminChangeReceiptComparison.module.css +16 -0
  6. package/src/components/booking/AdminChangeReceiptComparison.tsx +14 -4
  7. package/src/components/booking/BookingDialog.module.css +43 -0
  8. package/src/components/booking/BookingFlowCollage.tsx +43 -16
  9. package/src/components/booking/Calendar.module.css +8 -3
  10. package/src/components/booking/ChangeBookingDialog.tsx +116 -55
  11. package/src/components/booking/ChangeBookingFlow.tsx +1 -0
  12. package/src/components/booking/CheckoutForm.module.css +7 -0
  13. package/src/components/booking/CollapsibleAddOnItem.module.css +3 -2
  14. package/src/components/booking/MealDrinkAddOnSelector.tsx +4 -4
  15. package/src/components/booking/PickupLocationDialog.tsx +19 -23
  16. package/src/components/booking/PickupLocationSelector.tsx +20 -25
  17. package/src/components/booking/PriceSummary.module.css +19 -0
  18. package/src/components/booking/PriceSummary.tsx +10 -7
  19. package/src/components/booking/TourDescription.module.css +6 -1
  20. package/src/components/booking/TourDescription.tsx +25 -16
  21. package/src/components/booking/change-booking-compare.module.css +154 -0
  22. package/src/components/booking/change-booking-compare.tsx +141 -1
  23. package/src/constants/images.ts +1 -1
  24. package/src/index.ts +11 -0
  25. package/src/lib/booking/pickup-location-disclaimer.ts +67 -0
  26. package/src/lib/booking-api.ts +1 -0
  27. package/src/strings/en.json +140 -68
  28. package/src/strings/es.json +181 -68
  29. package/src/strings/fr.json +181 -68
  30. package/test/pickup-location-disclaimer.test.ts +44 -0
@@ -17,6 +17,11 @@
17
17
  display: flex;
18
18
  flex-direction: column;
19
19
  gap: 1rem;
20
+ min-width: 0;
21
+ }
22
+
23
+ .contactSection > * {
24
+ min-width: 0;
20
25
  }
21
26
 
22
27
  .contactGrid {
@@ -170,10 +175,12 @@
170
175
 
171
176
  .contactStaticValue {
172
177
  margin: 0;
178
+ max-width: 100%;
173
179
  font-size: 1rem;
174
180
  line-height: 1.4;
175
181
  color: var(--booking-stone-900, #1c1917);
176
182
  font-weight: 500;
183
+ overflow-wrap: anywhere;
177
184
  }
178
185
 
179
186
  .pickupSection {
@@ -1,6 +1,7 @@
1
1
  .card {
2
2
  overflow: hidden;
3
- border: 1px solid var(--booking-stone-200, #e7e5e4);
3
+ /* The scoped booking preflight can be emitted after CSS modules in local dev. */
4
+ border: 1px solid var(--booking-stone-200, #e7e5e4) !important;
4
5
  border-radius: 0.75rem;
5
6
  background: #fff;
6
7
  }
@@ -67,6 +68,6 @@
67
68
  }
68
69
 
69
70
  .content {
70
- border-top: 1px solid var(--booking-stone-100, #f5f5f4);
71
+ border-top: 1px solid var(--booking-stone-100, #f5f5f4) !important;
71
72
  padding: 0.75rem 0.875rem;
72
73
  }
@@ -280,7 +280,7 @@ export function MealDrinkAddOnSelector({
280
280
  updateSelections(next, drinks);
281
281
  }}
282
282
  disabled={qty <= 0 || !canDecrementTotal}
283
- className="h-8 w-8 rounded-full border border-stone-300 bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
283
+ className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
284
284
  >
285
285
 
286
286
  </button>
@@ -295,7 +295,7 @@ export function MealDrinkAddOnSelector({
295
295
  };
296
296
  updateSelections(next, drinks);
297
297
  }}
298
- className="h-8 w-8 rounded-full border border-stone-300 bg-white text-stone-600 hover:bg-stone-50 text-sm"
298
+ className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 text-sm`}
299
299
  >
300
300
  +
301
301
  </button>
@@ -338,7 +338,7 @@ export function MealDrinkAddOnSelector({
338
338
  });
339
339
  }}
340
340
  disabled={qty <= 0}
341
- className="h-8 w-8 rounded-full border border-stone-300 bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
341
+ className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
342
342
  >
343
343
 
344
344
  </button>
@@ -357,7 +357,7 @@ export function MealDrinkAddOnSelector({
357
357
  });
358
358
  }}
359
359
  disabled={otherQty <= 0}
360
- className="h-8 w-8 rounded-full border border-stone-300 bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
360
+ className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
361
361
  >
362
362
  +
363
363
  </button>
@@ -10,12 +10,9 @@ import {
10
10
  } from '../../lib/booking-api';
11
11
  import { PickupLocationSelector } from './PickupLocationSelector';
12
12
  import { useTranslations } from '../../lib/booking/i18n';
13
+ import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
13
14
  import styles from './PickupLocationDialog.module.css';
14
15
 
15
- const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
16
- const SAMSON_MALL_PARKING_DISCLAIMER =
17
- '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.';
18
-
19
16
  function findProductWithPickupLocations(
20
17
  products: Product[],
21
18
  bookingProductId: string
@@ -31,13 +28,6 @@ function findProductWithPickupLocations(
31
28
  return null;
32
29
  }
33
30
 
34
- function hasSamsonMallParkingDisclaimer(location: { id?: string | null; name?: string | null }): boolean {
35
- return (
36
- location.id === SAMSON_MALL_PICKUP_LOCATION_ID ||
37
- location.name?.trim().toLowerCase() === 'samson mall'
38
- );
39
- }
40
-
41
31
  /** Itinerary rows shown on manage-booking (may include optional label/time from API). */
42
32
  export interface PickupDialogItineraryStep {
43
33
  label?: string | null;
@@ -228,20 +218,26 @@ export function PickupLocationDialog({
228
218
  lastName,
229
219
  ]);
230
220
 
221
+ const currentPickupLocation = booking.pickupLocationId
222
+ ? pickupLocations.find((l) => l.id === booking.pickupLocationId) ?? null
223
+ : null;
231
224
  const currentPickupName =
232
225
  booking.travelerHotel ||
233
226
  (booking.pickupLocationId
234
- ? pickupLocations.find((l) => l.id === booking.pickupLocationId)?.name ?? booking.pickupLocationId
227
+ ? currentPickupLocation?.name ?? booking.pickupLocationId
235
228
  : null);
236
- const currentPickupRequiresParkingDisclaimer = hasSamsonMallParkingDisclaimer({
237
- id: booking.pickupLocationId,
238
- name: currentPickupName,
239
- });
229
+ const currentPickupDisclaimer = getPickupLocationDisclaimer(
230
+ currentPickupLocation ?? {
231
+ id: booking.pickupLocationId,
232
+ name: currentPickupName,
233
+ }
234
+ );
240
235
  const selectedPickupLocation = selectedLocationId
241
236
  ? pickupLocations.find((l) => l.id === selectedLocationId) ?? null
242
237
  : null;
243
- const selectedPickupRequiresParkingDisclaimer =
244
- selectedPickupLocation ? hasSamsonMallParkingDisclaimer(selectedPickupLocation) : false;
238
+ const selectedPickupDisclaimer = selectedPickupLocation
239
+ ? getPickupLocationDisclaimer(selectedPickupLocation)
240
+ : null;
245
241
 
246
242
  const itineraryDisplay = booking.itineraryDisplay ?? [];
247
243
  const pickupOrDropOffSteps = itineraryDisplay.filter(
@@ -332,9 +328,9 @@ export function PickupLocationDialog({
332
328
  {getDropoffTimeFromItinerary(itineraryDisplay)}
333
329
  </p>
334
330
  )}
335
- {currentPickupRequiresParkingDisclaimer && (
331
+ {currentPickupDisclaimer && (
336
332
  <p className={styles.parkingNotice} role="note">
337
- {SAMSON_MALL_PARKING_DISCLAIMER}
333
+ {currentPickupDisclaimer.message}
338
334
  </p>
339
335
  )}
340
336
  </div>
@@ -366,10 +362,10 @@ export function PickupLocationDialog({
366
362
  />
367
363
  </div>
368
364
 
369
- {selectedPickupRequiresParkingDisclaimer && (
365
+ {selectedPickupDisclaimer && (
370
366
  <div className={styles.selectedParkingNotice} role="note">
371
- <strong>Samson Mall pickup note</strong>
372
- <span>{SAMSON_MALL_PARKING_DISCLAIMER}</span>
367
+ <strong>{selectedPickupDisclaimer.title}</strong>
368
+ <span>{selectedPickupDisclaimer.message}</span>
373
369
  </div>
374
370
  )}
375
371
 
@@ -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>
@@ -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
+ }