@v-office/website-chat 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/{catalog-ClcCUjf7.js → catalog-Buxzskkg.js} +10 -4
- package/dist/catalog.d.ts +82 -2
- package/dist/catalog.js +2 -2
- package/dist/{icons-BK5Lk0cP.js → icons-B0_KM_uQ.js} +17 -1
- package/dist/index.d.ts +88 -3
- package/dist/index.js +484 -54
- package/dist/panel-B_wg4Wl0.js +2594 -0
- package/dist/{protocol-Bmb_uxVQ.d.ts → protocol-Dg1HZNWG.d.ts} +12 -1
- package/instructions/CHANGELOG.md +1 -0
- package/instructions/MIGRATION.md +1 -0
- package/instructions/versions/1.3.0/CHANGELOG.md +82 -0
- package/instructions/versions/1.3.0/MIGRATION.md +22 -0
- package/package.json +1 -1
- package/dist/panel-CfS3SuRX.js +0 -1396
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as MAX_MESSAGE_CHARS, t as CHAT_LOCALES } from "./protocol-BuNqZAMt.js";
|
|
2
|
-
import {
|
|
2
|
+
import { h as chatAvailable, l as Spark, p as loadConversation, t as Arrow } from "./icons-B0_KM_uQ.js";
|
|
3
3
|
import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -12,6 +12,9 @@ const DEFAULT_COPY = {
|
|
|
12
12
|
title: "Reiseassistent",
|
|
13
13
|
newConversation: "Neues Gespräch",
|
|
14
14
|
notice: "Sie chatten mit einer KI. Preise und Verfügbarkeit in den Karten kommen direkt aus unserem Buchungssystem.",
|
|
15
|
+
aiBadge: "KI",
|
|
16
|
+
noticeShort: "Preise aus dem Buchungssystem",
|
|
17
|
+
noticeMore: "Mehr zum KI-Hinweis",
|
|
15
18
|
greeting: "Hallo! Erzählen Sie mir einfach, wie Ihr Urlaub aussehen soll: wann, mit wem und was Ihnen wichtig ist. Ich suche passende Unterkünfte, prüfe freie Termine und Preise und zeige Ihnen, was passt.",
|
|
16
19
|
suggestions: [
|
|
17
20
|
"Sommerwoche für 2 Erwachsene und 2 Kinder",
|
|
@@ -19,9 +22,33 @@ const DEFAULT_COPY = {
|
|
|
19
22
|
"Ein Wochenende zu zweit mit Sauna",
|
|
20
23
|
"Was ist am nächsten Wochenende noch frei?"
|
|
21
24
|
],
|
|
22
|
-
placeholder: "
|
|
25
|
+
placeholder: "Ihre Reise beschreiben …",
|
|
23
26
|
send: "Senden",
|
|
24
27
|
thinking: "Die Antwort wird geschrieben",
|
|
28
|
+
status: {
|
|
29
|
+
search: "Suche passende Unterkünfte …",
|
|
30
|
+
rental: "Lese die Angaben zur Unterkunft …",
|
|
31
|
+
availability: "Prüfe freie Tage …",
|
|
32
|
+
quote: "Prüfe den Preis im Buchungssystem …",
|
|
33
|
+
reviews: "Lese die Bewertungen …"
|
|
34
|
+
},
|
|
35
|
+
retry: "Erneut senden",
|
|
36
|
+
privacyLink: "Datenschutzerklärung",
|
|
37
|
+
yourPick: "Ihre Auswahl",
|
|
38
|
+
yourTrip: "Ihre Reise",
|
|
39
|
+
assumed: "angenommen",
|
|
40
|
+
guests: {
|
|
41
|
+
one: "{n} Gast",
|
|
42
|
+
other: "{n} Gäste"
|
|
43
|
+
},
|
|
44
|
+
adultsShort: { other: "{n} Erw." },
|
|
45
|
+
weekend: "Wochenende",
|
|
46
|
+
tripStarters: {
|
|
47
|
+
party: "Wir sind ",
|
|
48
|
+
period: "Andere Reisezeit: ",
|
|
49
|
+
nights: "Andere Dauer: ",
|
|
50
|
+
wishes: "Andere Wünsche: "
|
|
51
|
+
},
|
|
25
52
|
yourMessage: "Ihre Nachricht",
|
|
26
53
|
consent: "Der Chat wird von einer KI beantwortet. Ihre Nachrichten werden dazu an unseren KI-Dienstleister übermittelt.",
|
|
27
54
|
consentAccept: "Chat starten",
|
|
@@ -31,7 +58,7 @@ const DEFAULT_COPY = {
|
|
|
31
58
|
contactLink: "Kontakt aufnehmen",
|
|
32
59
|
bookStay: "Zur Buchung",
|
|
33
60
|
viewRental: "Zur Unterkunft",
|
|
34
|
-
book: "
|
|
61
|
+
book: "Zur Buchung",
|
|
35
62
|
view: "Ansehen",
|
|
36
63
|
matchBadge: "Passt genau",
|
|
37
64
|
nights: {
|
|
@@ -39,8 +66,28 @@ const DEFAULT_COPY = {
|
|
|
39
66
|
other: "{n} Nächte"
|
|
40
67
|
},
|
|
41
68
|
total: "Gesamtpreis",
|
|
69
|
+
totalForNights: {
|
|
70
|
+
one: "Gesamtpreis für {n} Nacht",
|
|
71
|
+
other: "Gesamtpreis für {n} Nächte"
|
|
72
|
+
},
|
|
73
|
+
priceDetails: "Preisdetails",
|
|
74
|
+
dealFallback: "Angebot",
|
|
75
|
+
youSave: "Sie sparen {amount}",
|
|
76
|
+
freeCancellation: "Kostenlos stornierbar bis {date}",
|
|
77
|
+
nonRefundable: "Nicht erstattbar",
|
|
78
|
+
arrival: "Anreise",
|
|
79
|
+
departure: "Abreise",
|
|
80
|
+
continueToBooking: "Weiter zur Buchung",
|
|
81
|
+
nothingBookedYet: "Noch nichts gebucht. Auf der Buchungsseite:",
|
|
82
|
+
handoverSteps: [
|
|
83
|
+
"Kontaktdaten eingeben",
|
|
84
|
+
"Zahlungsart wählen",
|
|
85
|
+
"Bestätigung per E-Mail"
|
|
86
|
+
],
|
|
42
87
|
notBookable: "Für diesen Zeitraum nicht buchbar",
|
|
43
88
|
photo: "Foto",
|
|
89
|
+
openPhotos: "Fotos von {name} ansehen",
|
|
90
|
+
closePhotos: "Fotos schließen",
|
|
44
91
|
previous: "Zurück",
|
|
45
92
|
next: "Weiter",
|
|
46
93
|
rentalsFound: "Gefundene Unterkünfte",
|
|
@@ -54,6 +101,19 @@ const DEFAULT_COPY = {
|
|
|
54
101
|
booked: "belegt",
|
|
55
102
|
closed: "nicht buchbar",
|
|
56
103
|
yourStay: "Ihr Zeitraum",
|
|
104
|
+
arrivalPossible: "Anreise möglich",
|
|
105
|
+
cheaper: "günstiger",
|
|
106
|
+
dearer: "teurer",
|
|
107
|
+
showPrice: "Preis anzeigen",
|
|
108
|
+
arrivalOn: "Anreise {date}",
|
|
109
|
+
possibleFrom: "Ab {date} möglich",
|
|
110
|
+
until: "bis {date}",
|
|
111
|
+
otherArrival: "Anderen Anreisetag wählen",
|
|
112
|
+
otherArrivalStarter: "Anreise am ",
|
|
113
|
+
freePeriods: "Freie Termine",
|
|
114
|
+
freePeriodsIn: "Freie Termine im {month}",
|
|
115
|
+
datesDiffer: "Termine je Unterkunft verschieden",
|
|
116
|
+
otherDate: "anderer Termin",
|
|
57
117
|
compareRooms: "Räume",
|
|
58
118
|
compareHighlights: "Highlights",
|
|
59
119
|
compareRating: "Bewertung",
|
|
@@ -89,6 +149,9 @@ const DEFAULT_COPY = {
|
|
|
89
149
|
title: "Travel assistant",
|
|
90
150
|
newConversation: "New conversation",
|
|
91
151
|
notice: "You are chatting with an AI. Prices and availability in the cards come straight from our booking system.",
|
|
152
|
+
aiBadge: "AI",
|
|
153
|
+
noticeShort: "Prices from the booking system",
|
|
154
|
+
noticeMore: "About this AI chat",
|
|
92
155
|
greeting: "Hello! Just tell me what your holiday should look like: when, who with and what matters to you. I find rentals that fit, check free dates and prices and show you what suits.",
|
|
93
156
|
suggestions: [
|
|
94
157
|
"A summer week for 2 adults and 2 kids",
|
|
@@ -96,9 +159,33 @@ const DEFAULT_COPY = {
|
|
|
96
159
|
"A weekend for two with a sauna",
|
|
97
160
|
"Anything free next weekend?"
|
|
98
161
|
],
|
|
99
|
-
placeholder: "
|
|
162
|
+
placeholder: "Describe your trip …",
|
|
100
163
|
send: "Send",
|
|
101
164
|
thinking: "Writing the answer",
|
|
165
|
+
status: {
|
|
166
|
+
search: "Searching rentals …",
|
|
167
|
+
rental: "Reading about the rental …",
|
|
168
|
+
availability: "Checking free days …",
|
|
169
|
+
quote: "Checking the price with the booking system …",
|
|
170
|
+
reviews: "Reading reviews …"
|
|
171
|
+
},
|
|
172
|
+
retry: "Send again",
|
|
173
|
+
privacyLink: "Privacy policy",
|
|
174
|
+
yourPick: "Your choice",
|
|
175
|
+
yourTrip: "Your trip",
|
|
176
|
+
assumed: "assumed",
|
|
177
|
+
guests: {
|
|
178
|
+
one: "{n} guest",
|
|
179
|
+
other: "{n} guests"
|
|
180
|
+
},
|
|
181
|
+
adultsShort: { other: "{n} ad." },
|
|
182
|
+
weekend: "Weekend",
|
|
183
|
+
tripStarters: {
|
|
184
|
+
party: "We are ",
|
|
185
|
+
period: "Other dates: ",
|
|
186
|
+
nights: "Other length: ",
|
|
187
|
+
wishes: "Other wishes: "
|
|
188
|
+
},
|
|
102
189
|
yourMessage: "Your message",
|
|
103
190
|
consent: "The chat is answered by an AI. Your messages are sent to our AI provider for that.",
|
|
104
191
|
consentAccept: "Start chat",
|
|
@@ -116,8 +203,28 @@ const DEFAULT_COPY = {
|
|
|
116
203
|
other: "{n} nights"
|
|
117
204
|
},
|
|
118
205
|
total: "Total",
|
|
206
|
+
totalForNights: {
|
|
207
|
+
one: "Total for {n} night",
|
|
208
|
+
other: "Total for {n} nights"
|
|
209
|
+
},
|
|
210
|
+
priceDetails: "Price details",
|
|
211
|
+
dealFallback: "Offer",
|
|
212
|
+
youSave: "You save {amount}",
|
|
213
|
+
freeCancellation: "Free cancellation until {date}",
|
|
214
|
+
nonRefundable: "Non-refundable",
|
|
215
|
+
arrival: "Arrival",
|
|
216
|
+
departure: "Departure",
|
|
217
|
+
continueToBooking: "Continue to booking",
|
|
218
|
+
nothingBookedYet: "Nothing is booked yet. On the booking page:",
|
|
219
|
+
handoverSteps: [
|
|
220
|
+
"Enter your details",
|
|
221
|
+
"Choose how to pay",
|
|
222
|
+
"Confirmation by email"
|
|
223
|
+
],
|
|
119
224
|
notBookable: "Not bookable for these dates",
|
|
120
225
|
photo: "Photo",
|
|
226
|
+
openPhotos: "View photos of {name}",
|
|
227
|
+
closePhotos: "Close photos",
|
|
121
228
|
previous: "Previous",
|
|
122
229
|
next: "Next",
|
|
123
230
|
rentalsFound: "Rentals found",
|
|
@@ -131,6 +238,19 @@ const DEFAULT_COPY = {
|
|
|
131
238
|
booked: "taken",
|
|
132
239
|
closed: "not bookable",
|
|
133
240
|
yourStay: "Your stay",
|
|
241
|
+
arrivalPossible: "Arrival possible",
|
|
242
|
+
cheaper: "cheaper",
|
|
243
|
+
dearer: "dearer",
|
|
244
|
+
showPrice: "Show price",
|
|
245
|
+
arrivalOn: "Arrival {date}",
|
|
246
|
+
possibleFrom: "Possible from {date}",
|
|
247
|
+
until: "until {date}",
|
|
248
|
+
otherArrival: "Choose another arrival",
|
|
249
|
+
otherArrivalStarter: "Arrival on ",
|
|
250
|
+
freePeriods: "Free dates",
|
|
251
|
+
freePeriodsIn: "Free dates in {month}",
|
|
252
|
+
datesDiffer: "Dates differ per rental",
|
|
253
|
+
otherDate: "other dates",
|
|
134
254
|
compareRooms: "Rooms",
|
|
135
255
|
compareHighlights: "Highlights",
|
|
136
256
|
compareRating: "Rating",
|
|
@@ -267,10 +387,17 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
267
387
|
--chat-round: calc(var(--chat-base-radius) * 100);
|
|
268
388
|
--chat-shadow: 0 18px 48px rgb(15 23 42 / 0.18);
|
|
269
389
|
--chat-card-shadow: 0 1px 3px rgb(15 23 42 / 0.08), 0 8px 24px rgb(15 23 42 / 0.06);
|
|
390
|
+
/* The stars' colour: dark amber on a light surface, light amber on a dark one, AA on both. */
|
|
391
|
+
--chat-rating: #8a5a00;
|
|
270
392
|
/* Not set here, so a site's value reaches the widget, on wide screens (from 1024px): \`--chat-top\`
|
|
271
393
|
keeps a sticky header uncovered, \`--chat-width\` widens the panel. A phone's sheet covers the
|
|
272
394
|
header: the panel is modal, so the header could not be tapped and would only cost height. */
|
|
273
395
|
}
|
|
396
|
+
@supports (color: oklch(from red calc(l + 0.1) c h)) {
|
|
397
|
+
:host { --chat-rating: oklch(from var(--chat-surface) calc(0.52 + (1 - l) * 0.35) 0.13 75);
|
|
398
|
+
/* Lines inside a raised dark card: the site's border lifted as the card is (cards below). */
|
|
399
|
+
--chat-card-border: oklch(from var(--chat-border) calc(l + max(0, 0.5 - l) * 0.6) c h); }
|
|
400
|
+
}
|
|
274
401
|
* { box-sizing: border-box; }
|
|
275
402
|
.chat { color: var(--chat-text); font-size: 15px; line-height: 1.45; }
|
|
276
403
|
/* One focus ring for every control, in the site's primary; a click or a tap shows none. Without
|
|
@@ -332,10 +459,29 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
332
459
|
width: min(var(--chat-width, 700px), calc(100vw - 40px)); border-radius: var(--chat-radius); }
|
|
333
460
|
[data-position="left"] .panel { right: auto; left: 20px; }
|
|
334
461
|
}
|
|
335
|
-
.header { display: flex; align-items: center; gap: 8px; padding: 12px 24px 4px; }
|
|
462
|
+
.header { position: relative; display: flex; align-items: center; gap: 8px; padding: 12px 24px 4px; }
|
|
336
463
|
@media (min-width: 1024px) { .header { padding-top: 20px; } }
|
|
337
|
-
|
|
338
|
-
|
|
464
|
+
/* Once the conversation runs: one line under the title instead of the notice, the list gets the room. */
|
|
465
|
+
.header[data-slim] { padding: 10px 16px 10px 24px; border-bottom: 1px solid var(--chat-border); }
|
|
466
|
+
/* A small phone keeps the line on one line: less inset, and an ellipsis before it would wrap. */
|
|
467
|
+
@media (max-width: 359px) { .header[data-slim] { padding-inline: 16px 12px; } }
|
|
468
|
+
.title-block { margin-right: auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
469
|
+
.title { margin: 0; display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600;
|
|
470
|
+
letter-spacing: 0.16em; text-transform: uppercase; color: var(--chat-accent); }
|
|
471
|
+
/* The persistent AI mark (AI Act transparency), in words, not colour. */
|
|
472
|
+
.badge { padding: 0 6px; border: 1px solid var(--chat-tint-edge); border-radius: min(6px, var(--chat-radius));
|
|
473
|
+
background: var(--chat-secondary); color: var(--chat-on-secondary); font-size: 12px; letter-spacing: 0.04em; }
|
|
474
|
+
.notice-short { margin: 0; display: flex; align-items: center; gap: 4px; min-width: 0; font-size: 12px;
|
|
475
|
+
color: var(--chat-muted); }
|
|
476
|
+
.notice-short > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
477
|
+
/* A small mark with a finger's target: the pseudo-element takes the tap around it. */
|
|
478
|
+
.info { position: relative; display: grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0;
|
|
479
|
+
border-radius: 999px; background: transparent; color: inherit; cursor: pointer; }
|
|
480
|
+
.info::after { content: ""; position: absolute; inset: -12px; }
|
|
481
|
+
.info svg { width: 14px; height: 14px; }
|
|
482
|
+
.notice-pop { position: absolute; z-index: 2; top: calc(100% + 6px); left: 16px; right: 16px; margin: 0;
|
|
483
|
+
padding: 12px 16px; border: 1px solid var(--chat-border); border-radius: calc(var(--chat-radius) * 0.75);
|
|
484
|
+
background: var(--chat-surface); box-shadow: var(--chat-shadow); font-size: 13px; color: var(--chat-text); }
|
|
339
485
|
.icon-button { flex: none; display: grid; place-items: center; width: 40px; height: 40px; padding: 0;
|
|
340
486
|
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
341
487
|
color: var(--chat-text); cursor: pointer; }
|
|
@@ -359,25 +505,30 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
359
505
|
.thinking { display: flex; align-items: center; gap: 5px; padding: 4px 2px; }
|
|
360
506
|
.thinking-label { margin-left: 6px; color: var(--chat-muted); font-size: 14px; }
|
|
361
507
|
.thinking i { width: 7px; height: 7px; border-radius: 999px; background: var(--chat-accent); opacity: 0.5; }
|
|
362
|
-
.dock { position: relative; padding:
|
|
363
|
-
background: var(--chat-surface-soft); }
|
|
508
|
+
.dock { position: relative; padding: 8px 12px calc(14px + env(safe-area-inset-bottom, 0px));
|
|
509
|
+
background: var(--chat-surface-soft); container: dock / inline-size; }
|
|
364
510
|
.dock::before { content: ""; position: absolute; left: 0; right: 0; top: -28px; height: 28px;
|
|
365
511
|
pointer-events: none; background: linear-gradient(to top, var(--chat-surface-soft), transparent); }
|
|
366
512
|
/* Suggestions wrap, on a phone too: in a row that scrolls, one cut chip hides the others. */
|
|
367
|
-
.suggestions { display: flex; flex-wrap: wrap; gap: 8px; margin:
|
|
513
|
+
.suggestions { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 8px 10px; }
|
|
368
514
|
/* Suggestions and quick replies: filled pills; a suggestion has an icon for its topic in the accent
|
|
369
515
|
colour (a quick reply is the model's words, an icon guessed from them can be wrong). */
|
|
370
516
|
.suggestion, .chip { flex: none; max-width: 100%; display: inline-flex; align-items: center; gap: 8px;
|
|
371
517
|
border: 1px solid var(--chat-tint-edge); border-radius: var(--chat-round); background: var(--chat-secondary);
|
|
372
518
|
color: var(--chat-on-secondary); padding: 8px 16px 8px 12px; font: inherit; font-size: 14px;
|
|
373
519
|
font-weight: 600; line-height: 1.35; text-align: left; cursor: pointer; }
|
|
520
|
+
/* A quick reply is a tinted pill, never outlined: outlined is a link out of the chat (ContactLink). The
|
|
521
|
+
one the visitor took goes quiet. */
|
|
374
522
|
.chip { padding-left: 16px; }
|
|
523
|
+
.chip[data-chosen] { border-color: var(--chat-border); background: transparent; color: var(--chat-muted);
|
|
524
|
+
cursor: default; }
|
|
375
525
|
.suggestion svg { flex: none; color: var(--chat-accent); }
|
|
376
526
|
.composer { display: flex; align-items: flex-end; gap: 8px; padding: 6px 6px 6px 18px;
|
|
377
527
|
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
378
528
|
box-shadow: 0 4px 16px rgb(15 23 42 / 0.08); }
|
|
379
|
-
/* The field's ring, not the send button's too (that one has its own)
|
|
380
|
-
|
|
529
|
+
/* The field's ring, not the send button's too (that one has its own), and only for a keyboard: a text
|
|
530
|
+
field matches :focus-visible after a tap as well. */
|
|
531
|
+
.composer[data-keyboard]:has(textarea:focus-visible) { outline: 2px solid var(--chat-primary); outline-offset: 2px; }
|
|
381
532
|
/* 16px keeps iOS from zooming into the field. */
|
|
382
533
|
.composer textarea { flex: 1; min-width: 0; min-height: 36px; max-height: 128px; resize: none; border: 0;
|
|
383
534
|
outline: none; background: transparent; padding: 6px 0; font: inherit; font-size: 16px;
|
|
@@ -387,6 +538,36 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
387
538
|
.send { flex: none; display: grid; place-items: center; width: 36px; height: 36px; padding: 0; border: 0;
|
|
388
539
|
border-radius: var(--chat-round); background: var(--chat-primary); color: var(--chat-on-primary); cursor: pointer; }
|
|
389
540
|
.send:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
541
|
+
/* The bar over the input: the stay in reach with its total and button, or what the chat searched with.
|
|
542
|
+
The composer's shape, one thumb above it. */
|
|
543
|
+
.dock-bar { display: flex; align-items: center; gap: 8px; min-height: 50px; margin-bottom: 8px; padding: 3px 3px 3px 16px;
|
|
544
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
545
|
+
box-shadow: 0 4px 16px rgb(15 23 42 / 0.08); }
|
|
546
|
+
.dock-bar .thumb { display: none; width: 40px; height: 40px; margin-left: -10px; border-radius: 999px; }
|
|
547
|
+
/* A wide panel has room for the photo; a phone needs the width for the party. */
|
|
548
|
+
@container dock (min-width: 456px) { .dock-bar .thumb { display: block; } }
|
|
549
|
+
.dock-text { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; padding: 0;
|
|
550
|
+
border: 0; background: none; color: inherit; font: inherit; line-height: 1.3; text-align: left; cursor: pointer; }
|
|
551
|
+
.dock-name, .dock-sub { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
552
|
+
.dock-name { font-size: 14px; font-weight: 600; }
|
|
553
|
+
.dock-sub { font-size: 12px; color: var(--chat-muted); }
|
|
554
|
+
.dock-sub .price { color: var(--chat-text); }
|
|
555
|
+
/* Narrower than a card's button: the bar's width goes to the party. */
|
|
556
|
+
.dock-bar .button.compact { margin: 0; min-height: 42px; padding: 0 11px; font-size: 13px; }
|
|
557
|
+
.dock-bar[data-kind="trip"] { padding: 3px 0 3px 6px; }
|
|
558
|
+
.trip-chips { flex: 1; min-width: 0; display: flex; gap: 4px; padding-right: 16px; overflow-x: auto;
|
|
559
|
+
scrollbar-width: none; mask-image: linear-gradient(to left, transparent, #000 16px); }
|
|
560
|
+
.trip-chips::-webkit-scrollbar { display: none; }
|
|
561
|
+
.trip-chip { flex: none; display: inline-flex; align-items: center; gap: 5px; min-height: 36px; padding: 0 10px;
|
|
562
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: transparent;
|
|
563
|
+
color: var(--chat-text); font: inherit; font-size: 13px; font-weight: 600; white-space: nowrap; cursor: pointer; }
|
|
564
|
+
/* Inside the row that scrolls, so it clips: the ring sits inside the chip. */
|
|
565
|
+
.trip-chip:focus-visible { outline-offset: -2px; }
|
|
566
|
+
/* A party the search assumed: dashed, and said in words. */
|
|
567
|
+
.trip-chip[data-assumed] { border-style: dashed; border-color: var(--chat-muted); }
|
|
568
|
+
.trip-chip em { font-style: normal; font-weight: 500; color: var(--chat-muted); }
|
|
569
|
+
/* The bar's stay, brought into view in the list: marked for a moment. */
|
|
570
|
+
[data-pulse] { outline: 2px solid var(--chat-primary); outline-offset: 2px; }
|
|
390
571
|
.consent { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 14px;
|
|
391
572
|
background: var(--chat-surface-soft); }
|
|
392
573
|
.consent p { margin: 0; }
|
|
@@ -410,8 +591,26 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
410
591
|
.gallery-wrap, .carousel-wrap { position: relative; }
|
|
411
592
|
.gallery, .carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
|
|
412
593
|
.gallery::-webkit-scrollbar, .carousel::-webkit-scrollbar { display: none; }
|
|
413
|
-
.gallery img { flex: 0 0 100%; scroll-snap-align: start; display: block; width: 100%;
|
|
414
|
-
height: auto; object-fit: cover; }
|
|
594
|
+
.gallery img, .photo-open { flex: 0 0 100%; scroll-snap-align: start; display: block; width: 100%;
|
|
595
|
+
aspect-ratio: 4 / 3; height: auto; object-fit: cover; }
|
|
596
|
+
/* Flatter in a row, to bring a card's price above the input on a phone: 5:3, and 2:1 on a card with
|
|
597
|
+
free periods; the single card keeps 4:3. */
|
|
598
|
+
.carousel :is(.gallery > img, .photo-open), .carousel .no-photo { aspect-ratio: 5 / 3; }
|
|
599
|
+
.carousel .rental-card:has(.periods) :is(.gallery > img, .photo-open, .no-photo) { aspect-ratio: 2 / 1; }
|
|
600
|
+
/* A card's photo opens the photos large. */
|
|
601
|
+
.photo-open { padding: 0; border: 0; background: none; cursor: zoom-in; }
|
|
602
|
+
.photo-open img { aspect-ratio: auto; height: 100%; }
|
|
603
|
+
.photo-open:focus-visible { outline-offset: -3px; }
|
|
604
|
+
.photo-count { position: absolute; top: 10px; right: 10px; padding: 3px 10px; border-radius: var(--chat-round);
|
|
605
|
+
background: rgb(15 23 42 / 0.55); color: #fff; font-size: 12px; font-weight: 600;
|
|
606
|
+
font-variant-numeric: tabular-nums; pointer-events: none; }
|
|
607
|
+
/* Place and score on the photo's foot, white on a dark gradient whatever the photo. */
|
|
608
|
+
.photo-meta { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: flex-end;
|
|
609
|
+
justify-content: space-between; gap: 8px; padding: 36px 16px 12px; pointer-events: none;
|
|
610
|
+
background: linear-gradient(transparent, rgb(15 23 42 / 0.62)); color: #fff; font-size: 14px; font-weight: 600; }
|
|
611
|
+
.photo-rating { flex: none; }
|
|
612
|
+
.photo-rating .star { color: #ffd166; }
|
|
613
|
+
.photo-rating small { font-size: 12px; font-weight: 500; opacity: 0.9; }
|
|
415
614
|
/* Dots say which photo shows; arrows come up under a pointer and on keyboard focus (a finger swipes:
|
|
416
615
|
on a touch screen they take no tap, so a tap on a photo's edge does not turn it unseen). */
|
|
417
616
|
.dots { position: absolute; left: 50%; bottom: 10px; translate: -50% 0; display: flex; gap: 4px; }
|
|
@@ -434,25 +633,86 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
434
633
|
@container (min-width: 520px) {
|
|
435
634
|
.carousel > .card { flex-basis: calc((100% - 24px) / 2.15); }
|
|
436
635
|
.carousel:has(> .card:nth-child(2):last-child) > .card { flex-basis: calc((100% - 12px) / 2); }
|
|
437
|
-
.carousel-wrap:has(> .carousel > .card:nth-child(2):last-child) > .snap-nav
|
|
636
|
+
.carousel-wrap:has(> .carousel > .card:nth-child(2):last-child) > .snap-nav > :not(.snap-caption) {
|
|
637
|
+
display: none; }
|
|
438
638
|
/* The count follows the first card in view, and two show at once: it would skip numbers. */
|
|
439
|
-
.carousel-wrap > .snap-nav >
|
|
639
|
+
.carousel-wrap > .snap-nav > .snap-count { display: none; }
|
|
440
640
|
}
|
|
441
|
-
/* The row's controls, grouped at its end: "‹ 2 / 8 ›"
|
|
641
|
+
/* The row's controls, grouped at its end: "‹ 2 / 8 ›" under a pointer, dots under a finger; a caption
|
|
642
|
+
at its start. */
|
|
442
643
|
.snap-nav { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 6px; }
|
|
644
|
+
.snap-caption { flex: 1; min-width: 0; color: var(--chat-muted); font-size: 13px; }
|
|
645
|
+
.snap-dots { display: none; gap: 5px; }
|
|
646
|
+
.snap-dots i { width: 7px; height: 7px; border-radius: 999px; background: var(--chat-border); }
|
|
647
|
+
.snap-dots i[data-current] { width: 18px; background: var(--chat-primary); }
|
|
648
|
+
@media (pointer: coarse) {
|
|
649
|
+
.snap-dots { display: flex; }
|
|
650
|
+
.snap-nav > button.snap-arrow, .snap-count { display: none; }
|
|
651
|
+
}
|
|
652
|
+
/* More cards beyond an edge fade out there. Cards snap to the row's start, so in the middle of the row
|
|
653
|
+
nothing of the previous card shows: only the end fades, never the card being read. At the row's end
|
|
654
|
+
the previous card peeks in, and its edge fades. */
|
|
655
|
+
.carousel:is([data-fade="end"], [data-fade="both"]) { mask-image: linear-gradient(to left, transparent, #000 44px); }
|
|
656
|
+
.carousel[data-fade="start"] { mask-image: linear-gradient(to right, transparent, #000 44px); }
|
|
443
657
|
.snap-nav button, .calendar-head button { flex: none; display: grid; place-items: center; width: 36px;
|
|
444
658
|
height: 36px; padding: 0; border: 1px solid var(--chat-border); background: var(--chat-surface);
|
|
445
659
|
color: var(--chat-text); border-radius: var(--chat-round); cursor: pointer; }
|
|
446
660
|
.snap-nav button:disabled, .calendar-head button:disabled { opacity: 0.5; cursor: default; }
|
|
447
|
-
.rating { color:
|
|
448
|
-
.breakdown .line { display: flex; justify-content: space-between; gap: 12px;
|
|
449
|
-
|
|
661
|
+
.rating { color: var(--chat-rating); font-weight: 700; }
|
|
662
|
+
.breakdown .line, .price-details .line { display: flex; justify-content: space-between; gap: 12px;
|
|
663
|
+
font-variant-numeric: tabular-nums; }
|
|
664
|
+
.breakdown .total, .price-details .total { border-top: 1px solid var(--chat-border); padding-top: 6px; margin-top: 2px;
|
|
450
665
|
font-weight: 700; }
|
|
451
|
-
.
|
|
452
|
-
|
|
453
|
-
.chip
|
|
454
|
-
|
|
455
|
-
|
|
666
|
+
.quick-replies { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
667
|
+
/* A chip on its own in a reply (a refused stay's "Anderen Anreisetag wählen"): as wide as its words. */
|
|
668
|
+
.ui > .chip { align-self: flex-start; }
|
|
669
|
+
/* Rows that choose or ask: other free periods (a radio mark, the total right-aligned), a refused stay's
|
|
670
|
+
allowed lengths ("Preis anzeigen"). Full width, a finger's height. */
|
|
671
|
+
.rows-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px;
|
|
672
|
+
border-bottom: 1px solid var(--chat-border); }
|
|
673
|
+
.rows-head > div { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
|
|
674
|
+
.rows-head strong { font-weight: 600; }
|
|
675
|
+
.rows-head .thumb { width: 40px; height: 40px; }
|
|
676
|
+
.row { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 60px; padding: 8px 16px;
|
|
677
|
+
border: 0; border-top: 1px solid var(--chat-border); background: transparent; color: inherit; font: inherit;
|
|
678
|
+
text-align: left; cursor: pointer; }
|
|
679
|
+
.rows > .row:first-child { border-top: 0; }
|
|
680
|
+
.row:disabled { cursor: default; }
|
|
681
|
+
/* Inside the card, which clips: the ring sits inside the row. */
|
|
682
|
+
.row:focus-visible { outline-offset: -2px; }
|
|
683
|
+
.row[aria-pressed="true"] { background: var(--chat-secondary); box-shadow: inset 3px 0 0 var(--chat-primary); }
|
|
684
|
+
.row-when { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
|
|
685
|
+
.row-when strong { font-weight: 600; }
|
|
686
|
+
.row-when span { color: var(--chat-muted); font-size: 13px; }
|
|
687
|
+
.row > .price { flex: none; white-space: nowrap; }
|
|
688
|
+
/* The nights and a discount's badge: side by side, or the badge under the nights where the row is narrow. */
|
|
689
|
+
.row-when > span:has(.deal) { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; }
|
|
690
|
+
.row-when .deal { color: var(--chat-on-secondary); }
|
|
691
|
+
.row-go { display: inline-flex; align-items: center; gap: 4px; color: var(--chat-primary); font-size: 14px;
|
|
692
|
+
font-weight: 600; white-space: nowrap; }
|
|
693
|
+
.radio { flex: none; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
|
|
694
|
+
border: 1.5px solid var(--chat-muted); }
|
|
695
|
+
.row[aria-pressed="true"] .radio { border-color: var(--chat-primary); background: var(--chat-primary);
|
|
696
|
+
color: var(--chat-on-primary); }
|
|
697
|
+
.refused .card-body { padding-bottom: 12px; }
|
|
698
|
+
.refused .rows { border-top: 1px solid var(--chat-border); }
|
|
699
|
+
/* A card's free periods from a flexible search: pills that switch its stay in place. */
|
|
700
|
+
.periods { margin: 0 18px; padding: 10px 0 12px; border-top: 1px solid var(--chat-border); }
|
|
701
|
+
.periods-caption { margin: 0 0 6px; font-size: 13px; color: var(--chat-muted); }
|
|
702
|
+
/* "24. Apr. – 1. Mai" needs about 115px: two to a carousel card's row, one on a small phone. Days of one
|
|
703
|
+
month ("14.–21.") fit three or four to a row. */
|
|
704
|
+
.period-pills { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 124px), 1fr)); gap: 6px; }
|
|
705
|
+
.period-pills[data-one-month] { grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); }
|
|
706
|
+
.period-pill { min-height: 38px; padding: 0 8px; border: 1px solid var(--chat-border);
|
|
707
|
+
border-radius: var(--chat-round); background: transparent; color: var(--chat-text); font: inherit;
|
|
708
|
+
font-size: 13px; font-weight: 600; white-space: nowrap; cursor: pointer; }
|
|
709
|
+
.period-pill:disabled { cursor: default; }
|
|
710
|
+
.period-pill[aria-pressed="true"] { border-color: var(--chat-primary); background: var(--chat-primary);
|
|
711
|
+
color: var(--chat-on-primary); }
|
|
712
|
+
.periods + .card-foot { border-top: 0; padding-top: 0; }
|
|
713
|
+
/* Compare: a stay on other dates than the first column's says so, in words. */
|
|
714
|
+
.other-date { display: inline-block; margin-left: 6px; padding: 0 6px; border: 1px solid var(--chat-border);
|
|
715
|
+
border-radius: var(--chat-round); color: var(--chat-muted); font-size: 12px; }
|
|
456
716
|
.calendar { border: 1px solid var(--chat-border); border-radius: var(--chat-radius); padding: 12px;
|
|
457
717
|
background: var(--chat-surface); }
|
|
458
718
|
.calendar-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
@@ -463,19 +723,55 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
463
723
|
.day { padding: 4px 0; border-radius: min(6px, var(--chat-radius)); }
|
|
464
724
|
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
|
|
465
725
|
white-space: nowrap; }
|
|
466
|
-
/*
|
|
467
|
-
|
|
468
|
-
.day
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
726
|
+
/* Read without colour: free days plain, taken ones hatched with a muted numeral, arrival days a dot and a
|
|
727
|
+
bold numeral, a free day's price level one to three ticks; the stay filled. */
|
|
728
|
+
.day { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
729
|
+
gap: 3px; min-height: 40px; padding: 0; border: 0; background: transparent; color: inherit; font: inherit;
|
|
730
|
+
font-variant-numeric: tabular-nums; }
|
|
731
|
+
button.day { cursor: pointer; }
|
|
732
|
+
.day[data-status="booked"], .legend i[data-status="booked"] { color: var(--chat-muted);
|
|
733
|
+
background: repeating-linear-gradient(135deg, transparent 0 4px, var(--chat-border) 4px 5.5px);
|
|
734
|
+
box-shadow: inset 0 0 0 1px var(--chat-border); }
|
|
473
735
|
.day[data-status="closed"] { color: var(--chat-muted); opacity: 0.6; }
|
|
474
|
-
.day[data-
|
|
475
|
-
|
|
476
|
-
.legend {
|
|
477
|
-
|
|
478
|
-
|
|
736
|
+
.day[data-arrival] { font-weight: 700; }
|
|
737
|
+
/* At the numeral, however wide the cell. */
|
|
738
|
+
.day[data-arrival]::before, .legend i[data-arrival] { content: ""; position: absolute; top: 4px;
|
|
739
|
+
right: calc(50% - 16px);
|
|
740
|
+
width: 5px; height: 5px; border-radius: 50%; background: var(--chat-primary); }
|
|
741
|
+
.ticks { display: flex; gap: 2px; height: 3px; }
|
|
742
|
+
.ticks i { width: 6px; height: 3px; border-radius: 2px; background: var(--chat-muted); }
|
|
743
|
+
.day[data-stay], .legend i[data-stay], .day[aria-pressed="true"] { background: var(--chat-primary);
|
|
744
|
+
color: var(--chat-on-primary); }
|
|
745
|
+
.day[data-stay]::before, .day[data-stay] .ticks i, .day[aria-pressed="true"]::before,
|
|
746
|
+
.day[aria-pressed="true"] .ticks i { background: var(--chat-on-primary); }
|
|
747
|
+
/* A tapped arrival is the one filled day: the marked stay steps back to a tint meanwhile. */
|
|
748
|
+
.calendar-grid:has(.day[aria-pressed="true"]) .day[data-stay]:not([aria-pressed="true"]) {
|
|
749
|
+
background: var(--chat-secondary); color: var(--chat-on-secondary); }
|
|
750
|
+
.calendar-grid:has(.day[aria-pressed="true"]) .day[data-stay]:not([aria-pressed="true"])::before {
|
|
751
|
+
background: var(--chat-primary); }
|
|
752
|
+
.calendar-grid:has(.day[aria-pressed="true"]) .day[data-stay]:not([aria-pressed="true"]) .ticks i {
|
|
753
|
+
background: var(--chat-muted); }
|
|
754
|
+
/* A finger's height for a day that takes a tap. */
|
|
755
|
+
@media (pointer: coarse) { button.day { min-height: 44px; } }
|
|
756
|
+
.cal-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px;
|
|
757
|
+
padding-top: 10px; border-top: 1px solid var(--chat-border); }
|
|
758
|
+
.cal-foot strong { font-weight: 600; white-space: nowrap; }
|
|
759
|
+
.cal-foot .button { gap: 6px; }
|
|
760
|
+
/* Forced colours drop tints and backgrounds: borders and the system's text colour carry the marks. */
|
|
761
|
+
@media (forced-colors: active) {
|
|
762
|
+
.day[data-status="booked"], .legend i[data-status="booked"] { background: none; border: 1px dashed CanvasText;
|
|
763
|
+
text-decoration: line-through; }
|
|
764
|
+
.day[data-arrival]::before, .ticks i, .legend i[data-arrival] { background: CanvasText; }
|
|
765
|
+
.day[data-stay], .day[aria-pressed="true"] { border: 2px solid Highlight; }
|
|
766
|
+
/* The stay and the tapped day share that border: the tapped one gets a second mark. */
|
|
767
|
+
.day[aria-pressed="true"] { outline: 2px solid Highlight; outline-offset: -5px; }
|
|
768
|
+
}
|
|
769
|
+
.legend { display: flex; flex-wrap: wrap; gap: 6px 12px; margin-top: 8px; font-size: 13px; }
|
|
770
|
+
.legend > span { display: inline-flex; align-items: center; gap: 4px; }
|
|
771
|
+
.legend > span > i { display: inline-block; width: 12px; height: 12px; border-radius: 3px;
|
|
772
|
+
border: 1px solid var(--chat-border); }
|
|
773
|
+
.legend > span > i[data-arrival] { position: static; width: 6px; height: 6px; border: 0; }
|
|
774
|
+
.legend-levels .ticks { align-self: center; }
|
|
479
775
|
.compare { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; }
|
|
480
776
|
.compare-col { border-radius: var(--chat-radius); padding: 10px; background: var(--chat-surface);
|
|
481
777
|
box-shadow: var(--chat-card-shadow); display: flex; flex-direction: column; gap: 6px; font-size: 14px;
|
|
@@ -494,21 +790,23 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
494
790
|
scrollbar-width: none; padding: 2px 2px 16px; margin-bottom: -10px; }
|
|
495
791
|
.compare::-webkit-scrollbar { display: none; }
|
|
496
792
|
.compare-col { scroll-snap-align: start; }
|
|
793
|
+
/* The rows line up across the columns: one long name off screen would push every visible row down. */
|
|
794
|
+
.compare-col > strong { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
|
|
795
|
+
overflow: hidden; }
|
|
497
796
|
}
|
|
498
797
|
/* Side by side, the rows line up across the columns (a long name pushes every row of its column down
|
|
499
798
|
otherwise): the name, the list's eight cells and the button share the comparison's ten rows. */
|
|
500
799
|
@supports (grid-template-rows: subgrid) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.compare-col .button { grid-row: -2; }
|
|
506
|
-
}
|
|
800
|
+
.compare { grid-template-rows: repeat(10, auto); row-gap: 0; }
|
|
801
|
+
.compare-col { display: grid; grid-row: span 10; grid-template-rows: subgrid; }
|
|
802
|
+
.compare-col dl { display: grid; grid-row: span 8; grid-template-rows: subgrid; }
|
|
803
|
+
.compare-col .button { grid-row: -2; }
|
|
507
804
|
}
|
|
508
805
|
.card-body { padding: 16px 18px 12px; display: flex; flex-direction: column; gap: 6px; }
|
|
509
806
|
.card-title { margin: 0; font-size: 18px; font-weight: 500; line-height: 1.3; }
|
|
510
807
|
/* One long name (v9 names often run to a dozen words) would make every card of a row taller: in a
|
|
511
|
-
carousel the name stops at three lines
|
|
808
|
+
carousel the name stops at three lines, as in a narrow Compare; the single card and the quote show it
|
|
809
|
+
whole. */
|
|
512
810
|
.carousel .card-title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
|
|
513
811
|
overflow: hidden; }
|
|
514
812
|
/* A rental card: place and score, name, facts and wishes; the stay's price and button below a line. */
|
|
@@ -516,8 +814,8 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
516
814
|
.rental-card .card-body { flex: 1; }
|
|
517
815
|
.card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
|
|
518
816
|
.place { color: var(--chat-accent); font-size: 14px; }
|
|
519
|
-
.
|
|
520
|
-
|
|
817
|
+
.card-meta .photo-rating { margin-left: auto; font-size: 14px; font-weight: 600; }
|
|
818
|
+
.card-meta .photo-rating .star { color: var(--chat-rating); }
|
|
521
819
|
.specs { margin: 0; color: var(--chat-muted); font-size: 14px; }
|
|
522
820
|
.card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
523
821
|
margin: 0 18px; padding: 12px 0 16px; border-top: 1px solid var(--chat-border); }
|
|
@@ -527,7 +825,10 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
527
825
|
.foot-meta .period { white-space: nowrap; }
|
|
528
826
|
.button.compact { margin: 0 0 0 auto; padding: 9px 20px; font-size: 14px; white-space: nowrap; }
|
|
529
827
|
.muted { color: var(--chat-muted); font-size: 13px; }
|
|
530
|
-
.price { font-weight: 700; }
|
|
828
|
+
.price { font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
829
|
+
/* "Gesamtpreis für 7 Nächte": what a total covers, over it. */
|
|
830
|
+
.price-label { color: var(--chat-muted); font-size: 13px; }
|
|
831
|
+
.price-line { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; }
|
|
531
832
|
/* Relative: the link's screen-reader name (an absolute .sr-only) stays inside a scrolled row, which
|
|
532
833
|
clips it, instead of sticking out of the row and widening the panel. */
|
|
533
834
|
.button { position: relative; display: inline-flex; align-items: center; justify-content: center; margin-top: 6px;
|
|
@@ -541,19 +842,75 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
541
842
|
.ui > .link, .fallback-links > .link { display: inline-flex; align-items: center; justify-content: center; padding: 10px 16px;
|
|
542
843
|
border: 1px solid var(--chat-primary); border-radius: var(--chat-round); text-decoration: none;
|
|
543
844
|
font-weight: 600; }
|
|
544
|
-
/* A link out of the chat while a reply still streams (ReplyLink).
|
|
845
|
+
/* A link out of the chat while a reply still streams (ReplyLink). A button keeps its full strength: it
|
|
846
|
+
waits a moment, it is not unavailable. */
|
|
545
847
|
[aria-disabled="true"] { opacity: 0.5; cursor: progress; }
|
|
848
|
+
.button[aria-disabled="true"] { opacity: 1; }
|
|
849
|
+
|
|
850
|
+
/* Cards rise above the message area: on a dark surface a step lighter, with a 1 px edge where the shadow
|
|
851
|
+
cannot show; a light surface stays as it is (the lift only applies below the middle lightness). */
|
|
852
|
+
@supports (color: oklch(from red calc(l + 0.1) c h)) {
|
|
853
|
+
.card, .compare-col, .calendar { background: oklch(from var(--chat-surface) calc(l + max(0, 0.5 - l) * 0.45) c h); }
|
|
854
|
+
.card, .compare-col, .calendar { --chat-border: var(--chat-card-border); }
|
|
855
|
+
.card, .compare-col { box-shadow: var(--chat-card-shadow),
|
|
856
|
+
0 0 0 1px oklch(from var(--chat-surface) calc(l + max(0, 0.5 - l) * 0.75) c h); }
|
|
857
|
+
}
|
|
858
|
+
/* A carousel card, about 290px wide on a phone and on a wide panel alike: the total's label across the
|
|
859
|
+
foot, the total and the button side by side under it, so the label does not wrap beside the button
|
|
860
|
+
and push the price under the input. */
|
|
861
|
+
.carousel .card-foot { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
|
|
862
|
+
gap: 0 8px; }
|
|
863
|
+
.carousel .card-foot > .foot-price { display: contents; }
|
|
864
|
+
.carousel .card-foot .price-label { grid-column: 1 / -1; }
|
|
865
|
+
.carousel .card-foot > .button { grid-column: 2; grid-row: 2 / span 3; }
|
|
866
|
+
@container (min-width: 300px) {
|
|
867
|
+
/* With a deal badge the total and the badge take the line, and the button sits beside dates and party. */
|
|
868
|
+
.carousel .card-foot:has(.deal) .price-line { grid-column: 1 / -1; }
|
|
869
|
+
.carousel .card-foot:has(.deal) > .button { grid-row: 3 / span 2; }
|
|
870
|
+
/* An unbookable stay: the button beside its one line. */
|
|
871
|
+
.carousel .card-foot:not(:has(.price-label)) > .button { grid-row: 1; }
|
|
872
|
+
}
|
|
873
|
+
/* A small phone's card has no room beside the total: the button goes under it, full width. */
|
|
874
|
+
@container (max-width: 299px) {
|
|
875
|
+
.carousel .card-foot { grid-template-columns: minmax(0, 1fr); }
|
|
876
|
+
.carousel .card-foot > .button { grid-column: 1; grid-row: auto; margin-top: 8px; }
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/* A card's photos large, in a sheet over the panel's content (sheet.tsx). */
|
|
880
|
+
.sheet-host { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
|
|
881
|
+
.sheet-host:empty { display: none; }
|
|
882
|
+
.sheet-scrim { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end;
|
|
883
|
+
pointer-events: auto; background: rgb(15 23 42 / 0.55); }
|
|
884
|
+
.sheet { display: flex; flex-direction: column; height: calc(100% - 40px);
|
|
885
|
+
border-radius: var(--chat-radius) var(--chat-radius) 0 0; background: var(--chat-surface);
|
|
886
|
+
box-shadow: 0 -8px 32px rgb(15 23 42 / 0.25); overflow: hidden; }
|
|
887
|
+
.sheet:focus { outline: none; }
|
|
888
|
+
.sheet-head { display: flex; align-items: center; gap: 10px; padding: 10px 16px 10px 20px; }
|
|
889
|
+
.sheet-title { margin-right: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
890
|
+
font-weight: 600; }
|
|
891
|
+
.sheet-count { color: var(--chat-muted); font-size: 13px; font-variant-numeric: tabular-nums; }
|
|
892
|
+
.sheet-photo { position: relative; flex: 1; min-height: 0; background: var(--chat-surface-soft); }
|
|
893
|
+
.sheet-photos { display: flex; height: 100%; overflow-x: auto; overscroll-behavior: contain;
|
|
894
|
+
scroll-snap-type: x mandatory; scrollbar-width: none; }
|
|
895
|
+
.sheet-photos::-webkit-scrollbar { display: none; }
|
|
896
|
+
/* Uncropped, at the photo's own ratio. */
|
|
897
|
+
.sheet-photos img { flex: 0 0 100%; width: 100%; height: 100%; object-fit: contain; scroll-snap-align: start; }
|
|
898
|
+
.sheet .sheet-photo > .gallery-arrow { opacity: 1; pointer-events: auto; }
|
|
899
|
+
.sheet .sheet-photo > .gallery-arrow:disabled { opacity: 0.4; }
|
|
900
|
+
.sheet-caption { margin: 0; padding: 10px 20px 0; font-size: 14px; }
|
|
901
|
+
.sheet-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px;
|
|
902
|
+
padding: 12px 20px calc(18px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--chat-border); }
|
|
546
903
|
|
|
547
904
|
/* A card without photos: in a carousel a quiet block holds the photo's place, so the row lines up;
|
|
548
905
|
on its own the card simply starts with its text. */
|
|
549
|
-
.no-photo { display: none; aspect-ratio:
|
|
906
|
+
.no-photo { display: none; aspect-ratio: 4 / 3; place-items: center; background: var(--chat-surface-soft);
|
|
550
907
|
color: var(--chat-muted); }
|
|
551
908
|
.carousel .no-photo { display: grid; }
|
|
552
909
|
/* A card on its own in a wide panel: a flatter photo, so the card's name and price fit the list with it. */
|
|
553
910
|
@container (min-width: 520px) {
|
|
554
|
-
.ui > .rental-card .gallery img { aspect-ratio: 2 / 1; max-height: 280px; }
|
|
911
|
+
.ui > .rental-card :is(.gallery > img, .photo-open) { aspect-ratio: 2 / 1; max-height: 280px; }
|
|
555
912
|
/* In a carousel too: the cards' prices and the reply under them come up above the dock sooner. */
|
|
556
|
-
.carousel .gallery img, .carousel .no-photo { aspect-ratio: 2 / 1; }
|
|
913
|
+
.carousel :is(.gallery > img, .photo-open), .carousel .no-photo { aspect-ratio: 2 / 1; }
|
|
557
914
|
}
|
|
558
915
|
/* Wishes a found stay matches, ticked under the name. */
|
|
559
916
|
.wishes { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
@@ -561,6 +918,71 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
561
918
|
border: 1px solid var(--chat-border); border-radius: var(--chat-round); font-size: 13px; font-weight: 600; }
|
|
562
919
|
.wishes svg { color: var(--chat-accent); }
|
|
563
920
|
|
|
921
|
+
/* A quote: its dates and party, the labelled total beside the button, the discount and the
|
|
922
|
+
cancellation terms, then the price lines folded away under "Preisdetails". */
|
|
923
|
+
.stay-card .card-body { gap: 12px; }
|
|
924
|
+
.stay-card .card-body:last-child { padding-bottom: 16px; }
|
|
925
|
+
.quote-head { display: flex; align-items: center; gap: 12px; }
|
|
926
|
+
.quote-head > div { display: flex; flex-direction: column; min-width: 0; }
|
|
927
|
+
.thumb { flex: none; width: 52px; height: 52px; border-radius: calc(var(--chat-radius) * 0.75);
|
|
928
|
+
object-fit: cover; }
|
|
929
|
+
.quote-when { margin: 0; color: var(--chat-muted); font-size: 14px; }
|
|
930
|
+
.quote-when .period { white-space: nowrap; }
|
|
931
|
+
/* Wraps on a small phone: the button goes under a total that would not fit beside it. */
|
|
932
|
+
.price-row { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px;
|
|
933
|
+
padding-top: 12px; border-top: 1px solid var(--chat-border); }
|
|
934
|
+
.price-row .price { font-size: 24px; line-height: 1.2; white-space: nowrap; }
|
|
935
|
+
.quote-facts { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
936
|
+
.deal, .cancel-line { margin: 0; display: flex; align-items: center; gap: 8px; font-size: 14px; }
|
|
937
|
+
.deal svg, .cancel-line svg, .ho-lines svg { flex: none; color: var(--chat-accent); }
|
|
938
|
+
.deal strong, .cancel-line strong { font-weight: 600; }
|
|
939
|
+
/* The date wraps as one: "7." at a line's end and "Jan. 2027" on the next read as two. */
|
|
940
|
+
.cancel-line strong { white-space: nowrap; }
|
|
941
|
+
/* The discount in the primary's tint: its words say it, not the colour. */
|
|
942
|
+
.deal { padding: 4px 12px 4px 10px; border: 1px solid var(--chat-tint-edge); border-radius: var(--chat-round);
|
|
943
|
+
background: var(--chat-secondary); color: var(--chat-on-secondary); }
|
|
944
|
+
/* A badge stays one line: two would make a blob beside a card's button. */
|
|
945
|
+
.deal.compact { display: inline-flex; padding: 1px 8px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
|
946
|
+
/* The quote's line: a pill on one line, a rounded box on two. */
|
|
947
|
+
.deal:not(.compact) { border-radius: 16px; }
|
|
948
|
+
.deal .saving { white-space: nowrap; }
|
|
949
|
+
.price-details { padding-top: 10px; border-top: 1px solid var(--chat-border); font-size: 14px; }
|
|
950
|
+
.price-details summary { display: flex; align-items: center; justify-content: space-between; min-height: 32px;
|
|
951
|
+
list-style: none; color: var(--chat-primary); font-weight: 600; cursor: pointer; }
|
|
952
|
+
.price-details summary::-webkit-details-marker { display: none; }
|
|
953
|
+
.price-details summary svg { transform: rotate(90deg); }
|
|
954
|
+
.price-details[open] summary svg { transform: rotate(-90deg); }
|
|
955
|
+
.price-details .line { margin-top: 6px; }
|
|
956
|
+
/* The hand-over: what was chosen, the total as the largest figure, the conversation's one full-width
|
|
957
|
+
button, and what the booking page asks next. */
|
|
958
|
+
.ho-dates { display: flex; align-items: center; gap: 10px; padding: 12px 14px;
|
|
959
|
+
border-radius: calc(var(--chat-radius) * 0.75); background: var(--chat-surface-soft); }
|
|
960
|
+
.ho-dates > div { display: flex; flex-direction: column; }
|
|
961
|
+
.ho-dates > div:last-child { text-align: right; }
|
|
962
|
+
.ho-dates strong { font-weight: 600; white-space: nowrap; }
|
|
963
|
+
.ho-nights { flex: 1; display: flex; align-items: center; gap: 6px; color: var(--chat-muted); font-size: 13px;
|
|
964
|
+
white-space: nowrap; }
|
|
965
|
+
.ho-nights::before, .ho-nights::after { content: ""; flex: 1; height: 1px; background: currentColor; opacity: 0.5; }
|
|
966
|
+
/* A phone under 360px has no room for both dates and the nights in a row: the dates side by side, the
|
|
967
|
+
nights under them. */
|
|
968
|
+
@container (max-width: 311px) {
|
|
969
|
+
.ho-dates { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }
|
|
970
|
+
.ho-nights { grid-column: 1 / -1; grid-row: 2; }
|
|
971
|
+
}
|
|
972
|
+
.ho-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px;
|
|
973
|
+
font-size: 14px; }
|
|
974
|
+
.ho-lines li { display: flex; align-items: center; gap: 6px; }
|
|
975
|
+
.ho-total { display: flex; flex-direction: column; padding-top: 12px; border-top: 1px solid var(--chat-border); }
|
|
976
|
+
.ho-total .price { font-size: 30px; line-height: 1.15; }
|
|
977
|
+
.button.block { display: flex; width: 100%; min-height: 50px; margin: 0; gap: 8px; font-size: 16px; }
|
|
978
|
+
.ho-note { margin: 0; color: var(--chat-muted); font-size: 13px; }
|
|
979
|
+
.ho-steps { list-style: none; counter-reset: step; margin: -4px 0 0; padding: 0; display: flex;
|
|
980
|
+
flex-direction: column; gap: 5px; font-size: 13px; }
|
|
981
|
+
.ho-steps li { counter-increment: step; display: flex; align-items: center; gap: 8px; }
|
|
982
|
+
.ho-steps li::before { content: counter(step); flex: none; display: grid; place-items: center; width: 20px;
|
|
983
|
+
height: 20px; border: 1px solid var(--chat-border); border-radius: 50%; color: var(--chat-muted);
|
|
984
|
+
font-size: 12px; font-weight: 700; }
|
|
985
|
+
|
|
564
986
|
/* The price calendar: a column per arrival, the bar's height its total. */
|
|
565
987
|
.price-columns { position: relative; list-style: none; margin: 10px 0 0; padding: 0; display: grid;
|
|
566
988
|
grid-auto-flow: column; grid-auto-columns: minmax(68px, 1fr); gap: 6px; overflow-x: auto;
|
|
@@ -631,6 +1053,10 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
631
1053
|
background: var(--chat-surface); color: var(--chat-text); }
|
|
632
1054
|
.gallery-wrap:hover .gallery-arrow:not(:disabled) { opacity: 1; }
|
|
633
1055
|
.price-column[data-free]:not(:disabled):hover { border-color: var(--chat-primary); }
|
|
1056
|
+
button.day:hover { box-shadow: inset 0 0 0 1px var(--chat-primary); }
|
|
1057
|
+
.row:not(:disabled):hover { background: var(--chat-surface-soft); }
|
|
1058
|
+
.row[aria-pressed="true"]:hover { background: var(--chat-secondary); }
|
|
1059
|
+
.period-pill:not(:disabled):hover { border-color: var(--chat-primary); }
|
|
634
1060
|
}
|
|
635
1061
|
|
|
636
1062
|
/* Fingers: every control at least 44px each way. */
|
|
@@ -638,7 +1064,8 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
638
1064
|
.icon-button, .send, .snap-nav button, .calendar-head button, .gallery-arrow { width: 44px; height: 44px; }
|
|
639
1065
|
.composer { padding: 4px 4px 4px 18px; }
|
|
640
1066
|
.composer textarea { min-height: 44px; padding-block: 10px; }
|
|
641
|
-
.chip, .suggestion, .button, .
|
|
1067
|
+
.chip, .suggestion, .button, .ui > .link, .fallback-links > .link, .period-pill { min-height: 44px; }
|
|
1068
|
+
.price-details summary { min-height: 44px; }
|
|
642
1069
|
}
|
|
643
1070
|
|
|
644
1071
|
[data-theme="soft"] { --chat-radius: calc(var(--chat-base-radius) * 2.4); --chat-shadow: 0 12px 32px rgb(15 23 42 / 0.12);
|
|
@@ -653,6 +1080,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
653
1080
|
@keyframes chat-in { from { opacity: 0; transform: translateY(16px); } }
|
|
654
1081
|
@keyframes chat-fade { from { opacity: 0; } }
|
|
655
1082
|
@keyframes chat-pulse { 50% { opacity: 1; transform: translateY(-2px); } }
|
|
1083
|
+
@keyframes chat-ring { from { outline-color: transparent; outline-offset: 8px; } }
|
|
656
1084
|
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
|
|
657
1085
|
.pill:hover { transform: translateY(-2px); }
|
|
658
1086
|
}
|
|
@@ -665,6 +1093,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
665
1093
|
.thinking i { animation: chat-pulse 1.1s ease-in-out infinite; }
|
|
666
1094
|
.thinking i:nth-child(2) { animation-delay: 160ms; }
|
|
667
1095
|
.thinking i:nth-child(3) { animation-delay: 320ms; }
|
|
1096
|
+
[data-pulse] { animation: chat-ring 500ms ease-out 2 alternate; }
|
|
668
1097
|
}
|
|
669
1098
|
/* Without motion the ring stands still, in the primary colour all round. */
|
|
670
1099
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -673,7 +1102,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
673
1102
|
`;
|
|
674
1103
|
//#endregion
|
|
675
1104
|
//#region src/WebsiteChat.tsx
|
|
676
|
-
const Panel = lazy(() => import("./panel-
|
|
1105
|
+
const Panel = lazy(() => import("./panel-B_wg4Wl0.js"));
|
|
677
1106
|
/** See {@link WebsiteChatComponent}. */
|
|
678
1107
|
const WebsiteChat = function WebsiteChat(props) {
|
|
679
1108
|
const { endpoint, locale, theme = "classic", position = "right" } = props;
|
|
@@ -745,11 +1174,12 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
745
1174
|
fallback: null,
|
|
746
1175
|
children: /* @__PURE__ */ jsx(Panel, {
|
|
747
1176
|
endpoint,
|
|
748
|
-
clientVersion: "1.
|
|
1177
|
+
clientVersion: "1.3.0",
|
|
749
1178
|
locale,
|
|
750
1179
|
copy,
|
|
751
1180
|
searchPath: props.searchPath,
|
|
752
1181
|
contactPath: props.contactPath,
|
|
1182
|
+
...props.privacyPath && { privacyPath: props.privacyPath },
|
|
753
1183
|
open,
|
|
754
1184
|
pending,
|
|
755
1185
|
onSent: sent,
|