@v-office/website-chat 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{catalog-y3xQ2jXW.js → catalog-ClcCUjf7.js} +13 -13
- package/dist/catalog.js +1 -1
- package/dist/{icons-DIqOmKoe.js → icons-BK5Lk0cP.js} +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +175 -74
- package/dist/{panel-BsocDe9I.js → panel-CfS3SuRX.js} +301 -146
- package/instructions/CHANGELOG.md +2 -0
- package/instructions/MIGRATION.md +2 -0
- package/instructions/versions/1.1.3/CHANGELOG.md +18 -0
- package/instructions/versions/1.1.3/MIGRATION.md +9 -0
- package/instructions/versions/1.2.0/CHANGELOG.md +45 -0
- package/instructions/versions/1.2.0/MIGRATION.md +15 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -41,8 +41,8 @@ It takes its colours and corners from the site's shadcn tokens (`--primary`,
|
|
|
41
41
|
are square (`--radius: 0`) gets square buttons too.
|
|
42
42
|
|
|
43
43
|
A site with a sticky header sets `--chat-top` (its height) on any element around the widget, such as
|
|
44
|
-
`:root`: the dimmed page and the panel start below it
|
|
45
|
-
(default `700px`).
|
|
44
|
+
`:root`: from 1024 px wide, the dimmed page and the panel start below it; narrower, the chat opens as a
|
|
45
|
+
full-height sheet over the header. `--chat-width` widens the panel on wide screens (default `700px`).
|
|
46
46
|
|
|
47
47
|
The service places these components in replies: `RentalCard` (photo gallery, rating, the wishes the
|
|
48
48
|
search that found it applied), `RentalCarousel`, `Compare`, `StayQuote`, `PriceBreakdown`,
|
|
@@ -28,67 +28,67 @@ const chatCatalog = defineCatalog(schema, {
|
|
|
28
28
|
},
|
|
29
29
|
RentalCarousel: {
|
|
30
30
|
props: z.object({ items: z.array(offer).min(2).max(8) }),
|
|
31
|
-
description: "Several rentals side by side, swipeable, each as a RentalCard
|
|
31
|
+
description: "Several rentals side by side, swipeable, each as a RentalCard: search results, instead of several RentalCards. After a flexible search, each rental with its first period as its stayId."
|
|
32
32
|
},
|
|
33
33
|
StayQuote: {
|
|
34
34
|
props: z.object({ stayId: id }),
|
|
35
|
-
description: "One priced stay: the rental, the dates, the party, the total and a booking button."
|
|
35
|
+
description: "One priced stay: the rental, the dates, the party, the total and a booking button. Show it whenever the visitor asks what one stay costs, with the stayId from quote_stay."
|
|
36
36
|
},
|
|
37
37
|
PriceBreakdown: {
|
|
38
38
|
props: z.object({ stayId: id }),
|
|
39
|
-
description: "What a
|
|
39
|
+
description: "What a stay's total is made of (nights, cleaning, pets, …), line by line, when the visitor asks. Only for a stay from quote_stay or configure_stay."
|
|
40
40
|
},
|
|
41
41
|
AlternativeStays: {
|
|
42
42
|
props: z.object({
|
|
43
43
|
rentalId: id,
|
|
44
44
|
stayIds: z.array(id).min(1).max(6)
|
|
45
45
|
}),
|
|
46
|
-
description: "
|
|
46
|
+
description: "Other free periods of one rental as tappable choices with their prices, right after that rental's card: the rental the reply is about, the one taken on the asked dates, or a rental's further periods after a flexible search; never the stay its card shows. For a rental the visitor named, its RentalCard comes first (stayId null when taken). For several taken rentals use a RentalCarousel with each rental's first free period as its stayId, and say the dates differ. At most one AlternativeStays per reply."
|
|
47
47
|
},
|
|
48
48
|
AvailabilityCalendar: {
|
|
49
49
|
props: z.object({
|
|
50
50
|
rentalId: id,
|
|
51
51
|
stayId: id.nullable()
|
|
52
52
|
}),
|
|
53
|
-
description: "A rental's month calendar of free and taken days, with a stay's dates marked when there is one.
|
|
53
|
+
description: "A rental's month calendar of free and taken days, with a stay's dates marked when there is one. Show it whenever the visitor asks when a rental is free, after get_availability. Read-only: the visitor cannot pick days in it."
|
|
54
54
|
},
|
|
55
55
|
PriceCalendar: {
|
|
56
56
|
props: z.object({ rentalId: id }),
|
|
57
|
-
description: "One rental's total for the same length of stay at the arrivals around the asked one
|
|
57
|
+
description: "One rental's total for the same length of stay at the arrivals around the asked one; tapping one asks for it. For other or cheaper arrival dates, only after price_calendar. Not what one stay costs: that is a StayQuote."
|
|
58
58
|
},
|
|
59
59
|
ReviewSummary: {
|
|
60
60
|
props: z.object({ rentalId: id }),
|
|
61
|
-
description: "A rental's guest rating as the booking system sums it up, its category ratings and a few review texts. Only after get_reviews."
|
|
61
|
+
description: "A rental's guest rating as the booking system sums it up, its category ratings and a few review texts, when the visitor asks how guests rate it. Only after get_reviews; sum up in a sentence, in your own words, what guests value."
|
|
62
62
|
},
|
|
63
63
|
RentalFacts: {
|
|
64
64
|
props: z.object({
|
|
65
65
|
rentalId: id,
|
|
66
66
|
groups: z.array(z.string().trim().min(1).max(80)).min(1).max(4)
|
|
67
67
|
}),
|
|
68
|
-
description: "The facts of one rental in the groups get_rental listed
|
|
68
|
+
description: "The facts of one rental in the groups get_rental listed, by their label. Show it whenever the visitor asks what one rental has or allows (pets, kitchen, parking, surroundings), after get_rental; answer the question in your text as well."
|
|
69
69
|
},
|
|
70
70
|
RentalMap: {
|
|
71
71
|
props: z.object({ rentalId: id }),
|
|
72
|
-
description: "Where one rental is: a map the visitor loads with a tap, and a link to its page."
|
|
72
|
+
description: "Where one rental is, when the visitor asks: a map the visitor loads with a tap, and a link to its page."
|
|
73
73
|
},
|
|
74
74
|
Compare: {
|
|
75
75
|
props: z.object({ items: z.array(offer).min(2).max(3) }),
|
|
76
|
-
description: "Two or three rentals side by side: rating, rooms, highlights, the stay and its price."
|
|
76
|
+
description: "Two or three rentals side by side: rating, rooms, highlights, the stay and its price. Show it whenever the visitor weighs or compares two or three rentals, with stays of the same dates; a rental taken then gets stayId null."
|
|
77
77
|
},
|
|
78
78
|
StayLink: {
|
|
79
79
|
props: z.object({
|
|
80
80
|
rentalId: id,
|
|
81
81
|
stayId: id.nullable()
|
|
82
82
|
}),
|
|
83
|
-
description: "A button to the rental's page on this website, with the stay filled in, to book there."
|
|
83
|
+
description: "A button to the rental's page on this website, with the stay filled in, to book there. Only for a rental or stay shown without one: cards, carousels, comparisons and quotes carry their own booking buttons."
|
|
84
84
|
},
|
|
85
85
|
ContactLink: {
|
|
86
86
|
props: z.object({}),
|
|
87
|
-
description: "A link to this website's contact page."
|
|
87
|
+
description: "A link to this website's contact page. Show it whenever you send the visitor to the agency; never only offer it."
|
|
88
88
|
},
|
|
89
89
|
QuickReplies: {
|
|
90
90
|
props: z.object({ options: z.array(z.string().trim().min(1).max(40)).min(1).max(4) }),
|
|
91
|
-
description: "Up to four short answers the visitor can tap
|
|
91
|
+
description: "Up to four short answers the visitor can tap, in the language of your reply (a date, a party size, a wish): with every question you ask. Never a fact or a price."
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
actions: {}
|
package/dist/catalog.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { i as SESSION_TTL_S, n as CHAT_PATH, r as MAX_MESSAGE_CHARS, t as CHAT_LOCALES } from "./protocol-BuNqZAMt.js";
|
|
2
|
-
import { a as componentsFor, c as rendersBold, i as chatSpec, l as stateKey, n as PLACEABLE_COMPONENTS, o as handsOverStays, r as chatCatalog, s as parseItem, t as COMPONENT_SINCE, u as statePath } from "./catalog-
|
|
2
|
+
import { a as componentsFor, c as rendersBold, i as chatSpec, l as stateKey, n as PLACEABLE_COMPONENTS, o as handsOverStays, r as chatCatalog, s as parseItem, t as COMPONENT_SINCE, u as statePath } from "./catalog-ClcCUjf7.js";
|
|
3
3
|
export { CHAT_LOCALES, CHAT_PATH, COMPONENT_SINCE, MAX_MESSAGE_CHARS, PLACEABLE_COMPONENTS, SESSION_TTL_S, chatCatalog, chatSpec, componentsFor, handsOverStays, parseItem, rendersBold, stateKey, statePath };
|
|
@@ -171,7 +171,7 @@ const TOPICS = [
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
topic: "people",
|
|
174
|
-
words: /famili|kinder|zu zweit|personen|erwachsene|gruppe|family|kids|people|adults|couple/
|
|
174
|
+
words: /famili|kinder|zu zweit|personen|erwachsene|gruppe|family|kids|people|adults|couple|for two(?! weeks| nights)/
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
topic: "date",
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ interface ChatCopy {
|
|
|
45
45
|
readonly photo: string;
|
|
46
46
|
readonly previous: string;
|
|
47
47
|
readonly next: string;
|
|
48
|
+
/** A carousel of found rentals, for a screen reader. */
|
|
49
|
+
readonly rentalsFound: string;
|
|
48
50
|
/** `{n}` reviews, per plural form. */
|
|
49
51
|
readonly reviews: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
50
52
|
readonly other: string;
|
|
@@ -82,6 +84,10 @@ interface ChatCopy {
|
|
|
82
84
|
readonly children: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
83
85
|
readonly other: string;
|
|
84
86
|
};
|
|
87
|
+
/** The pets in a stay's party, after the people. */
|
|
88
|
+
readonly pets: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
89
|
+
readonly other: string;
|
|
90
|
+
};
|
|
85
91
|
}
|
|
86
92
|
export declare const DEFAULT_COPY: Readonly<Record<ChatLocale, ChatCopy>>;
|
|
87
93
|
//#endregion
|
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 { f as chatAvailable, s as Spark, t as Arrow, u as loadConversation } from "./icons-
|
|
2
|
+
import { f as chatAvailable, s as Spark, t as Arrow, u as loadConversation } from "./icons-BK5Lk0cP.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";
|
|
@@ -14,10 +14,10 @@ const DEFAULT_COPY = {
|
|
|
14
14
|
notice: "Sie chatten mit einer KI. Preise und Verfügbarkeit in den Karten kommen direkt aus unserem Buchungssystem.",
|
|
15
15
|
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
16
|
suggestions: [
|
|
17
|
-
"
|
|
17
|
+
"Sommerwoche für 2 Erwachsene und 2 Kinder",
|
|
18
18
|
"Eine Woche mit Hund, gern mit eingezäuntem Garten",
|
|
19
19
|
"Ein Wochenende zu zweit mit Sauna",
|
|
20
|
-
"Was ist
|
|
20
|
+
"Was ist am nächsten Wochenende noch frei?"
|
|
21
21
|
],
|
|
22
22
|
placeholder: "Wann, mit wem, was ist Ihnen wichtig?",
|
|
23
23
|
send: "Senden",
|
|
@@ -43,6 +43,7 @@ const DEFAULT_COPY = {
|
|
|
43
43
|
photo: "Foto",
|
|
44
44
|
previous: "Zurück",
|
|
45
45
|
next: "Weiter",
|
|
46
|
+
rentalsFound: "Gefundene Unterkünfte",
|
|
46
47
|
reviews: {
|
|
47
48
|
one: "{n} Bewertung",
|
|
48
49
|
other: "{n} Bewertungen"
|
|
@@ -75,6 +76,10 @@ const DEFAULT_COPY = {
|
|
|
75
76
|
children: {
|
|
76
77
|
one: "{n} Kind",
|
|
77
78
|
other: "{n} Kinder"
|
|
79
|
+
},
|
|
80
|
+
pets: {
|
|
81
|
+
one: "{n} Haustier",
|
|
82
|
+
other: "{n} Haustiere"
|
|
78
83
|
}
|
|
79
84
|
},
|
|
80
85
|
en: {
|
|
@@ -86,10 +91,10 @@ const DEFAULT_COPY = {
|
|
|
86
91
|
notice: "You are chatting with an AI. Prices and availability in the cards come straight from our booking system.",
|
|
87
92
|
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.",
|
|
88
93
|
suggestions: [
|
|
89
|
-
"A
|
|
94
|
+
"A summer week for 2 adults and 2 kids",
|
|
90
95
|
"A week with our dog, ideally with a fenced garden",
|
|
91
96
|
"A weekend for two with a sauna",
|
|
92
|
-
"
|
|
97
|
+
"Anything free next weekend?"
|
|
93
98
|
],
|
|
94
99
|
placeholder: "When, who with, what matters to you?",
|
|
95
100
|
send: "Send",
|
|
@@ -115,6 +120,7 @@ const DEFAULT_COPY = {
|
|
|
115
120
|
photo: "Photo",
|
|
116
121
|
previous: "Previous",
|
|
117
122
|
next: "Next",
|
|
123
|
+
rentalsFound: "Rentals found",
|
|
118
124
|
reviews: {
|
|
119
125
|
one: "{n} review",
|
|
120
126
|
other: "{n} reviews"
|
|
@@ -147,6 +153,10 @@ const DEFAULT_COPY = {
|
|
|
147
153
|
children: {
|
|
148
154
|
one: "{n} child",
|
|
149
155
|
other: "{n} children"
|
|
156
|
+
},
|
|
157
|
+
pets: {
|
|
158
|
+
one: "{n} pet",
|
|
159
|
+
other: "{n} pets"
|
|
150
160
|
}
|
|
151
161
|
}
|
|
152
162
|
};
|
|
@@ -246,6 +256,8 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
246
256
|
${SITE_RADIUS.chat}: var(${SITE_RADIUS.site}, ${SITE_RADIUS.own});
|
|
247
257
|
--chat-secondary: color-mix(in srgb, var(--chat-primary) 12%, var(--chat-surface));
|
|
248
258
|
--chat-on-secondary: var(--chat-text);
|
|
259
|
+
/* The tinted bubbles' edge: on a dark surface the tint alone barely stands off the message area. */
|
|
260
|
+
--chat-tint-edge: color-mix(in srgb, var(--chat-primary) 30%, transparent);
|
|
249
261
|
/* The small marks (the chat's name and sparkle, a card's place, icons, the ring): the primary,
|
|
250
262
|
unless a site sets an accent of its own (\`colors.accent\`). */
|
|
251
263
|
--chat-accent: var(--chat-primary);
|
|
@@ -255,15 +267,21 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
255
267
|
--chat-round: calc(var(--chat-base-radius) * 100);
|
|
256
268
|
--chat-shadow: 0 18px 48px rgb(15 23 42 / 0.18);
|
|
257
269
|
--chat-card-shadow: 0 1px 3px rgb(15 23 42 / 0.08), 0 8px 24px rgb(15 23 42 / 0.06);
|
|
258
|
-
/* Not set here, so a site's value reaches the widget: \`--chat-top\`
|
|
259
|
-
\`--chat-width\` widens the panel
|
|
270
|
+
/* Not set here, so a site's value reaches the widget, on wide screens (from 1024px): \`--chat-top\`
|
|
271
|
+
keeps a sticky header uncovered, \`--chat-width\` widens the panel. A phone's sheet covers the
|
|
272
|
+
header: the panel is modal, so the header could not be tapped and would only cost height. */
|
|
260
273
|
}
|
|
261
274
|
* { box-sizing: border-box; }
|
|
262
275
|
.chat { color: var(--chat-text); font-size: 15px; line-height: 1.45; }
|
|
276
|
+
/* One focus ring for every control, in the site's primary; a click or a tap shows none. Without
|
|
277
|
+
specificity (:where), so a control that needs its ring elsewhere sets it with a plain rule. */
|
|
278
|
+
:where(a, button, [tabindex]:not([tabindex="-1"])):focus-visible { outline: 2px solid var(--chat-primary);
|
|
279
|
+
outline-offset: 2px; }
|
|
263
280
|
|
|
264
281
|
/* The entry pill: frosted glass over any page, the chat's mark, a ring running round it until the
|
|
265
282
|
visitor has asked something. Phones tap it; wide screens type into it. */
|
|
266
|
-
.entry { position: fixed; right: 20px; bottom: 20px
|
|
283
|
+
.entry { position: fixed; right: 20px; bottom: calc(20px + env(safe-area-inset-bottom, 0px));
|
|
284
|
+
z-index: 2147483000; }
|
|
267
285
|
[data-position="left"] .entry { right: auto; left: 20px; }
|
|
268
286
|
.pill { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 12px;
|
|
269
287
|
padding: 8px 20px 8px 8px; border-radius: var(--chat-round);
|
|
@@ -297,69 +315,75 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
297
315
|
}
|
|
298
316
|
|
|
299
317
|
/* The panel: over the dimmed page, a card at the screen's edge; on a phone a sheet from the bottom. */
|
|
300
|
-
.scrim { position: fixed;
|
|
318
|
+
.scrim { position: fixed; inset: 0; z-index: 2147482999;
|
|
301
319
|
background: rgb(15 23 42 / 0.45); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
|
|
320
|
+
/* Clipped, not hidden, where supported: a clipping box is no scroll container, so no script, focus or
|
|
321
|
+
find-in-page can scroll the panel and shift its header out of view. */
|
|
302
322
|
.panel { position: fixed; z-index: 2147483000; left: 0; right: 0; bottom: 0;
|
|
303
|
-
top:
|
|
323
|
+
top: 12px; display: flex; flex-direction: column;
|
|
304
324
|
background: var(--chat-surface); border-radius: var(--chat-radius) var(--chat-radius) 0 0;
|
|
305
|
-
box-shadow: var(--chat-shadow); overflow: hidden; }
|
|
325
|
+
box-shadow: var(--chat-shadow); overflow: hidden; overflow: clip; }
|
|
306
326
|
.scrim[hidden], .panel[hidden] { display: none; }
|
|
327
|
+
/* Focused by script only (a phone reopening a conversation), never a stop for Tab. */
|
|
328
|
+
.panel:focus { outline: none; }
|
|
307
329
|
@media (min-width: 1024px) {
|
|
330
|
+
.scrim { top: var(--chat-top, 0px); }
|
|
308
331
|
.panel { left: auto; right: 20px; bottom: 20px; top: calc(var(--chat-top, 0px) + 20px);
|
|
309
332
|
width: min(var(--chat-width, 700px), calc(100vw - 40px)); border-radius: var(--chat-radius); }
|
|
310
333
|
[data-position="left"] .panel { right: auto; left: 20px; }
|
|
311
334
|
}
|
|
312
|
-
.header { display: flex; align-items: center; gap: 8px; padding:
|
|
335
|
+
.header { display: flex; align-items: center; gap: 8px; padding: 12px 24px 4px; }
|
|
336
|
+
@media (min-width: 1024px) { .header { padding-top: 20px; } }
|
|
313
337
|
.title { margin: 0 auto 0 0; font-size: 13px; font-weight: 600; letter-spacing: 0.16em;
|
|
314
338
|
text-transform: uppercase; color: var(--chat-accent); }
|
|
315
339
|
.icon-button { flex: none; display: grid; place-items: center; width: 40px; height: 40px; padding: 0;
|
|
316
340
|
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
317
341
|
color: var(--chat-text); cursor: pointer; }
|
|
318
|
-
.icon-button:hover { border-color: var(--chat-primary); }
|
|
319
342
|
/* Closing is the panel's one strong control, filled like the site's buttons. */
|
|
320
343
|
.close { background: var(--chat-primary); border-color: var(--chat-primary); color: var(--chat-on-primary); }
|
|
321
|
-
.
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
.notice { margin: 0; padding: 0 24px 14px; font-size: 13px; color: var(--chat-muted); }
|
|
345
|
+
/* Positioned: the absolute speaker labels (.sr-only) scroll with the list instead of giving the panel a
|
|
346
|
+
scroll range, and a turn's offsetTop is measured from the list. */
|
|
347
|
+
.messages { position: relative; flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 24px 24px 40px;
|
|
324
348
|
display: flex; flex-direction: column; gap: 16px; background: var(--chat-surface-soft);
|
|
325
349
|
container-type: inline-size; }
|
|
326
350
|
.turn { display: flex; flex-direction: column; }
|
|
327
351
|
.message { max-width: 88%; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
328
352
|
.message[data-role="visitor"] { align-self: flex-end; margin-left: 48px; padding: 12px 16px;
|
|
329
|
-
border-radius: calc(var(--chat-radius) * 0.75);
|
|
353
|
+
border: 1px solid var(--chat-tint-edge); border-radius: calc(var(--chat-radius) * 0.75);
|
|
354
|
+
background: var(--chat-secondary);
|
|
330
355
|
color: var(--chat-on-secondary); }
|
|
331
356
|
.message[data-role="assistant"] { align-self: flex-start; max-width: 100%; font-size: 16px; line-height: 1.6; }
|
|
332
357
|
.message strong { font-weight: 600; }
|
|
333
358
|
.ui { align-self: stretch; display: flex; flex-direction: column; gap: 12px; }
|
|
334
|
-
.thinking { display: flex; gap: 5px; padding: 4px 2px; }
|
|
359
|
+
.thinking { display: flex; align-items: center; gap: 5px; padding: 4px 2px; }
|
|
360
|
+
.thinking-label { margin-left: 6px; color: var(--chat-muted); font-size: 14px; }
|
|
335
361
|
.thinking i { width: 7px; height: 7px; border-radius: 999px; background: var(--chat-accent); opacity: 0.5; }
|
|
336
|
-
.dock { position: relative; padding: 10px 20px 16px
|
|
362
|
+
.dock { position: relative; padding: 10px 20px calc(16px + env(safe-area-inset-bottom, 0px));
|
|
363
|
+
background: var(--chat-surface-soft); }
|
|
337
364
|
.dock::before { content: ""; position: absolute; left: 0; right: 0; top: -28px; height: 28px;
|
|
338
365
|
pointer-events: none; background: linear-gradient(to top, var(--chat-surface-soft), transparent); }
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
/* 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: 0 0 10px; }
|
|
368
|
+
/* Suggestions and quick replies: filled pills; a suggestion has an icon for its topic in the accent
|
|
369
|
+
colour (a quick reply is the model's words, an icon guessed from them can be wrong). */
|
|
343
370
|
.suggestion, .chip { flex: none; max-width: 100%; display: inline-flex; align-items: center; gap: 8px;
|
|
344
|
-
border: 1px solid
|
|
371
|
+
border: 1px solid var(--chat-tint-edge); border-radius: var(--chat-round); background: var(--chat-secondary);
|
|
345
372
|
color: var(--chat-on-secondary); padding: 8px 16px 8px 12px; font: inherit; font-size: 14px;
|
|
346
|
-
font-weight: 600; line-height: 1.35; text-align: left;
|
|
347
|
-
.
|
|
348
|
-
|
|
349
|
-
.chip { white-space: normal; }
|
|
350
|
-
.suggestion:hover { border-color: var(--chat-primary); background: var(--chat-surface); color: var(--chat-text); }
|
|
351
|
-
@media (min-width: 1024px) {
|
|
352
|
-
.suggestions { flex-wrap: wrap; overflow: visible; margin: 0 0 10px; padding: 0; }
|
|
353
|
-
.suggestion { white-space: normal; }
|
|
354
|
-
}
|
|
373
|
+
font-weight: 600; line-height: 1.35; text-align: left; cursor: pointer; }
|
|
374
|
+
.chip { padding-left: 16px; }
|
|
375
|
+
.suggestion svg { flex: none; color: var(--chat-accent); }
|
|
355
376
|
.composer { display: flex; align-items: flex-end; gap: 8px; padding: 6px 6px 6px 18px;
|
|
356
377
|
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
357
378
|
box-shadow: 0 4px 16px rgb(15 23 42 / 0.08); }
|
|
358
|
-
|
|
379
|
+
/* The field's ring, not the send button's too (that one has its own). */
|
|
380
|
+
.composer:has(textarea:focus-visible) { outline: 2px solid var(--chat-primary); outline-offset: 2px; }
|
|
359
381
|
/* 16px keeps iOS from zooming into the field. */
|
|
360
382
|
.composer textarea { flex: 1; min-width: 0; min-height: 36px; max-height: 128px; resize: none; border: 0;
|
|
361
383
|
outline: none; background: transparent; padding: 6px 0; font: inherit; font-size: 16px;
|
|
362
384
|
line-height: 1.5; color: inherit; field-sizing: content; }
|
|
385
|
+
/* The field's only visible label: the muted text colour, not the browser's grey (below AA in dark). */
|
|
386
|
+
.composer textarea::placeholder { color: var(--chat-muted); opacity: 1; }
|
|
363
387
|
.send { flex: none; display: grid; place-items: center; width: 36px; height: 36px; padding: 0; border: 0;
|
|
364
388
|
border-radius: var(--chat-round); background: var(--chat-primary); color: var(--chat-on-primary); cursor: pointer; }
|
|
365
389
|
.send:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
@@ -374,8 +398,11 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
374
398
|
white-space: pre-wrap; }
|
|
375
399
|
/* The bubble's own text colour, softened: the muted grey suits the surface, not a set secondary. */
|
|
376
400
|
.pending figcaption { margin-bottom: 2px; color: inherit; opacity: 0.75; }
|
|
377
|
-
|
|
401
|
+
/* The chat cannot answer: its reply to the visitor's message, the ways on as buttons. */
|
|
402
|
+
.fallback { display: flex; flex-direction: column; gap: 12px; }
|
|
378
403
|
.fallback p { margin: 0; }
|
|
404
|
+
.fallback-links { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
405
|
+
.fallback-links > .button { margin: 0; }
|
|
379
406
|
|
|
380
407
|
/* What the replies show. */
|
|
381
408
|
.card { border-radius: var(--chat-radius); overflow: hidden; background: var(--chat-surface);
|
|
@@ -397,7 +424,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
397
424
|
box-shadow: 0 2px 8px rgb(0 0 0 / 0.18); cursor: pointer; opacity: 0; }
|
|
398
425
|
.gallery-arrow[data-side="prev"] { left: 10px; }
|
|
399
426
|
.gallery-arrow[data-side="next"] { right: 10px; }
|
|
400
|
-
.gallery-
|
|
427
|
+
.gallery-arrow:focus-visible { opacity: 1; }
|
|
401
428
|
@media (hover: none) { .gallery-arrow:not(:focus-visible) { pointer-events: none; } }
|
|
402
429
|
/* The row scrolls, so it clips: room below for the cards' shadow, taken back from the gap under it. */
|
|
403
430
|
.carousel { gap: 12px; padding: 2px 2px 16px; margin-bottom: -10px; }
|
|
@@ -408,74 +435,112 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
408
435
|
.carousel > .card { flex-basis: calc((100% - 24px) / 2.15); }
|
|
409
436
|
.carousel:has(> .card:nth-child(2):last-child) > .card { flex-basis: calc((100% - 12px) / 2); }
|
|
410
437
|
.carousel-wrap:has(> .carousel > .card:nth-child(2):last-child) > .snap-nav { display: none; }
|
|
411
|
-
/*
|
|
412
|
-
.carousel-wrap > .snap-nav > span {
|
|
438
|
+
/* The count follows the first card in view, and two show at once: it would skip numbers. */
|
|
439
|
+
.carousel-wrap > .snap-nav > span { display: none; }
|
|
413
440
|
}
|
|
414
|
-
|
|
415
|
-
.snap-nav
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
441
|
+
/* The row's controls, grouped at its end: "‹ 2 / 8 ›". */
|
|
442
|
+
.snap-nav { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 6px; }
|
|
443
|
+
.snap-nav button, .calendar-head button { flex: none; display: grid; place-items: center; width: 36px;
|
|
444
|
+
height: 36px; padding: 0; border: 1px solid var(--chat-border); background: var(--chat-surface);
|
|
445
|
+
color: var(--chat-text); border-radius: var(--chat-round); cursor: pointer; }
|
|
446
|
+
.snap-nav button:disabled, .calendar-head button:disabled { opacity: 0.5; cursor: default; }
|
|
419
447
|
.rating { color: #b7791f; font-weight: 700; }
|
|
420
448
|
.breakdown .line { display: flex; justify-content: space-between; gap: 12px; }
|
|
421
449
|
.breakdown .total { border-top: 1px solid var(--chat-border); padding-top: 6px; margin-top: 2px;
|
|
422
450
|
font-weight: 700; }
|
|
423
|
-
.
|
|
424
|
-
.
|
|
425
|
-
|
|
426
|
-
|
|
451
|
+
.alt-chips, .quick-replies { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
452
|
+
.alt-caption { margin: 0 0 4px; font-size: 13px; color: var(--chat-muted); }
|
|
453
|
+
.chip-link { display: inline-flex; align-items: center; border: 1px solid var(--chat-primary);
|
|
454
|
+
color: var(--chat-primary); border-radius: var(--chat-round); padding: 5px 10px; text-decoration: none;
|
|
455
|
+
font-size: 14px; }
|
|
427
456
|
.calendar { border: 1px solid var(--chat-border); border-radius: var(--chat-radius); padding: 12px;
|
|
428
457
|
background: var(--chat-surface); }
|
|
429
458
|
.calendar-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
430
459
|
font-weight: 700; margin-bottom: 8px; text-align: center; }
|
|
431
460
|
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; text-align: center;
|
|
432
|
-
font-size:
|
|
461
|
+
font-size: 13px; }
|
|
433
462
|
.weekday { color: var(--chat-muted); font-weight: 600; }
|
|
434
463
|
.day { padding: 4px 0; border-radius: min(6px, var(--chat-radius)); }
|
|
435
464
|
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
|
|
436
465
|
white-space: nowrap; }
|
|
466
|
+
/* Free, booked and the stay must tell apart in dark too: a stronger tint for free, an edge for booked
|
|
467
|
+
(inset, so a booked day is no taller than the others). */
|
|
437
468
|
.day[data-status="free"], .legend i[data-status="free"] {
|
|
438
|
-
background: color-mix(in srgb, var(--chat-primary)
|
|
469
|
+
background: color-mix(in srgb, var(--chat-primary) 26%, var(--chat-surface)); color: var(--chat-text); }
|
|
439
470
|
.day[data-status="booked"], .legend i[data-status="booked"] { background: var(--chat-surface-soft);
|
|
440
|
-
color: var(--chat-muted);
|
|
471
|
+
box-shadow: inset 0 0 0 1px var(--chat-border); color: var(--chat-muted);
|
|
441
472
|
text-decoration: line-through; }
|
|
442
473
|
.day[data-status="closed"] { color: var(--chat-muted); opacity: 0.6; }
|
|
443
474
|
.day[data-stay], .legend i[data-stay] { background: var(--chat-primary); color: var(--chat-on-primary);
|
|
444
475
|
text-decoration: none; }
|
|
445
|
-
.legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size:
|
|
446
|
-
.legend i { display: inline-block; width:
|
|
447
|
-
vertical-align: -1px; }
|
|
476
|
+
.legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 13px; }
|
|
477
|
+
.legend i { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 4px;
|
|
478
|
+
vertical-align: -1px; border: 1px solid var(--chat-border); }
|
|
448
479
|
.compare { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; }
|
|
449
480
|
.compare-col { border-radius: var(--chat-radius); padding: 10px; background: var(--chat-surface);
|
|
450
|
-
box-shadow: var(--chat-card-shadow); display: flex; flex-direction: column; gap: 6px; font-size:
|
|
451
|
-
min-width: 0; }
|
|
452
|
-
/* Three columns leave a button about 90px: "Zur Buchung" fits on one line only without side padding. */
|
|
453
|
-
.compare-col .button { padding: 8px 2px; font-size: 13px; }
|
|
481
|
+
box-shadow: var(--chat-card-shadow); display: flex; flex-direction: column; gap: 6px; font-size: 14px;
|
|
482
|
+
min-width: 0; hyphens: auto; }
|
|
454
483
|
.compare-col dl { margin: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
|
455
|
-
.compare-col dt { color: var(--chat-muted); font-size:
|
|
456
|
-
.compare-col dd { margin: 0; overflow-wrap:
|
|
484
|
+
.compare-col dt { color: var(--chat-muted); font-size: 12px; margin-top: 4px; }
|
|
485
|
+
.compare-col dd { margin: 0; overflow-wrap: break-word; }
|
|
486
|
+
.compare-col .price { font-size: 16px; }
|
|
487
|
+
/* A phone has room for two columns, not three of 100px with a word on each line: the third peeks in
|
|
488
|
+
and the row scrolls, with room below for the columns' shadow, as the carousel. */
|
|
489
|
+
@container (max-width: 519px) {
|
|
490
|
+
/* A phone shows one card at a time: each as tall as its own content, so the first card's price and
|
|
491
|
+
button are not pushed down to the tallest card's foot. */
|
|
492
|
+
.carousel { align-items: flex-start; }
|
|
493
|
+
.compare { grid-auto-columns: minmax(150px, 1fr); overflow-x: auto; scroll-snap-type: x mandatory;
|
|
494
|
+
scrollbar-width: none; padding: 2px 2px 16px; margin-bottom: -10px; }
|
|
495
|
+
.compare::-webkit-scrollbar { display: none; }
|
|
496
|
+
.compare-col { scroll-snap-align: start; }
|
|
497
|
+
}
|
|
498
|
+
/* Side by side, the rows line up across the columns (a long name pushes every row of its column down
|
|
499
|
+
otherwise): the name, the list's eight cells and the button share the comparison's ten rows. */
|
|
500
|
+
@supports (grid-template-rows: subgrid) {
|
|
501
|
+
@container (min-width: 520px) {
|
|
502
|
+
.compare { grid-template-rows: repeat(10, auto); row-gap: 0; }
|
|
503
|
+
.compare-col { display: grid; grid-row: span 10; grid-template-rows: subgrid; }
|
|
504
|
+
.compare-col dl { display: grid; grid-row: span 8; grid-template-rows: subgrid; }
|
|
505
|
+
.compare-col .button { grid-row: -2; }
|
|
506
|
+
}
|
|
507
|
+
}
|
|
457
508
|
.card-body { padding: 16px 18px 12px; display: flex; flex-direction: column; gap: 6px; }
|
|
458
509
|
.card-title { margin: 0; font-size: 18px; font-weight: 500; line-height: 1.3; }
|
|
510
|
+
/* 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. The single card, the quote and Compare show it whole. */
|
|
512
|
+
.carousel .card-title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
|
|
513
|
+
overflow: hidden; }
|
|
459
514
|
/* A rental card: place and score, name, facts and wishes; the stay's price and button below a line. */
|
|
460
515
|
.rental-card, .stay-card { display: flex; flex-direction: column; }
|
|
461
516
|
.rental-card .card-body { flex: 1; }
|
|
462
517
|
.card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
|
|
463
518
|
.place { color: var(--chat-accent); font-size: 14px; }
|
|
464
519
|
.score-chip { margin-left: auto; padding: 2px 8px; border-radius: calc(var(--chat-radius) * 0.4);
|
|
465
|
-
background: var(--chat-secondary); color: var(--chat-on-secondary); font-size:
|
|
520
|
+
background: var(--chat-secondary); color: var(--chat-on-secondary); font-size: 13px; font-weight: 700; }
|
|
466
521
|
.specs { margin: 0; color: var(--chat-muted); font-size: 14px; }
|
|
467
522
|
.card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
468
523
|
margin: 0 18px; padding: 12px 0 16px; border-top: 1px solid var(--chat-border); }
|
|
469
524
|
.foot-price { display: flex; flex-direction: column; min-width: 0; }
|
|
470
525
|
.foot-price .price { font-size: 18px; line-height: 1.3; }
|
|
471
|
-
.foot-meta { color: var(--chat-muted); font-size:
|
|
526
|
+
.foot-meta { color: var(--chat-muted); font-size: 14px; }
|
|
527
|
+
.foot-meta .period { white-space: nowrap; }
|
|
472
528
|
.button.compact { margin: 0 0 0 auto; padding: 9px 20px; font-size: 14px; white-space: nowrap; }
|
|
473
529
|
.muted { color: var(--chat-muted); font-size: 13px; }
|
|
474
530
|
.price { font-weight: 700; }
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
531
|
+
/* Relative: the link's screen-reader name (an absolute .sr-only) stays inside a scrolled row, which
|
|
532
|
+
clips it, instead of sticking out of the row and widening the panel. */
|
|
533
|
+
.button { position: relative; display: inline-flex; align-items: center; justify-content: center; margin-top: 6px;
|
|
534
|
+
text-align: center; text-decoration: none; padding: 10px 16px; border-radius: var(--chat-round);
|
|
535
|
+
background: var(--chat-primary); color: var(--chat-on-primary); border: 0; cursor: pointer; font: inherit;
|
|
536
|
+
font-weight: 600; }
|
|
478
537
|
.link { color: var(--chat-primary); }
|
|
538
|
+
/* A button on its own in a reply: as wide as its label needs, not the panel's 650px. */
|
|
539
|
+
.ui > .button, .ui > .link { align-self: flex-start; min-width: 14rem; }
|
|
540
|
+
/* The way to the contact page in a reply (ContactLink) and in the fallback: a button, outlined. */
|
|
541
|
+
.ui > .link, .fallback-links > .link { display: inline-flex; align-items: center; justify-content: center; padding: 10px 16px;
|
|
542
|
+
border: 1px solid var(--chat-primary); border-radius: var(--chat-round); text-decoration: none;
|
|
543
|
+
font-weight: 600; }
|
|
479
544
|
/* A link out of the chat while a reply still streams (ReplyLink). */
|
|
480
545
|
[aria-disabled="true"] { opacity: 0.5; cursor: progress; }
|
|
481
546
|
|
|
@@ -484,6 +549,12 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
484
549
|
.no-photo { display: none; aspect-ratio: 5 / 3; place-items: center; background: var(--chat-surface-soft);
|
|
485
550
|
color: var(--chat-muted); }
|
|
486
551
|
.carousel .no-photo { display: grid; }
|
|
552
|
+
/* 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
|
+
@container (min-width: 520px) {
|
|
554
|
+
.ui > .rental-card .gallery img { aspect-ratio: 2 / 1; max-height: 280px; }
|
|
555
|
+
/* 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; }
|
|
557
|
+
}
|
|
487
558
|
/* Wishes a found stay matches, ticked under the name. */
|
|
488
559
|
.wishes { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
489
560
|
.wishes li { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px 4px 10px;
|
|
@@ -491,21 +562,31 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
491
562
|
.wishes svg { color: var(--chat-accent); }
|
|
492
563
|
|
|
493
564
|
/* The price calendar: a column per arrival, the bar's height its total. */
|
|
494
|
-
.price-columns { list-style: none; margin: 10px 0 0; padding: 0; display: grid;
|
|
495
|
-
grid-auto-columns: minmax(68px, 1fr); gap: 6px; overflow-x: auto;
|
|
496
|
-
|
|
565
|
+
.price-columns { position: relative; list-style: none; margin: 10px 0 0; padding: 0; display: grid;
|
|
566
|
+
grid-auto-flow: column; grid-auto-columns: minmax(68px, 1fr); gap: 6px; overflow-x: auto;
|
|
567
|
+
scrollbar-width: none; }
|
|
568
|
+
.price-columns::-webkit-scrollbar { display: none; }
|
|
569
|
+
/* More columns beyond an edge fade out there. */
|
|
570
|
+
.price-columns[data-fade="end"] { mask-image: linear-gradient(to left, transparent, #000 32px); }
|
|
571
|
+
.price-columns[data-fade="start"] { mask-image: linear-gradient(to right, transparent, #000 32px); }
|
|
572
|
+
.price-columns[data-fade="both"] { mask-image: linear-gradient(to right, transparent, #000 32px,
|
|
573
|
+
#000 calc(100% - 32px), transparent); }
|
|
574
|
+
/* The arrival's day at the column's foot, level across the row when "nicht buchbar" takes two lines. */
|
|
575
|
+
.price-column { height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 4px;
|
|
497
576
|
padding: 6px 2px; border: 1px solid transparent; border-radius: 10px; background: transparent;
|
|
498
577
|
color: inherit; font: inherit; cursor: pointer; }
|
|
499
578
|
.price-column:disabled { cursor: default; }
|
|
500
|
-
|
|
579
|
+
/* Inside the row that scrolls, so it clips: the ring sits inside the column. */
|
|
580
|
+
.price-column:focus-visible { outline-offset: -2px; }
|
|
501
581
|
.price-column[data-asked] { border-color: var(--chat-border); background: var(--chat-surface-soft); }
|
|
502
582
|
.bar-track { display: flex; align-items: flex-end; justify-content: center; width: 100%; height: 72px; }
|
|
503
583
|
.bar { width: 60%; border-radius: min(6px, var(--chat-radius)) min(6px, var(--chat-radius)) 0 0;
|
|
504
584
|
background: color-mix(in srgb, var(--chat-primary) 30%, var(--chat-surface)); }
|
|
505
585
|
.price-column[data-cheapest] .bar, .legend i[data-cheapest] { background: var(--chat-primary); }
|
|
506
586
|
.legend i[data-asked] { background: var(--chat-surface-soft); border: 1px solid var(--chat-border); }
|
|
507
|
-
.price-
|
|
508
|
-
.price-
|
|
587
|
+
.price-column > .muted { margin-top: auto; }
|
|
588
|
+
.price-text { font-size: 14px; font-weight: 700; white-space: nowrap; }
|
|
589
|
+
.price-column:not([data-free]) .price-text { color: var(--chat-muted); font-weight: 500; white-space: normal; }
|
|
509
590
|
|
|
510
591
|
/* Reviews: the PMS's score and grade, its categories, a few texts. */
|
|
511
592
|
.score { display: flex; align-items: center; gap: 12px; margin-top: 4px; }
|
|
@@ -531,7 +612,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
531
612
|
.map-gate { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
|
|
532
613
|
min-height: 150px; padding: 16px; text-align: center; background: var(--chat-surface-soft); }
|
|
533
614
|
.map-gate p { margin: 0; }
|
|
534
|
-
.map-attribution { margin: 0; padding: 2px 8px; font-size:
|
|
615
|
+
.map-attribution { margin: 0; padding: 2px 8px; font-size: 12px; color: var(--chat-muted); text-align: right; }
|
|
535
616
|
.map-attribution a { color: inherit; }
|
|
536
617
|
.map-pin { display: block; width: 18px; height: 18px; border-radius: 999px; background: var(--chat-primary);
|
|
537
618
|
border: 3px solid var(--chat-surface); box-shadow: 0 2px 6px rgb(0 0 0 / 0.35); }
|
|
@@ -542,6 +623,24 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
542
623
|
.maplibregl-canvas:focus-visible { outline: 2px solid var(--chat-primary); outline-offset: -2px; }
|
|
543
624
|
.maplibregl-marker { position: absolute; top: 0; left: 0; will-change: transform; }
|
|
544
625
|
|
|
626
|
+
/* Hover only where there is a pointer: on a touch screen a tapped chip would keep its hover look. */
|
|
627
|
+
@media (hover: hover) {
|
|
628
|
+
.icon-button:hover { border-color: var(--chat-primary); }
|
|
629
|
+
.close:hover { opacity: 0.9; }
|
|
630
|
+
.suggestion:hover, .chip:not(:disabled):hover { border-color: var(--chat-primary);
|
|
631
|
+
background: var(--chat-surface); color: var(--chat-text); }
|
|
632
|
+
.gallery-wrap:hover .gallery-arrow:not(:disabled) { opacity: 1; }
|
|
633
|
+
.price-column[data-free]:not(:disabled):hover { border-color: var(--chat-primary); }
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/* Fingers: every control at least 44px each way. */
|
|
637
|
+
@media (pointer: coarse) {
|
|
638
|
+
.icon-button, .send, .snap-nav button, .calendar-head button, .gallery-arrow { width: 44px; height: 44px; }
|
|
639
|
+
.composer { padding: 4px 4px 4px 18px; }
|
|
640
|
+
.composer textarea { min-height: 44px; padding-block: 10px; }
|
|
641
|
+
.chip, .suggestion, .button, .chip-link, .ui > .link, .fallback-links > .link { min-height: 44px; }
|
|
642
|
+
}
|
|
643
|
+
|
|
545
644
|
[data-theme="soft"] { --chat-radius: calc(var(--chat-base-radius) * 2.4); --chat-shadow: 0 12px 32px rgb(15 23 42 / 0.12);
|
|
546
645
|
--chat-card-shadow: 0 4px 18px rgb(15 23 42 / 0.07); }
|
|
547
646
|
[data-theme="minimal"] { --chat-radius: calc(var(--chat-base-radius) * 0.6); --chat-shadow: none; --chat-card-shadow: none; }
|
|
@@ -554,11 +653,13 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
554
653
|
@keyframes chat-in { from { opacity: 0; transform: translateY(16px); } }
|
|
555
654
|
@keyframes chat-fade { from { opacity: 0; } }
|
|
556
655
|
@keyframes chat-pulse { 50% { opacity: 1; transform: translateY(-2px); } }
|
|
656
|
+
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
|
|
657
|
+
.pill:hover { transform: translateY(-2px); }
|
|
658
|
+
}
|
|
557
659
|
@media (prefers-reduced-motion: no-preference) {
|
|
558
660
|
.glow::before { animation: chat-spin 3.2s linear infinite; }
|
|
559
661
|
.entry { animation: chat-in 400ms cubic-bezier(0.25, 1, 0.5, 1); }
|
|
560
662
|
.pill { transition: transform 200ms ease-out; }
|
|
561
|
-
.pill:hover { transform: translateY(-2px); }
|
|
562
663
|
.panel { animation: chat-in 400ms cubic-bezier(0.25, 1, 0.5, 1); }
|
|
563
664
|
.scrim { animation: chat-fade 300ms ease-out; }
|
|
564
665
|
.thinking i { animation: chat-pulse 1.1s ease-in-out infinite; }
|
|
@@ -572,7 +673,7 @@ ${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
|
572
673
|
`;
|
|
573
674
|
//#endregion
|
|
574
675
|
//#region src/WebsiteChat.tsx
|
|
575
|
-
const Panel = lazy(() => import("./panel-
|
|
676
|
+
const Panel = lazy(() => import("./panel-CfS3SuRX.js"));
|
|
576
677
|
/** See {@link WebsiteChatComponent}. */
|
|
577
678
|
const WebsiteChat = function WebsiteChat(props) {
|
|
578
679
|
const { endpoint, locale, theme = "classic", position = "right" } = props;
|
|
@@ -644,7 +745,7 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
644
745
|
fallback: null,
|
|
645
746
|
children: /* @__PURE__ */ jsx(Panel, {
|
|
646
747
|
endpoint,
|
|
647
|
-
clientVersion: "1.
|
|
748
|
+
clientVersion: "1.2.0",
|
|
648
749
|
locale,
|
|
649
750
|
copy,
|
|
650
751
|
searchPath: props.searchPath,
|