@v-office/website-chat 1.0.1 → 1.1.1
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 +8 -3
- package/dist/{catalog-RXKmEmMW.js → catalog-y3xQ2jXW.js} +16 -9
- package/dist/catalog.d.ts +19 -0
- package/dist/catalog.js +2 -2
- package/dist/icons-DIqOmKoe.js +243 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +225 -84
- package/dist/{panel-C0Bb7ln-.js → panel-BsocDe9I.js} +195 -115
- package/instructions/CHANGELOG.md +2 -0
- package/instructions/MIGRATION.md +2 -0
- package/instructions/versions/1.1.0/CHANGELOG.md +52 -0
- package/instructions/versions/1.1.0/MIGRATION.md +22 -0
- package/instructions/versions/1.1.1/CHANGELOG.md +26 -0
- package/instructions/versions/1.1.1/MIGRATION.md +13 -0
- package/package.json +3 -3
- package/dist/icons-B5PAY3aU.js +0 -145
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 { f as chatAvailable, s as Spark, t as Arrow, u as loadConversation } from "./icons-DIqOmKoe.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,7 +12,7 @@ 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
|
-
greeting: "Hallo!
|
|
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
|
"Ein Ferienhaus für 4 Personen in den Sommerferien",
|
|
18
18
|
"Eine Woche mit Hund, gern mit eingezäuntem Garten",
|
|
@@ -31,6 +31,13 @@ const DEFAULT_COPY = {
|
|
|
31
31
|
contactLink: "Kontakt aufnehmen",
|
|
32
32
|
bookStay: "Zur Buchung",
|
|
33
33
|
viewRental: "Zur Unterkunft",
|
|
34
|
+
book: "Buchen",
|
|
35
|
+
view: "Ansehen",
|
|
36
|
+
matchBadge: "Passt genau",
|
|
37
|
+
nights: {
|
|
38
|
+
one: "{n} Nacht",
|
|
39
|
+
other: "{n} Nächte"
|
|
40
|
+
},
|
|
34
41
|
total: "Gesamtpreis",
|
|
35
42
|
notBookable: "Für diesen Zeitraum nicht buchbar",
|
|
36
43
|
photo: "Foto",
|
|
@@ -77,7 +84,7 @@ const DEFAULT_COPY = {
|
|
|
77
84
|
title: "Travel assistant",
|
|
78
85
|
newConversation: "New conversation",
|
|
79
86
|
notice: "You are chatting with an AI. Prices and availability in the cards come straight from our booking system.",
|
|
80
|
-
greeting: "Hello!
|
|
87
|
+
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.",
|
|
81
88
|
suggestions: [
|
|
82
89
|
"A holiday home for 4 in the summer holidays",
|
|
83
90
|
"A week with our dog, ideally with a fenced garden",
|
|
@@ -96,6 +103,13 @@ const DEFAULT_COPY = {
|
|
|
96
103
|
contactLink: "Contact us",
|
|
97
104
|
bookStay: "Book this stay",
|
|
98
105
|
viewRental: "View the rental",
|
|
106
|
+
book: "Book",
|
|
107
|
+
view: "View",
|
|
108
|
+
matchBadge: "Good match",
|
|
109
|
+
nights: {
|
|
110
|
+
one: "{n} night",
|
|
111
|
+
other: "{n} nights"
|
|
112
|
+
},
|
|
99
113
|
total: "Total",
|
|
100
114
|
notBookable: "Not bookable for these dates",
|
|
101
115
|
photo: "Photo",
|
|
@@ -138,17 +152,88 @@ const DEFAULT_COPY = {
|
|
|
138
152
|
};
|
|
139
153
|
//#endregion
|
|
140
154
|
//#region src/theme.ts
|
|
141
|
-
/** The curated looks over one layout
|
|
142
|
-
* lighter shadows), `minimal` (borders instead of shadows,
|
|
155
|
+
/** The curated looks over one layout, each relative to the site's corner radius: `classic` (soft
|
|
156
|
+
* shadows), `soft` (rounder corners, lighter shadows), `minimal` (borders instead of shadows, tighter
|
|
157
|
+
* corners). */
|
|
143
158
|
const CHAT_THEMES = [
|
|
144
159
|
"classic",
|
|
145
160
|
"soft",
|
|
146
161
|
"minimal"
|
|
147
162
|
];
|
|
148
|
-
/**
|
|
163
|
+
/** The site tokens the chat follows: the shadcn set every AI Studio template defines (and
|
|
164
|
+
* website-components reads), the chat variable each sets, and the chat's own value for a site
|
|
165
|
+
* without it. CSS custom properties reach into the shadow root; `all: initial` leaves them alone.
|
|
166
|
+
* Not `--secondary`: shadcn sets it next to `--muted`, so the visitor's messages would vanish on the
|
|
167
|
+
* message area; the chat's tint is the site's primary, lightened (`--chat-secondary`). */
|
|
168
|
+
const SITE_COLORS = [
|
|
169
|
+
{
|
|
170
|
+
site: "--primary",
|
|
171
|
+
chat: "--chat-primary",
|
|
172
|
+
own: "#1f4f8b"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
site: "--primary-foreground",
|
|
176
|
+
chat: "--chat-on-primary",
|
|
177
|
+
own: "#fff"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
site: "--background",
|
|
181
|
+
chat: "--chat-surface",
|
|
182
|
+
own: "#fff"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
site: "--muted",
|
|
186
|
+
chat: "--chat-surface-soft",
|
|
187
|
+
own: "#f5f7fa"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
site: "--foreground",
|
|
191
|
+
chat: "--chat-text",
|
|
192
|
+
own: "#1b1f24"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
site: "--muted-foreground",
|
|
196
|
+
chat: "--chat-muted",
|
|
197
|
+
own: "#5b6470"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
site: "--border",
|
|
201
|
+
chat: "--chat-border",
|
|
202
|
+
own: "#d9dee5"
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
/** shadcn's base radius; the themes scale it (classic 1.6×, so 16px for shadcn's 10px). */
|
|
206
|
+
const SITE_RADIUS = {
|
|
207
|
+
site: "--radius",
|
|
208
|
+
chat: "--chat-base-radius",
|
|
209
|
+
own: "10px"
|
|
210
|
+
};
|
|
211
|
+
/** See {@link UnusableSiteTokens}. */
|
|
212
|
+
const unusableSiteTokens = (host) => {
|
|
213
|
+
if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return {};
|
|
214
|
+
const style = getComputedStyle(host);
|
|
215
|
+
const unusable = (token, property) => {
|
|
216
|
+
const value = style.getPropertyValue(token.site).trim();
|
|
217
|
+
return value !== "" && !CSS.supports(property, value);
|
|
218
|
+
};
|
|
219
|
+
return Object.fromEntries([...SITE_COLORS.filter((token) => unusable(token, "color")).map((t) => [t.chat, t.own]), ...unusable(SITE_RADIUS, "width") ? [[SITE_RADIUS.chat, SITE_RADIUS.own]] : []]);
|
|
220
|
+
};
|
|
221
|
+
/** See {@link ColorVariables}. An override brings its own text colour, as before the chat took the
|
|
222
|
+
* site's tokens: the site's foregrounds suit the site's colours (a dark theme's light primary has
|
|
223
|
+
* dark text), not the brand colour a site sets here. */
|
|
149
224
|
const colorVariables = (colors) => ({
|
|
150
|
-
...colors?.primary && {
|
|
151
|
-
|
|
225
|
+
...colors?.primary && {
|
|
226
|
+
"--chat-primary": colors.primary,
|
|
227
|
+
"--chat-on-primary": "#fff"
|
|
228
|
+
},
|
|
229
|
+
...colors?.secondary && {
|
|
230
|
+
"--chat-secondary": colors.secondary,
|
|
231
|
+
"--chat-on-secondary": "#1b1f24"
|
|
232
|
+
},
|
|
233
|
+
...colors?.accent && {
|
|
234
|
+
"--chat-accent": colors.accent,
|
|
235
|
+
"--chat-on-accent": "#fff"
|
|
236
|
+
}
|
|
152
237
|
});
|
|
153
238
|
/** The widget's stylesheet, inserted into its shadow root. */
|
|
154
239
|
const CHAT_CSS = `
|
|
@@ -157,15 +242,17 @@ const CHAT_CSS = `
|
|
|
157
242
|
\`:host\` rules, and only important ones reverse that. */
|
|
158
243
|
all: initial !important;
|
|
159
244
|
font-family: inherit !important;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
--chat-secondary:
|
|
163
|
-
--chat-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
--chat-
|
|
167
|
-
--chat-
|
|
168
|
-
--chat-radius:
|
|
245
|
+
${SITE_COLORS.map((t) => ` ${t.chat}: var(${t.site}, ${t.own});`).join("\n")}
|
|
246
|
+
${SITE_RADIUS.chat}: var(${SITE_RADIUS.site}, ${SITE_RADIUS.own});
|
|
247
|
+
--chat-secondary: color-mix(in srgb, var(--chat-primary) 12%, var(--chat-surface));
|
|
248
|
+
--chat-on-secondary: var(--chat-text);
|
|
249
|
+
/* The small marks (the chat's name and sparkle, a card's place, icons, the ring): the primary,
|
|
250
|
+
unless a site sets an accent of its own (\`colors.accent\`). */
|
|
251
|
+
--chat-accent: var(--chat-primary);
|
|
252
|
+
--chat-on-accent: var(--chat-on-primary);
|
|
253
|
+
--chat-radius: calc(var(--chat-base-radius) * 1.6);
|
|
254
|
+
/* Buttons, chips and the pill are pills, unless the site's corners are square (radius 0). */
|
|
255
|
+
--chat-round: calc(var(--chat-base-radius) * 100);
|
|
169
256
|
--chat-shadow: 0 18px 48px rgb(15 23 42 / 0.18);
|
|
170
257
|
--chat-card-shadow: 0 1px 3px rgb(15 23 42 / 0.08), 0 8px 24px rgb(15 23 42 / 0.06);
|
|
171
258
|
/* Not set here, so a site's value reaches the widget: \`--chat-top\` keeps a sticky header uncovered,
|
|
@@ -179,15 +266,16 @@ const CHAT_CSS = `
|
|
|
179
266
|
.entry { position: fixed; right: 20px; bottom: 20px; z-index: 2147483000; }
|
|
180
267
|
[data-position="left"] .entry { right: auto; left: 20px; }
|
|
181
268
|
.pill { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 12px;
|
|
182
|
-
padding: 8px 20px 8px 8px; border-radius:
|
|
183
|
-
|
|
269
|
+
padding: 8px 20px 8px 8px; border-radius: var(--chat-round);
|
|
270
|
+
border: 1px solid color-mix(in srgb, var(--chat-surface) 70%, transparent);
|
|
271
|
+
background: color-mix(in srgb, var(--chat-surface) 86%, transparent); backdrop-filter: blur(16px) saturate(1.4);
|
|
184
272
|
-webkit-backdrop-filter: blur(16px) saturate(1.4); box-shadow: var(--chat-shadow);
|
|
185
273
|
color: var(--chat-text); font: inherit; text-align: left; cursor: pointer; }
|
|
186
274
|
.spark { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 999px;
|
|
187
|
-
background: var(--chat-
|
|
275
|
+
background: var(--chat-accent); color: var(--chat-on-accent); }
|
|
188
276
|
.pill-text { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.25; }
|
|
189
|
-
.pill-title { font-size: 12px; font-weight: 600; color: var(--chat-
|
|
190
|
-
.pill-prompt { font-size:
|
|
277
|
+
.pill-title { font-size: 12px; font-weight: 600; color: var(--chat-accent); }
|
|
278
|
+
.pill-prompt { font-size: 15px; font-weight: 500; }
|
|
191
279
|
.launcher-field { display: none; }
|
|
192
280
|
.launcher-field input { border: 0; outline: none; background: transparent; padding: 0; margin: 0;
|
|
193
281
|
font: inherit; font-size: 14px; font-weight: 600; color: var(--chat-text); width: 100%; }
|
|
@@ -195,12 +283,14 @@ const CHAT_CSS = `
|
|
|
195
283
|
.launcher-field:focus-within input::placeholder { color: var(--chat-muted); }
|
|
196
284
|
/* The field has no outline of its own: the pill shows keyboard focus, typed text or not. */
|
|
197
285
|
.launcher-field:focus-within { outline: 2px solid var(--chat-primary); outline-offset: 2px; }
|
|
198
|
-
|
|
286
|
+
/* Until the first message the pill glows in the accent colour as well. */
|
|
287
|
+
.entry[data-glow] .pill { box-shadow: var(--chat-shadow), 0 0 28px color-mix(in srgb, var(--chat-accent) 38%, transparent); }
|
|
288
|
+
.glow { position: absolute; inset: -3px; border-radius: var(--chat-round); overflow: hidden; padding: 3px;
|
|
199
289
|
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
200
290
|
-webkit-mask-composite: xor; mask: linear-gradient(#000 0 0) content-box exclude,
|
|
201
291
|
linear-gradient(#000 0 0); }
|
|
202
292
|
.glow::before { content: ""; position: absolute; inset: -150%;
|
|
203
|
-
background: conic-gradient(from 0deg, transparent 0 62%, var(--chat-
|
|
293
|
+
background: conic-gradient(from 0deg, transparent 0 62%, var(--chat-accent) 82%, transparent 100%); }
|
|
204
294
|
@media (min-width: 1024px) {
|
|
205
295
|
.launcher[data-narrow] { display: none; }
|
|
206
296
|
.launcher-field { display: flex; width: 26rem; padding: 8px; cursor: text; }
|
|
@@ -216,46 +306,54 @@ const CHAT_CSS = `
|
|
|
216
306
|
.scrim[hidden], .panel[hidden] { display: none; }
|
|
217
307
|
@media (min-width: 1024px) {
|
|
218
308
|
.panel { left: auto; right: 20px; bottom: 20px; top: calc(var(--chat-top, 0px) + 20px);
|
|
219
|
-
width: min(var(--chat-width,
|
|
309
|
+
width: min(var(--chat-width, 700px), calc(100vw - 40px)); border-radius: var(--chat-radius); }
|
|
220
310
|
[data-position="left"] .panel { right: auto; left: 20px; }
|
|
221
311
|
}
|
|
222
|
-
.header { display: flex; align-items: center; gap: 8px; padding:
|
|
223
|
-
.title { margin: 0 auto 0 0; font-size:
|
|
224
|
-
text-transform: uppercase; color: var(--chat-
|
|
225
|
-
.icon-button { flex: none; display: grid; place-items: center; width:
|
|
226
|
-
border: 1px solid var(--chat-border); border-radius:
|
|
312
|
+
.header { display: flex; align-items: center; gap: 8px; padding: 20px 24px 4px; }
|
|
313
|
+
.title { margin: 0 auto 0 0; font-size: 13px; font-weight: 600; letter-spacing: 0.16em;
|
|
314
|
+
text-transform: uppercase; color: var(--chat-accent); }
|
|
315
|
+
.icon-button { flex: none; display: grid; place-items: center; width: 40px; height: 40px; padding: 0;
|
|
316
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
227
317
|
color: var(--chat-text); cursor: pointer; }
|
|
228
318
|
.icon-button:hover { border-color: var(--chat-primary); }
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.
|
|
319
|
+
/* Closing is the panel's one strong control, filled like the site's buttons. */
|
|
320
|
+
.close { background: var(--chat-primary); border-color: var(--chat-primary); color: var(--chat-on-primary); }
|
|
321
|
+
.close:hover { opacity: 0.9; }
|
|
322
|
+
.notice { margin: 0; padding: 0 24px 14px; font-size: 12px; color: var(--chat-muted); }
|
|
323
|
+
.messages { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 24px 24px 40px;
|
|
232
324
|
display: flex; flex-direction: column; gap: 16px; background: var(--chat-surface-soft);
|
|
233
325
|
container-type: inline-size; }
|
|
234
326
|
.turn { display: flex; flex-direction: column; }
|
|
235
327
|
.message { max-width: 88%; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
236
|
-
.message[data-role="visitor"] { align-self: flex-end; margin-left: 48px; padding:
|
|
237
|
-
border-radius: var(--chat-radius); background: var(--chat-secondary);
|
|
238
|
-
|
|
328
|
+
.message[data-role="visitor"] { align-self: flex-end; margin-left: 48px; padding: 12px 16px;
|
|
329
|
+
border-radius: calc(var(--chat-radius) * 0.75); background: var(--chat-secondary);
|
|
330
|
+
color: var(--chat-on-secondary); }
|
|
331
|
+
.message[data-role="assistant"] { align-self: flex-start; max-width: 100%; font-size: 16px; line-height: 1.6; }
|
|
332
|
+
.message strong { font-weight: 600; }
|
|
239
333
|
.ui { align-self: stretch; display: flex; flex-direction: column; gap: 12px; }
|
|
240
334
|
.thinking { display: flex; gap: 5px; padding: 4px 2px; }
|
|
241
|
-
.thinking i { width: 7px; height: 7px; border-radius: 999px; background: var(--chat-
|
|
242
|
-
.dock { position: relative; padding: 10px 20px 16px; background: var(--chat-surface); }
|
|
335
|
+
.thinking i { width: 7px; height: 7px; border-radius: 999px; background: var(--chat-accent); opacity: 0.5; }
|
|
336
|
+
.dock { position: relative; padding: 10px 20px 16px; background: var(--chat-surface-soft); }
|
|
243
337
|
.dock::before { content: ""; position: absolute; left: 0; right: 0; top: -28px; height: 28px;
|
|
244
338
|
pointer-events: none; background: linear-gradient(to top, var(--chat-surface-soft), transparent); }
|
|
245
339
|
.suggestions { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; margin: 0 -20px 10px;
|
|
246
340
|
padding: 0 20px; }
|
|
247
341
|
.suggestions::-webkit-scrollbar { display: none; }
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
342
|
+
/* Suggestions and quick replies: filled pills, an icon for the topic in the accent colour. */
|
|
343
|
+
.suggestion, .chip { flex: none; max-width: 100%; display: inline-flex; align-items: center; gap: 8px;
|
|
344
|
+
border: 1px solid transparent; border-radius: var(--chat-round); background: var(--chat-secondary);
|
|
345
|
+
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; white-space: nowrap; cursor: pointer; }
|
|
347
|
+
.suggestion svg, .chip svg { flex: none; color: var(--chat-accent); }
|
|
348
|
+
/* Suggestions scroll in one row; a quick reply wraps inside the message's width. */
|
|
349
|
+
.chip { white-space: normal; }
|
|
350
|
+
.suggestion:hover { border-color: var(--chat-primary); background: var(--chat-surface); color: var(--chat-text); }
|
|
253
351
|
@media (min-width: 1024px) {
|
|
254
352
|
.suggestions { flex-wrap: wrap; overflow: visible; margin: 0 0 10px; padding: 0; }
|
|
255
353
|
.suggestion { white-space: normal; }
|
|
256
354
|
}
|
|
257
355
|
.composer { display: flex; align-items: flex-end; gap: 8px; padding: 6px 6px 6px 18px;
|
|
258
|
-
border: 1px solid var(--chat-border); border-radius:
|
|
356
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-round); background: var(--chat-surface);
|
|
259
357
|
box-shadow: 0 4px 16px rgb(15 23 42 / 0.08); }
|
|
260
358
|
.composer:focus-within { border-color: var(--chat-text); }
|
|
261
359
|
/* 16px keeps iOS from zooming into the field. */
|
|
@@ -263,14 +361,19 @@ const CHAT_CSS = `
|
|
|
263
361
|
outline: none; background: transparent; padding: 6px 0; font: inherit; font-size: 16px;
|
|
264
362
|
line-height: 1.5; color: inherit; field-sizing: content; }
|
|
265
363
|
.send { flex: none; display: grid; place-items: center; width: 36px; height: 36px; padding: 0; border: 0;
|
|
266
|
-
border-radius:
|
|
364
|
+
border-radius: var(--chat-round); background: var(--chat-primary); color: var(--chat-on-primary); cursor: pointer; }
|
|
267
365
|
.send:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
268
|
-
.consent { flex: 1; overflow-y: auto; padding: 24px
|
|
366
|
+
.consent { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 14px;
|
|
269
367
|
background: var(--chat-surface-soft); }
|
|
270
368
|
.consent p { margin: 0; }
|
|
369
|
+
.consent-card { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; padding: 18px 20px;
|
|
370
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-radius); background: var(--chat-surface);
|
|
371
|
+
font-size: 14px; color: var(--chat-muted); }
|
|
271
372
|
.pending { margin: 0; align-self: flex-end; max-width: 88%; padding: 10px 14px;
|
|
272
|
-
border-radius: var(--chat-radius); background: var(--chat-secondary);
|
|
273
|
-
|
|
373
|
+
border-radius: var(--chat-radius); background: var(--chat-secondary); color: var(--chat-on-secondary);
|
|
374
|
+
white-space: pre-wrap; }
|
|
375
|
+
/* The bubble's own text colour, softened: the muted grey suits the surface, not a set secondary. */
|
|
376
|
+
.pending figcaption { margin-bottom: 2px; color: inherit; opacity: 0.75; }
|
|
274
377
|
.fallback { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-bottom: 10px; }
|
|
275
378
|
.fallback p { margin: 0; }
|
|
276
379
|
|
|
@@ -282,19 +385,35 @@ const CHAT_CSS = `
|
|
|
282
385
|
.gallery::-webkit-scrollbar, .carousel::-webkit-scrollbar { display: none; }
|
|
283
386
|
.gallery img { flex: 0 0 100%; scroll-snap-align: start; display: block; width: 100%; aspect-ratio: 5 / 3;
|
|
284
387
|
height: auto; object-fit: cover; }
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
.
|
|
388
|
+
/* Dots say which photo shows; arrows come up under a pointer and on keyboard focus (a finger swipes:
|
|
389
|
+
on a touch screen they take no tap, so a tap on a photo's edge does not turn it unseen). */
|
|
390
|
+
.dots { position: absolute; left: 50%; bottom: 10px; translate: -50% 0; display: flex; gap: 4px; }
|
|
391
|
+
.dots i { width: 6px; height: 6px; border-radius: 999px; background: rgb(255 255 255 / 0.6);
|
|
392
|
+
box-shadow: 0 0 2px rgb(0 0 0 / 0.3); }
|
|
393
|
+
.dots i[data-current] { width: 14px; background: #fff; }
|
|
394
|
+
.gallery-arrow { position: absolute; top: 50%; translate: 0 -50%; display: grid; place-items: center;
|
|
395
|
+
width: 32px; height: 32px; padding: 0; border: 0; border-radius: var(--chat-round);
|
|
396
|
+
background: color-mix(in srgb, var(--chat-surface) 92%, transparent); color: var(--chat-text);
|
|
397
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / 0.18); cursor: pointer; opacity: 0; }
|
|
398
|
+
.gallery-arrow[data-side="prev"] { left: 10px; }
|
|
399
|
+
.gallery-arrow[data-side="next"] { right: 10px; }
|
|
400
|
+
.gallery-wrap:hover .gallery-arrow:not(:disabled), .gallery-arrow:focus-visible { opacity: 1; }
|
|
401
|
+
@media (hover: none) { .gallery-arrow:not(:focus-visible) { pointer-events: none; } }
|
|
402
|
+
/* The row scrolls, so it clips: room below for the cards' shadow, taken back from the gap under it. */
|
|
403
|
+
.carousel { gap: 12px; padding: 2px 2px 16px; margin-bottom: -10px; }
|
|
289
404
|
.carousel > .card { flex: 0 0 86%; scroll-snap-align: start; }
|
|
290
|
-
/* Room for two:
|
|
405
|
+
/* Room for two: two cards side by side and the next one peeking in, still one row to swipe; two
|
|
406
|
+
results fill the row exactly, with nothing to swipe to. */
|
|
291
407
|
@container (min-width: 520px) {
|
|
292
|
-
.carousel
|
|
293
|
-
.carousel-
|
|
408
|
+
.carousel > .card { flex-basis: calc((100% - 24px) / 2.15); }
|
|
409
|
+
.carousel:has(> .card:nth-child(2):last-child) > .card { flex-basis: calc((100% - 12px) / 2); }
|
|
410
|
+
.carousel-wrap:has(> .carousel > .card:nth-child(2):last-child) > .snap-nav { display: none; }
|
|
411
|
+
/* "n / total" counts one card per step, which a row of two no longer takes: the arrows stay. */
|
|
412
|
+
.carousel-wrap > .snap-nav > span { visibility: hidden; }
|
|
294
413
|
}
|
|
295
414
|
.snap-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; }
|
|
296
415
|
.snap-nav button, .calendar-head button { border: 1px solid var(--chat-border); background: var(--chat-surface);
|
|
297
|
-
color: var(--chat-text); border-radius:
|
|
416
|
+
color: var(--chat-text); border-radius: var(--chat-round); width: 28px; height: 28px; cursor: pointer; font: inherit;
|
|
298
417
|
line-height: 1; }
|
|
299
418
|
.snap-nav button:disabled, .calendar-head button:disabled { opacity: 0.35; cursor: default; }
|
|
300
419
|
.rating { color: #b7791f; font-weight: 700; }
|
|
@@ -302,11 +421,9 @@ const CHAT_CSS = `
|
|
|
302
421
|
.breakdown .total { border-top: 1px solid var(--chat-border); padding-top: 6px; margin-top: 2px;
|
|
303
422
|
font-weight: 700; }
|
|
304
423
|
.alternatives, .quick-replies { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
305
|
-
.chip-link { border: 1px solid var(--chat-primary); color: var(--chat-primary); border-radius:
|
|
424
|
+
.chip-link { border: 1px solid var(--chat-primary); color: var(--chat-primary); border-radius: var(--chat-round);
|
|
306
425
|
padding: 5px 10px; text-decoration: none; font-size: 13px; }
|
|
307
|
-
.chip { border:
|
|
308
|
-
border-radius: 999px; padding: 7px 13px; font: inherit; font-size: 13px; cursor: pointer; }
|
|
309
|
-
.chip:hover { border-color: var(--chat-primary); background: var(--chat-surface); }
|
|
426
|
+
.chip:hover { border-color: var(--chat-primary); background: var(--chat-surface); color: var(--chat-text); }
|
|
310
427
|
.calendar { border: 1px solid var(--chat-border); border-radius: var(--chat-radius); padding: 12px;
|
|
311
428
|
background: var(--chat-surface); }
|
|
312
429
|
.calendar-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
@@ -314,13 +431,15 @@ const CHAT_CSS = `
|
|
|
314
431
|
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; text-align: center;
|
|
315
432
|
font-size: 12px; }
|
|
316
433
|
.weekday { color: var(--chat-muted); font-weight: 600; }
|
|
317
|
-
.day { padding: 4px 0; border-radius: 6px; }
|
|
434
|
+
.day { padding: 4px 0; border-radius: min(6px, var(--chat-radius)); }
|
|
318
435
|
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
|
|
319
436
|
white-space: nowrap; }
|
|
320
|
-
.day[data-status="free"], .legend i[data-status="free"] {
|
|
321
|
-
|
|
437
|
+
.day[data-status="free"], .legend i[data-status="free"] {
|
|
438
|
+
background: color-mix(in srgb, var(--chat-primary) 14%, var(--chat-surface)); color: var(--chat-text); }
|
|
439
|
+
.day[data-status="booked"], .legend i[data-status="booked"] { background: var(--chat-surface-soft);
|
|
440
|
+
color: var(--chat-muted);
|
|
322
441
|
text-decoration: line-through; }
|
|
323
|
-
.day[data-status="closed"] { color:
|
|
442
|
+
.day[data-status="closed"] { color: var(--chat-muted); opacity: 0.6; }
|
|
324
443
|
.day[data-stay], .legend i[data-stay] { background: var(--chat-primary); color: var(--chat-on-primary);
|
|
325
444
|
text-decoration: none; }
|
|
326
445
|
.legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 12px; }
|
|
@@ -335,25 +454,41 @@ const CHAT_CSS = `
|
|
|
335
454
|
.compare-col dl { margin: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
|
336
455
|
.compare-col dt { color: var(--chat-muted); font-size: 11px; margin-top: 4px; }
|
|
337
456
|
.compare-col dd { margin: 0; overflow-wrap: anywhere; }
|
|
338
|
-
.card-body { padding:
|
|
339
|
-
.card-title { margin: 0; font-size:
|
|
457
|
+
.card-body { padding: 16px 18px 12px; display: flex; flex-direction: column; gap: 6px; }
|
|
458
|
+
.card-title { margin: 0; font-size: 18px; font-weight: 500; line-height: 1.3; }
|
|
459
|
+
/* A rental card: place and score, name, facts and wishes; the stay's price and button below a line. */
|
|
460
|
+
.rental-card, .stay-card { display: flex; flex-direction: column; }
|
|
461
|
+
.rental-card .card-body { flex: 1; }
|
|
462
|
+
.card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
|
|
463
|
+
.place { color: var(--chat-accent); font-size: 14px; }
|
|
464
|
+
.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: 12px; font-weight: 700; }
|
|
466
|
+
.specs { margin: 0; color: var(--chat-muted); font-size: 14px; }
|
|
467
|
+
.card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
468
|
+
margin: 0 18px; padding: 12px 0 16px; border-top: 1px solid var(--chat-border); }
|
|
469
|
+
.foot-price { display: flex; flex-direction: column; min-width: 0; }
|
|
470
|
+
.foot-price .price { font-size: 18px; line-height: 1.3; }
|
|
471
|
+
.foot-meta { color: var(--chat-muted); font-size: 12px; }
|
|
472
|
+
.button.compact { margin: 0 0 0 auto; padding: 9px 20px; font-size: 14px; white-space: nowrap; }
|
|
340
473
|
.muted { color: var(--chat-muted); font-size: 13px; }
|
|
341
474
|
.price { font-weight: 700; }
|
|
342
475
|
.button { display: inline-block; margin-top: 6px; text-align: center; text-decoration: none;
|
|
343
|
-
padding: 10px 16px; border-radius:
|
|
476
|
+
padding: 10px 16px; border-radius: var(--chat-round); background: var(--chat-primary); color: var(--chat-on-primary);
|
|
344
477
|
border: 0; cursor: pointer; font: inherit; font-weight: 600; }
|
|
345
478
|
.link { color: var(--chat-primary); }
|
|
346
479
|
/* A link out of the chat while a reply still streams (ReplyLink). */
|
|
347
480
|
[aria-disabled="true"] { opacity: 0.5; cursor: progress; }
|
|
348
481
|
|
|
349
|
-
/*
|
|
350
|
-
|
|
351
|
-
.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.wishes li
|
|
482
|
+
/* A card without photos: in a carousel a quiet block holds the photo's place, so the row lines up;
|
|
483
|
+
on its own the card simply starts with its text. */
|
|
484
|
+
.no-photo { display: none; aspect-ratio: 5 / 3; place-items: center; background: var(--chat-surface-soft);
|
|
485
|
+
color: var(--chat-muted); }
|
|
486
|
+
.carousel .no-photo { display: grid; }
|
|
487
|
+
/* Wishes a found stay matches, ticked under the name. */
|
|
488
|
+
.wishes { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
489
|
+
.wishes li { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px 4px 10px;
|
|
490
|
+
border: 1px solid var(--chat-border); border-radius: var(--chat-round); font-size: 13px; font-weight: 600; }
|
|
491
|
+
.wishes svg { color: var(--chat-accent); }
|
|
357
492
|
|
|
358
493
|
/* The price calendar: a column per arrival, the bar's height its total. */
|
|
359
494
|
.price-columns { list-style: none; margin: 10px 0 0; padding: 0; display: grid; grid-auto-flow: column;
|
|
@@ -365,7 +500,8 @@ const CHAT_CSS = `
|
|
|
365
500
|
.price-column[data-free]:not(:disabled):hover { border-color: var(--chat-primary); }
|
|
366
501
|
.price-column[data-asked] { border-color: var(--chat-border); background: var(--chat-surface-soft); }
|
|
367
502
|
.bar-track { display: flex; align-items: flex-end; justify-content: center; width: 100%; height: 72px; }
|
|
368
|
-
.bar { width: 60%; border-radius: 6px 6px
|
|
503
|
+
.bar { width: 60%; border-radius: min(6px, var(--chat-radius)) min(6px, var(--chat-radius)) 0 0;
|
|
504
|
+
background: color-mix(in srgb, var(--chat-primary) 30%, var(--chat-surface)); }
|
|
369
505
|
.price-column[data-cheapest] .bar, .legend i[data-cheapest] { background: var(--chat-primary); }
|
|
370
506
|
.legend i[data-asked] { background: var(--chat-surface-soft); border: 1px solid var(--chat-border); }
|
|
371
507
|
.price-text { font-size: 12px; font-weight: 700; white-space: nowrap; }
|
|
@@ -398,7 +534,7 @@ const CHAT_CSS = `
|
|
|
398
534
|
.map-attribution { margin: 0; padding: 2px 8px; font-size: 10px; color: var(--chat-muted); text-align: right; }
|
|
399
535
|
.map-attribution a { color: inherit; }
|
|
400
536
|
.map-pin { display: block; width: 18px; height: 18px; border-radius: 999px; background: var(--chat-primary);
|
|
401
|
-
border: 3px solid
|
|
537
|
+
border: 3px solid var(--chat-surface); box-shadow: 0 2px 6px rgb(0 0 0 / 0.35); }
|
|
402
538
|
.maplibregl-map { position: relative; overflow: hidden; }
|
|
403
539
|
.maplibregl-canvas-container, .maplibregl-canvas { position: absolute; left: 0; top: 0; }
|
|
404
540
|
/* The canvas takes keyboard focus: a ring for the keyboard, none for a click or a tap. */
|
|
@@ -406,13 +542,13 @@ const CHAT_CSS = `
|
|
|
406
542
|
.maplibregl-canvas:focus-visible { outline: 2px solid var(--chat-primary); outline-offset: -2px; }
|
|
407
543
|
.maplibregl-marker { position: absolute; top: 0; left: 0; will-change: transform; }
|
|
408
544
|
|
|
409
|
-
[data-theme="soft"] { --chat-radius:
|
|
545
|
+
[data-theme="soft"] { --chat-radius: calc(var(--chat-base-radius) * 2.4); --chat-shadow: 0 12px 32px rgb(15 23 42 / 0.12);
|
|
410
546
|
--chat-card-shadow: 0 4px 18px rgb(15 23 42 / 0.07); }
|
|
411
|
-
[data-theme="minimal"] { --chat-radius:
|
|
547
|
+
[data-theme="minimal"] { --chat-radius: calc(var(--chat-base-radius) * 0.6); --chat-shadow: none; --chat-card-shadow: none; }
|
|
412
548
|
[data-theme="minimal"] .panel, [data-theme="minimal"] .pill, [data-theme="minimal"] .card,
|
|
413
549
|
[data-theme="minimal"] .compare-col { border: 1px solid var(--chat-border); }
|
|
414
|
-
|
|
415
|
-
[data-theme="minimal"]
|
|
550
|
+
/* No pills in minimal: every control has its tight corners. */
|
|
551
|
+
[data-theme="minimal"] { --chat-round: var(--chat-radius); }
|
|
416
552
|
|
|
417
553
|
@keyframes chat-spin { to { transform: rotate(1turn); } }
|
|
418
554
|
@keyframes chat-in { from { opacity: 0; transform: translateY(16px); } }
|
|
@@ -436,7 +572,7 @@ const CHAT_CSS = `
|
|
|
436
572
|
`;
|
|
437
573
|
//#endregion
|
|
438
574
|
//#region src/WebsiteChat.tsx
|
|
439
|
-
const Panel = lazy(() => import("./panel-
|
|
575
|
+
const Panel = lazy(() => import("./panel-BsocDe9I.js"));
|
|
440
576
|
/** See {@link WebsiteChatComponent}. */
|
|
441
577
|
const WebsiteChat = function WebsiteChat(props) {
|
|
442
578
|
const { endpoint, locale, theme = "classic", position = "right" } = props;
|
|
@@ -445,6 +581,7 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
445
581
|
const field = useRef(null);
|
|
446
582
|
const wasOpen = useRef(false);
|
|
447
583
|
const [root, setRoot] = useState(null);
|
|
584
|
+
const [replaced, setReplaced] = useState({});
|
|
448
585
|
const [available, setAvailable] = useState(false);
|
|
449
586
|
const [mounted, setMounted] = useState(false);
|
|
450
587
|
const [open, setOpen] = useState(false);
|
|
@@ -483,6 +620,7 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
483
620
|
useEffect(() => {
|
|
484
621
|
const element = host.current;
|
|
485
622
|
if (!available || !element) return;
|
|
623
|
+
setReplaced(unusableSiteTokens(element));
|
|
486
624
|
setRoot(element.shadowRoot ?? element.attachShadow({ mode: "open" }));
|
|
487
625
|
}, [available]);
|
|
488
626
|
const submitEntry = (event) => {
|
|
@@ -494,7 +632,10 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
494
632
|
};
|
|
495
633
|
return /* @__PURE__ */ jsx("div", {
|
|
496
634
|
ref: host,
|
|
497
|
-
style:
|
|
635
|
+
style: {
|
|
636
|
+
...replaced,
|
|
637
|
+
...colorVariables(props.colors)
|
|
638
|
+
},
|
|
498
639
|
children: root && available && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: CHAT_CSS }), /* @__PURE__ */ jsxs("div", {
|
|
499
640
|
className: "chat",
|
|
500
641
|
"data-theme": theme,
|
|
@@ -503,7 +644,7 @@ const WebsiteChat = function WebsiteChat(props) {
|
|
|
503
644
|
fallback: null,
|
|
504
645
|
children: /* @__PURE__ */ jsx(Panel, {
|
|
505
646
|
endpoint,
|
|
506
|
-
clientVersion: "1.
|
|
647
|
+
clientVersion: "1.1.1",
|
|
507
648
|
locale,
|
|
508
649
|
copy,
|
|
509
650
|
searchPath: props.searchPath,
|