@vanillaskyai/video 0.10.21 → 0.10.22

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/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.10.22
8
+
9
+ - Offer eight diverse homepage prompts with curated footage, a balanced fresh-page shuffle, and stable ordering when returning Home.
10
+ - Browse four cards on desktop and two on mobile, with only the active card loading video.
11
+
7
12
  ## 0.10.21
8
13
 
9
14
  - Choose illustrated, realistic, or cinematic generated-video direction within the existing chat brief, with consistent treatment across shots and caller style overrides.
@@ -8,6 +8,47 @@ import {
8
8
  VIDEO_PROTOCOL_VERSION
9
9
  } from "./chunk-7NZOMTAL.js";
10
10
 
11
+ // src/video-chat/welcome-cards.ts
12
+ var WELCOME_CARDS = [
13
+ { prompt: "Why do cats stare at us?", category: "curiosity", media: { "url": "https://videos.pexels.com/video-files/6131490/6131490-sd_640_360_25fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/6131490/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
14
+ { prompt: "Tell me a joke about office life", category: "entertainment", media: { "url": "https://videos.pexels.com/video-files/7438239/7438239-sd_640_338_25fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/7438239/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
15
+ { prompt: "Where would you take me in Japan?", category: "explore", media: { "url": "https://videos.pexels.com/video-files/35972506/15252635_360_640_30fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/35972506/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
16
+ { prompt: "How do I make better coffee?", category: "practical", media: { "url": "https://videos.pexels.com/video-files/5564283/5564283-sd_640_360_24fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/5564283/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
17
+ { prompt: "What would Earth look like without humans?", category: "explore", media: { "url": "https://videos.pexels.com/video-files/28732001/12464538_640_360_30fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/28732001/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
18
+ { prompt: "Tell me a short story with a twist", category: "entertainment", media: { "url": "https://videos.pexels.com/video-files/5683159/5683159-sd_640_360_30fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/5683159/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
19
+ { prompt: "Why does music give us goosebumps?", category: "curiosity", media: { "url": "https://videos.pexels.com/video-files/6870450/6870450-sd_640_360_30fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/6870450/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } },
20
+ { prompt: "How can I make my room feel bigger?", category: "practical", media: { "url": "https://videos.pexels.com/video-files/37479012/15875787_360_640_30fps.mp4", "type": "video", "posterUrl": "https://images.pexels.com/videos/37479012/pictures/preview-0.jpg?auto=compress&fit=crop&w=640" } }
21
+ ];
22
+ function orderWelcomeCards(cards, seed) {
23
+ if (cards.length !== WELCOME_CARDS.length || new Set(cards.map((card) => card.prompt)).size !== cards.length || cards.some((card) => !WELCOME_CARDS.some((entry) => entry.prompt === card.prompt))) return [...cards];
24
+ let state = seed >>> 0;
25
+ const random = () => {
26
+ state = Math.imul(state, 1664525) + 1013904223 >>> 0;
27
+ return state / 4294967296;
28
+ };
29
+ const shuffle = (values) => {
30
+ const copy = [...values];
31
+ for (let i = copy.length - 1; i > 0; i--) {
32
+ const j = Math.floor(random() * (i + 1));
33
+ [copy[i], copy[j]] = [copy[j], copy[i]];
34
+ }
35
+ return copy;
36
+ };
37
+ const groups = /* @__PURE__ */ new Map();
38
+ for (const card of shuffle(cards)) {
39
+ const category = WELCOME_CARDS.find((entry) => entry.prompt === card.prompt).category;
40
+ const group = groups.get(category) ?? [];
41
+ group.push(card);
42
+ groups.set(category, group);
43
+ }
44
+ const categories = shuffle([...groups.values()]);
45
+ return [...categories.map((group) => group[0]), ...shuffle(categories.flatMap((group) => group.slice(1)))];
46
+ }
47
+ var pageSeed;
48
+ function welcomeVisitSeed() {
49
+ return pageSeed ??= Math.floor(Math.random() * 4294967296);
50
+ }
51
+
11
52
  // src/video-chat/recovery.ts
12
53
  var MEDIA_RECOVERY_NOTICE = "Some visuals were replaced so your response can continue.";
13
54
 
@@ -160,6 +201,9 @@ function safeMediaUrl(value) {
160
201
  }
161
202
 
162
203
  export {
204
+ WELCOME_CARDS,
205
+ orderWelcomeCards,
206
+ welcomeVisitSeed,
163
207
  MEDIA_RECOVERY_NOTICE,
164
208
  encodeVideoSseEvent,
165
209
  decodeVideoSse,
package/dist/react.js CHANGED
@@ -9,9 +9,11 @@ import {
9
9
  import {
10
10
  MEDIA_RECOVERY_NOTICE,
11
11
  decodeVideoSse,
12
+ orderWelcomeCards,
12
13
  sanitizeVideoChatMedia,
14
+ welcomeVisitSeed,
13
15
  withDeadline
14
- } from "./chunk-HKUI5BTB.js";
16
+ } from "./chunk-44WND2VP.js";
15
17
  import {
16
18
  applyVideoEvent,
17
19
  checksumVideo,
@@ -2311,7 +2313,7 @@ function useVideoChatSession(options = {}) {
2311
2313
  if (!response.ok) throw await responseError(response);
2312
2314
  return response.json();
2313
2315
  }).then((value) => {
2314
- if (mountedRef.current) dispatch({ type: "welcome", value });
2316
+ if (mountedRef.current) dispatch({ type: "welcome", value: { ...value, cards: orderWelcomeCards(value.cards, welcomeVisitSeed()) } });
2315
2317
  }).catch(() => void 0);
2316
2318
  return () => {
2317
2319
  endTiming();
@@ -3008,7 +3010,7 @@ function CaptionPages({ text, getProgress }) {
3008
3010
  }
3009
3011
 
3010
3012
  // src/video-chat/video-chat.tsx
3011
- import { useCallback as useCallback6, useEffect as useEffect10, useId, useLayoutEffect as useLayoutEffect3, useMemo as useMemo3, useRef as useRef9, useState as useState8 } from "react";
3013
+ import { useCallback as useCallback5, useEffect as useEffect10, useId as useId2, useLayoutEffect as useLayoutEffect3, useMemo as useMemo3, useRef as useRef9, useState as useState8 } from "react";
3012
3014
 
3013
3015
  // src/video-chat/icons.tsx
3014
3016
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -3118,7 +3120,7 @@ function useFocusTrap(active, surface) {
3118
3120
  }
3119
3121
 
3120
3122
  // src/video-chat/suggestion-cards.tsx
3121
- import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef5, useState as useState4 } from "react";
3123
+ import { useEffect as useEffect7, useId, useRef as useRef5, useState as useState4 } from "react";
3122
3124
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3123
3125
  function Frame({ media, poster, playing, onReady, onError, revealWhenReady = false }) {
3124
3126
  const video = useRef5(null);
@@ -3130,6 +3132,9 @@ function Frame({ media, poster, playing, onReady, onError, revealWhenReady = fal
3130
3132
  else element.pause();
3131
3133
  }, [playing]);
3132
3134
  if (!media) return null;
3135
+ if (playing === false && media.type === "video" && media.posterUrl) {
3136
+ return /* @__PURE__ */ jsx5("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "", onLoad: onReady, onError });
3137
+ }
3133
3138
  const ready = () => {
3134
3139
  setPlayingUrl(media.url);
3135
3140
  onReady?.();
@@ -3161,37 +3166,38 @@ function Frame({ media, poster, playing, onReady, onError, revealWhenReady = fal
3161
3166
  poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx5("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "", onLoad: onReady })
3162
3167
  ] });
3163
3168
  }
3164
- function SuggestionCards({ suggestions, label, onAsk }) {
3169
+ function SuggestionCards({ suggestions, label, onAsk, browse = false }) {
3165
3170
  const railRef = useRef5(null);
3171
+ const railId = useId();
3166
3172
  const [at, setAt] = useState4(0);
3167
- const [taken, setTaken] = useState4(false);
3168
- const take = useCallback3((index) => {
3169
- setTaken(true);
3170
- setAt(index);
3171
- }, []);
3172
- useEffect7(() => {
3173
- if (taken || suggestions.length < 2) return;
3174
- if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
3175
- const tour = window.setInterval(() => setAt((index) => (index + 1) % suggestions.length), 5e3);
3176
- return () => window.clearInterval(tour);
3177
- }, [taken, suggestions.length]);
3173
+ const [overflow, setOverflow] = useState4({ before: false, after: false });
3178
3174
  useEffect7(() => {
3179
3175
  const rail = railRef.current;
3180
- const card = rail?.children[at];
3181
- if (!rail || !card) return;
3182
- if (rail.scrollWidth <= rail.clientWidth + 4) return;
3183
- const centred = card.offsetLeft - (rail.clientWidth - card.offsetWidth) / 2;
3184
- rail.scrollTo({ left: Math.max(0, centred), behavior: "smooth" });
3185
- }, [at]);
3176
+ if (!browse || !rail) return;
3177
+ const update = () => setOverflow({ before: rail.scrollLeft > 2, after: rail.scrollLeft + rail.clientWidth < rail.scrollWidth - 2 });
3178
+ update();
3179
+ rail.addEventListener("scroll", update, { passive: true });
3180
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(update);
3181
+ observer?.observe(rail);
3182
+ return () => {
3183
+ rail.removeEventListener("scroll", update);
3184
+ observer?.disconnect();
3185
+ };
3186
+ }, [browse, suggestions.length]);
3187
+ const move = (direction) => {
3188
+ const rail = railRef.current;
3189
+ if (!rail) return;
3190
+ rail.scrollBy({ left: direction * rail.clientWidth, behavior: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
3191
+ };
3186
3192
  if (suggestions.length === 0) return null;
3187
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
3188
- /* @__PURE__ */ jsx5("ul", { className: "cards", ref: railRef, "aria-label": label, children: suggestions.map((card, index) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs4(
3193
+ const cards = /* @__PURE__ */ jsxs4(Fragment2, { children: [
3194
+ /* @__PURE__ */ jsx5("ul", { className: "cards", id: railId, ref: railRef, "aria-label": label, children: suggestions.map((card, index) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs4(
3189
3195
  "button",
3190
3196
  {
3191
3197
  type: "button",
3192
3198
  "data-active": index === at ? "" : void 0,
3193
- onFocus: () => take(index),
3194
- onPointerEnter: () => take(index),
3199
+ onFocus: () => setAt(index),
3200
+ onPointerEnter: () => setAt(index),
3195
3201
  onClick: () => onAsk(card),
3196
3202
  children: [
3197
3203
  /* @__PURE__ */ jsx5(Frame, { media: card.media, poster: true, playing: index === at }),
@@ -3207,11 +3213,23 @@ function SuggestionCards({ suggestions, label, onAsk }) {
3207
3213
  "aria-pressed": index === at,
3208
3214
  "aria-label": `Show suggestion ${index + 1}: ${card.prompt}`,
3209
3215
  className: index === at ? "on" : void 0,
3210
- onClick: () => take(index)
3216
+ onClick: () => {
3217
+ setAt(index);
3218
+ const rail = railRef.current;
3219
+ const card2 = rail?.children[index];
3220
+ if (rail && card2) rail.scrollTo({ left: card2.offsetLeft, behavior: "smooth" });
3221
+ }
3211
3222
  },
3212
3223
  card.prompt
3213
3224
  )) })
3214
3225
  ] });
3226
+ return browse ? /* @__PURE__ */ jsxs4("div", { className: "suggestion-rail", children: [
3227
+ cards,
3228
+ (overflow.before || overflow.after) && /* @__PURE__ */ jsxs4("div", { className: "rail-arrows", children: [
3229
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Previous suggestions", "aria-controls": railId, disabled: !overflow.before, onClick: () => move(-1), children: "\u2039" }),
3230
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Next suggestions", "aria-controls": railId, disabled: !overflow.after, onClick: () => move(1), children: "\u203A" })
3231
+ ] })
3232
+ ] }) : cards;
3215
3233
  }
3216
3234
 
3217
3235
  // src/video-chat/welcome.tsx
@@ -3226,7 +3244,7 @@ function Welcome({ data, onAsk, title }) {
3226
3244
  /* @__PURE__ */ jsx6("br", {}),
3227
3245
  /* @__PURE__ */ jsx6("em", { children: "in video, not text." })
3228
3246
  ] }) }),
3229
- /* @__PURE__ */ jsx6(SuggestionCards, { suggestions: data?.cards ?? [], label: "Suggested prompts", onAsk })
3247
+ /* @__PURE__ */ jsx6(SuggestionCards, { browse: true, suggestions: data?.cards ?? [], label: "Suggested prompts", onAsk })
3230
3248
  ] })
3231
3249
  ] });
3232
3250
  }
@@ -3276,7 +3294,7 @@ function OpeningChapter({ title, preparing = false }) {
3276
3294
  }
3277
3295
 
3278
3296
  // src/video-chat/use-voice-input.ts
3279
- import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
3297
+ import { useCallback as useCallback3, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
3280
3298
  function recognitionConstructor() {
3281
3299
  if (typeof window === "undefined") return void 0;
3282
3300
  const holder = window;
@@ -3358,7 +3376,7 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3358
3376
  useEffect8(() => {
3359
3377
  setSupported(supportsVoiceInput(transcriptionAvailable));
3360
3378
  }, [transcriptionAvailable]);
3361
- const stop = useCallback4(() => {
3379
+ const stop = useCallback3(() => {
3362
3380
  recognitionRef.current?.abort();
3363
3381
  recognitionRef.current = void 0;
3364
3382
  const operation = operationRef.current;
@@ -3376,7 +3394,7 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3376
3394
  operation?.controller.abort();
3377
3395
  operation?.finish?.();
3378
3396
  }, []);
3379
- const finish = useCallback4(() => {
3397
+ const finish = useCallback3(() => {
3380
3398
  const recognition = recognitionRef.current;
3381
3399
  if (recognition) {
3382
3400
  recognition.stop();
@@ -3390,7 +3408,7 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3390
3408
  }
3391
3409
  stop();
3392
3410
  }, [stop]);
3393
- const record = useCallback4(async () => {
3411
+ const record = useCallback3(async () => {
3394
3412
  if (!transcriptionAvailable || !recorderAvailable()) {
3395
3413
  setError("Server transcription is not configured.");
3396
3414
  return;
@@ -3428,7 +3446,7 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3428
3446
  }
3429
3447
  }
3430
3448
  }, [transcriptionAvailable, options.endpoint, options.headers, options.credentials, options.fetcher]);
3431
- const toggle = useCallback4(() => {
3449
+ const toggle = useCallback3(() => {
3432
3450
  if (listening) {
3433
3451
  finish();
3434
3452
  return;
@@ -3480,18 +3498,18 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
3480
3498
  }
3481
3499
 
3482
3500
  // src/video-chat/use-immersive-controls.ts
3483
- import { useCallback as useCallback5, useEffect as useEffect9, useRef as useRef8, useState as useState7 } from "react";
3501
+ import { useCallback as useCallback4, useEffect as useEffect9, useRef as useRef8, useState as useState7 } from "react";
3484
3502
  function useImmersiveControls(playing, pinned, hasCaptions = false) {
3485
3503
  const [visible, setVisible] = useState7(true);
3486
3504
  const hovered = useRef8(false);
3487
3505
  const focused = useRef8(false);
3488
3506
  const previousCaptions = useRef8(false);
3489
3507
  const timer = useRef8(null);
3490
- const clearTimer = useCallback5(() => {
3508
+ const clearTimer = useCallback4(() => {
3491
3509
  if (timer.current !== null) clearTimeout(timer.current);
3492
3510
  timer.current = null;
3493
3511
  }, []);
3494
- const reveal = useCallback5(() => {
3512
+ const reveal = useCallback4(() => {
3495
3513
  clearTimer();
3496
3514
  setVisible(true);
3497
3515
  if (playing && !pinned && !hovered.current && !focused.current) {
@@ -3513,21 +3531,21 @@ function useImmersiveControls(playing, pinned, hasCaptions = false) {
3513
3531
  }
3514
3532
  return clearTimer;
3515
3533
  }, [hasCaptions, playing, pinned, reveal, clearTimer]);
3516
- const onPointerEnter = useCallback5((event) => {
3534
+ const onPointerEnter = useCallback4((event) => {
3517
3535
  if (event?.pointerType === "touch") return;
3518
3536
  hovered.current = true;
3519
3537
  reveal();
3520
3538
  }, [reveal]);
3521
- const onPointerLeave = useCallback5(() => {
3539
+ const onPointerLeave = useCallback4(() => {
3522
3540
  if (!hovered.current) return;
3523
3541
  hovered.current = false;
3524
3542
  reveal();
3525
3543
  }, [reveal]);
3526
- const onFocusCapture = useCallback5((event) => {
3544
+ const onFocusCapture = useCallback4((event) => {
3527
3545
  focused.current = event.target.matches(":focus-visible");
3528
3546
  reveal();
3529
3547
  }, [reveal]);
3530
- const onBlurCapture = useCallback5((event) => {
3548
+ const onBlurCapture = useCallback4((event) => {
3531
3549
  if (focused.current && !event.currentTarget.contains(event.relatedTarget)) {
3532
3550
  focused.current = false;
3533
3551
  reveal();
@@ -3601,7 +3619,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3601
3619
  if (turn.id !== chat.shownTurn?.id) return;
3602
3620
  if (previous?.id !== turn.id || previous.mode !== turn.mode) setSelectedMode(turn.mode);
3603
3621
  }, [chat.currentTurn, chat.shownTurn?.id]);
3604
- const instanceId = useId();
3622
+ const instanceId = useId2();
3605
3623
  const historyId = `${instanceId}-history`;
3606
3624
  const settingsId = `${instanceId}-settings`;
3607
3625
  const promptId = `${instanceId}-prompt`;
@@ -3618,13 +3636,13 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3618
3636
  });
3619
3637
  const historySurfaces = useMemo3(() => [historyRef, historyButtonRef], []);
3620
3638
  const settingsSurfaces = useMemo3(() => [settingsRef, settingsButtonRef], []);
3621
- const closeHistory = useCallback6(() => setHistoryOpen(false), []);
3622
- const closeSettings = useCallback6(() => setSettingsOpen(false), []);
3639
+ const closeHistory = useCallback5(() => setHistoryOpen(false), []);
3640
+ const closeSettings = useCallback5(() => setSettingsOpen(false), []);
3623
3641
  useDismiss(historyOpen, closeHistory, historySurfaces);
3624
3642
  useDismiss(settingsOpen, closeSettings, settingsSurfaces);
3625
3643
  useFocusTrap(settingsOpen, settingsRef);
3626
3644
  useFocusTrap(historyOpen, historyRef);
3627
- const ask = useCallback6((value) => {
3645
+ const ask = useCallback5((value) => {
3628
3646
  const prompt = (typeof value === "string" ? value : value.prompt).trim();
3629
3647
  if (!prompt) return;
3630
3648
  setDraft("");
@@ -3639,7 +3657,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3639
3657
  opening: value.opening
3640
3658
  });
3641
3659
  }, [chat, listen]);
3642
- const newSession = useCallback6(() => {
3660
+ const newSession = useCallback5(() => {
3643
3661
  if (chat.turns.length === 0) {
3644
3662
  setHistoryOpen(false);
3645
3663
  setSettingsOpen(false);
@@ -3712,7 +3730,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3712
3730
  const line = chat.caption ?? "";
3713
3731
  const fullTranscript = shown?.video ? [shown.opening, ...shown.video.scenes.map((scene) => scene.narration)].filter((entry) => Boolean(entry)) : chat.transcript;
3714
3732
  const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */ jsx9(Stop, {}) } : status === "paused" ? { label: "Continue", action: chat.resume, icon: /* @__PURE__ */ jsx9(Play, {}) } : status === "ended" && shown?.completed && shown.video ? { label: "Play again", action: chat.replay, icon: /* @__PURE__ */ jsx9(Replay, {}) } : void 0;
3715
- const cancelInput = useCallback6(() => {
3733
+ const cancelInput = useCallback5(() => {
3716
3734
  listen.stop();
3717
3735
  setDraft("");
3718
3736
  setEditing(false);
@@ -3765,7 +3783,11 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3765
3783
  onKeyDownCapture: controls.reveal,
3766
3784
  children: [
3767
3785
  /* @__PURE__ */ jsxs7("header", { className: "chrome", ...controlEvents, children: [
3768
- /* @__PURE__ */ jsx9("div", { className: "session-brand", children: /* @__PURE__ */ jsx9("a", { className: "home-link", href: "/", "aria-label": "Home", children: /* @__PURE__ */ jsx9(Logo, {}) }) }),
3786
+ /* @__PURE__ */ jsx9("div", { className: "session-brand", children: /* @__PURE__ */ jsx9("a", { className: "home-link", href: "/", "aria-label": "Home", onClick: (event) => {
3787
+ if (window.location.pathname !== "/" || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
3788
+ event.preventDefault();
3789
+ newSession();
3790
+ }, children: /* @__PURE__ */ jsx9(Logo, {}) }) }),
3769
3791
  /* @__PURE__ */ jsxs7("div", { className: "group", children: [
3770
3792
  /* @__PURE__ */ jsxs7(
3771
3793
  "button",
package/dist/server.js CHANGED
@@ -20,12 +20,13 @@ import {
20
20
  } from "./chunk-IQMYK5DX.js";
21
21
  import {
22
22
  MEDIA_RECOVERY_NOTICE,
23
+ WELCOME_CARDS,
23
24
  decodeVideoSse,
24
25
  encodeVideoSseEvent,
25
26
  sanitizeVideoChatMedia,
26
27
  videoSseHeaders,
27
28
  withDeadline
28
- } from "./chunk-HKUI5BTB.js";
29
+ } from "./chunk-44WND2VP.js";
29
30
  import "./chunk-RXTN2CW6.js";
30
31
  import {
31
32
  allowedKeys,
@@ -1328,28 +1329,6 @@ var MAX_PROMPT_CHARACTERS = 8e3;
1328
1329
  var MAX_CONVERSATION_TURNS = 12;
1329
1330
  var MAX_CONVERSATION_RESPONSE_CHARACTERS = 8e3;
1330
1331
  var VIDEO_CHAT_OPENING_EVENT_TYPE = "data.video-chat-opening";
1331
- var DEFAULT_WELCOME_PROMPTS = [
1332
- {
1333
- prompt: "Why does the Moon always show one face?",
1334
- opening: "The Moon turns, perfectly matching its orbit.",
1335
- mediaQuery: "full moon night sky"
1336
- },
1337
- {
1338
- prompt: "Tell me a tiny story about a robot growing a garden on Mars",
1339
- opening: "One patient robot is about to make Mars bloom.",
1340
- mediaQuery: "robot garden mars"
1341
- },
1342
- {
1343
- prompt: "Recommend a perfect rainy afternoon in Amsterdam",
1344
- opening: "Rain makes Amsterdam's best afternoons feel even warmer.",
1345
- mediaQuery: "Amsterdam rain cafe"
1346
- },
1347
- {
1348
- prompt: "Pitch a playful ad for a coffee mug that never spills",
1349
- opening: "This mug makes gravity look completely optional.",
1350
- mediaQuery: "coffee mug desk"
1351
- }
1352
- ];
1353
1332
  function jsonError2(status, code, message, headers) {
1354
1333
  return Response.json({ error: { code, message } }, { status, headers });
1355
1334
  }
@@ -1658,7 +1637,7 @@ function createVideoChatHandler(options) {
1658
1637
  transcription: transcribe != null,
1659
1638
  modes: searchMedia ? ["cinematic", "pexels"] : ["cinematic"]
1660
1639
  };
1661
- const welcomePrompts = (welcomeOptions?.prompts ?? DEFAULT_WELCOME_PROMPTS).slice(0, 4);
1640
+ const welcomePrompts = (welcomeOptions?.prompts ?? WELCOME_CARDS).slice(0, 8);
1662
1641
  const heroQuery = welcomeOptions?.heroQuery;
1663
1642
  let welcomeResponse;
1664
1643
  let requestSequence = 0;
@@ -1860,7 +1839,7 @@ function createVideoChatHandler(options) {
1860
1839
  };
1861
1840
  const [hero, ...cards] = await Promise.all([
1862
1841
  heroQuery === void 0 ? DEFAULT_WELCOME_HERO : resolve(heroQuery),
1863
- ...welcomePrompts.map((entry) => resolve(entry.mediaQuery))
1842
+ ...welcomePrompts.map((entry, index) => welcomeOptions?.prompts === void 0 ? WELCOME_CARDS[index]?.media ?? null : resolve("mediaQuery" in entry ? entry.mediaQuery : void 0))
1864
1843
  ]);
1865
1844
  return {
1866
1845
  cacheable: !failed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.10.21",
3
+ "version": "0.10.22",
4
4
  "description": "Open-source voice-and-video chat SDK for AI applications.",
5
5
  "keywords": [
6
6
  "video-chat",
@@ -9,7 +9,7 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@vanillaskyai/video": "0.10.21",
12
+ "@vanillaskyai/video": "0.10.22",
13
13
  "react": "^19.2.8",
14
14
  "react-dom": "^19.2.8",
15
15
  "@ai-sdk/anthropic": "^3.0.0",
@@ -1127,3 +1127,17 @@
1127
1127
  .vanillasky-video-chat .transcript-toggle { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 8px 12px; border: 0; border-radius: 10px; background: var(--vs-media-glass); color: var(--vs-media-muted); font: inherit; font-size: 13px; cursor: pointer; pointer-events: auto; }
1128
1128
  .vanillasky-video-chat .transcript-toggle svg { width: 16px; height: 16px; }
1129
1129
  .vanillasky-video-chat .transcript-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
1130
+
1131
+ /* Welcome shows a deliberate four-card window; the rest stays in the rail. */
1132
+ .vanillasky-video-chat .suggestion-rail { position: relative; flex-shrink: 0; width: calc(4 * clamp(160px, 18vw, 260px) + 66px); max-width: calc(100% + 24px); margin: auto -12px 0; }
1133
+ .vanillasky-video-chat .suggestion-rail .cards { width: 100%; max-width: 100%; margin: 0; }
1134
+ .vanillasky-video-chat .rail-arrows { position: absolute; inset: 50% -6px auto; display: flex; justify-content: space-between; transform: translateY(-50%); pointer-events: none; }
1135
+ .vanillasky-video-chat .rail-arrows button { width: 36px; height: 36px; border: 1px solid rgb(255 255 255 / 30%); border-radius: 50%; background: #202438; color: white; font: inherit; font-size: 25px; line-height: 1; cursor: pointer; pointer-events: auto; }
1136
+ .vanillasky-video-chat .rail-arrows button:disabled { visibility: hidden; pointer-events: none; }
1137
+ .vanillasky-video-chat .rail-arrows button:focus-visible { outline: 2px solid white; outline-offset: 3px; }
1138
+ @media (max-width: 700px) {
1139
+ .vanillasky-video-chat .suggestion-rail { width: calc(100% + 40px); max-width: calc(100% + 40px); margin-inline: -20px; }
1140
+ .vanillasky-video-chat .rail-arrows { display: none; }
1141
+ }
1142
+
1143
+ @media (hover: none) { .vanillasky-video-chat .rail-arrows { display: none; } }