@vanillaskyai/video 0.10.17 → 0.10.19
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 +11 -0
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-MXLVTOGR.js → chunk-M4QTEJTK.js} +5 -1
- package/dist/react.js +79 -61
- package/dist/server.js +23 -10
- package/package.json +1 -1
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/stock.ts +12 -7
- package/styles/video-chat.css +52 -35
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.19
|
|
8
|
+
|
|
9
|
+
- Show a subtle preparation status while the first response scene is loading.
|
|
10
|
+
- Use the same compact suggestion-card layout before and after answers, and link the logo to the homepage.
|
|
11
|
+
- Plan relevant illustrative stock subjects per beat and prefer, rather than require, matching footage orientation in the starter.
|
|
12
|
+
|
|
13
|
+
## 0.10.18
|
|
14
|
+
|
|
15
|
+
- Preserve later valid shots after a malformed body record containing an unescaped newline, while keeping malformed initial containers rejected. Bound unfinished JSON records independently of provider chunk sizes.
|
|
16
|
+
- Keep the outgoing scene visible while a canonical image backdrop prepares or reaches its bounded chapter recovery.
|
|
17
|
+
|
|
7
18
|
## 0.10.17
|
|
8
19
|
|
|
9
20
|
- Let video playback prepare without waiting for an optional poster, while retaining actual video-frame and narration readiness checks.
|
package/dist/check-runtime.js
CHANGED
|
@@ -448,11 +448,15 @@ function VideoFrame({
|
|
|
448
448
|
}
|
|
449
449
|
const previousIndex = timeline.findIndex((range) => sceneReadinessKey(range.scene) === displayedKey.current);
|
|
450
450
|
const previous = timeline[previousIndex];
|
|
451
|
-
const canPrepare = (range) => Boolean(range && mediaAudioMuted
|
|
451
|
+
const canPrepare = (range) => Boolean(range && (mediaAudioMuted || !sceneHasVideoBackdrop(range)) && sceneHasBackdrop(range) && supportsExternalVideoBackdrop(kit.getTemplate(range.scene.templateId)));
|
|
452
452
|
const canRetain = (range) => canPrepare(range) || range?.scene.templateId === "chapterTitle";
|
|
453
453
|
const hasPlayableMedia = (range) => {
|
|
454
454
|
if (!range || !preparedMedia.has(sceneReadinessKey(range.scene))) return false;
|
|
455
455
|
const layer = [...recoveryRoot.current?.querySelectorAll("[data-layer-scene-id]") ?? []].find((node) => node.getAttribute("data-layer-scene-id") === range.scene.id);
|
|
456
|
+
if (!sceneHasVideoBackdrop(range)) {
|
|
457
|
+
const image = [...layer?.querySelectorAll("img") ?? []].find((element) => element.getAttribute("src") === range.scene.variables.mediaUrl);
|
|
458
|
+
return Boolean(image && image.getAttribute("src") === range.scene.variables.mediaUrl && image.complete && image.naturalWidth > 0);
|
|
459
|
+
}
|
|
456
460
|
const video = layer?.querySelector("video");
|
|
457
461
|
return Boolean(video && video.getAttribute("src") === range.scene.variables.mediaUrl && video.currentSrc === video.src && video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA);
|
|
458
462
|
};
|
package/dist/react.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
VideoFrame,
|
|
41
41
|
getDimensions,
|
|
42
42
|
sceneReadinessKey
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-M4QTEJTK.js";
|
|
44
44
|
import "./chunk-5JBMYQP6.js";
|
|
45
45
|
import "./chunk-224QNWRA.js";
|
|
46
46
|
import {
|
|
@@ -3047,35 +3047,46 @@ function Welcome({ data, onAsk, title }) {
|
|
|
3047
3047
|
|
|
3048
3048
|
// src/video-chat/opening-chapter.tsx
|
|
3049
3049
|
import { useLayoutEffect, useRef as useRef5, useState as useState4 } from "react";
|
|
3050
|
-
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
3051
|
-
function OpeningChapter({ title }) {
|
|
3050
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3051
|
+
function OpeningChapter({ title, preparing = false }) {
|
|
3052
3052
|
const root = useRef5(null);
|
|
3053
3053
|
const [size, setSize] = useState4({ width: 1080, height: 1080 });
|
|
3054
|
+
const [titleBottom, setTitleBottom] = useState4(0);
|
|
3054
3055
|
useLayoutEffect(() => {
|
|
3055
3056
|
const element = root.current;
|
|
3056
3057
|
if (!element) return;
|
|
3057
3058
|
const measure = () => {
|
|
3058
3059
|
const { width, height } = element.getBoundingClientRect();
|
|
3059
3060
|
if (width > 0 && height > 0) setSize({ width, height });
|
|
3061
|
+
const title3 = element.querySelector('[data-title-composition="centered"]');
|
|
3062
|
+
if (title3) setTitleBottom(title3.getBoundingClientRect().bottom - element.getBoundingClientRect().top);
|
|
3060
3063
|
};
|
|
3061
3064
|
measure();
|
|
3062
3065
|
if (typeof ResizeObserver === "undefined") return;
|
|
3063
3066
|
const observer = new ResizeObserver(measure);
|
|
3064
3067
|
observer.observe(element);
|
|
3068
|
+
const title2 = element.querySelector('[data-title-composition="centered"]');
|
|
3069
|
+
if (title2) observer.observe(title2);
|
|
3065
3070
|
return () => observer.disconnect();
|
|
3066
|
-
}, []);
|
|
3067
|
-
return /* @__PURE__ */
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3071
|
+
}, [title]);
|
|
3072
|
+
return /* @__PURE__ */ jsxs6("div", { ref: root, "data-opening-chapter": true, className: "opening-chapter", children: [
|
|
3073
|
+
/* @__PURE__ */ jsx6(
|
|
3074
|
+
TitleSceneTemplate,
|
|
3075
|
+
{
|
|
3076
|
+
variables: { title },
|
|
3077
|
+
style: {},
|
|
3078
|
+
width: size.width,
|
|
3079
|
+
height: size.height,
|
|
3080
|
+
progress: 0.3,
|
|
3081
|
+
beatIntensity: 0,
|
|
3082
|
+
safeZone: { top: 0, right: 0, bottom: 0, left: 0 }
|
|
3083
|
+
}
|
|
3084
|
+
),
|
|
3085
|
+
preparing && /* @__PURE__ */ jsxs6("div", { className: "video-preparation", style: { top: titleBottom + 24 }, role: "status", "aria-label": "Video preparation", children: [
|
|
3086
|
+
/* @__PURE__ */ jsx6("span", { className: "video-preparation-spinner", "aria-hidden": "true" }),
|
|
3087
|
+
/* @__PURE__ */ jsx6("span", { className: "video-preparation-text", children: "Preparing your video\u2026" })
|
|
3088
|
+
] })
|
|
3089
|
+
] });
|
|
3079
3090
|
}
|
|
3080
3091
|
|
|
3081
3092
|
// src/video-chat/use-voice-input.ts
|
|
@@ -3353,7 +3364,7 @@ var visualModes = [
|
|
|
3353
3364
|
var defaultMode = visualModes[0];
|
|
3354
3365
|
|
|
3355
3366
|
// src/video-chat/video-chat.tsx
|
|
3356
|
-
import { Fragment as Fragment4, jsx as jsx8, jsxs as
|
|
3367
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3357
3368
|
var DESKTOP_WIDTH = 900;
|
|
3358
3369
|
function useViewportOrientation() {
|
|
3359
3370
|
const [portrait, setPortrait] = useState7(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`).matches : false);
|
|
@@ -3457,6 +3468,13 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3457
3468
|
const handoffStopped = chat.status === "error" || chat.status === "cancelled";
|
|
3458
3469
|
const waitingForBody = showing && presentedBody !== handoffKey;
|
|
3459
3470
|
const openingChapter = Boolean(shown?.prompt) && (!showing || waitingForBody) && chat.status !== "error" && chat.status !== "cancelled" && chat.status !== "ended";
|
|
3471
|
+
const [preparingKey, setPreparingKey] = useState7();
|
|
3472
|
+
useEffect9(() => {
|
|
3473
|
+
setPreparingKey(void 0);
|
|
3474
|
+
if (!openingChapter || !shown?.opening || chat.speaking) return;
|
|
3475
|
+
const timer = setTimeout(() => setPreparingKey(shown.id), 1e3);
|
|
3476
|
+
return () => clearTimeout(timer);
|
|
3477
|
+
}, [openingChapter, shown?.opening, shown?.id, chat.speaking]);
|
|
3460
3478
|
useLayoutEffect2(() => {
|
|
3461
3479
|
const current = { key: handoffKey, live: showing && !handoffStopped, active: openingChapter && showing, frame: 0 };
|
|
3462
3480
|
handoff.current = current;
|
|
@@ -3533,7 +3551,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3533
3551
|
observer.observe(composer);
|
|
3534
3552
|
return () => observer.disconnect();
|
|
3535
3553
|
}, []);
|
|
3536
|
-
return /* @__PURE__ */
|
|
3554
|
+
return /* @__PURE__ */ jsxs7(
|
|
3537
3555
|
"div",
|
|
3538
3556
|
{
|
|
3539
3557
|
className: `vanillasky-video-chat${className ? ` ${className}` : ""}`,
|
|
@@ -3545,13 +3563,13 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3545
3563
|
onPointerDown: controls.reveal,
|
|
3546
3564
|
onKeyDownCapture: controls.reveal,
|
|
3547
3565
|
children: [
|
|
3548
|
-
/* @__PURE__ */
|
|
3549
|
-
/* @__PURE__ */
|
|
3550
|
-
/* @__PURE__ */ jsx8(Logo, {}),
|
|
3551
|
-
(shown?.mode ?? selectedMode ?? options.mode) === "pexels" && /* @__PURE__ */ jsx8("a", { className: "media-credit", href: "https://www.pexels.com", target: "_blank", rel: "noopener noreferrer", children: "
|
|
3566
|
+
/* @__PURE__ */ jsxs7("header", { className: "chrome", ...controlEvents, children: [
|
|
3567
|
+
/* @__PURE__ */ jsxs7("div", { className: "session-brand", children: [
|
|
3568
|
+
/* @__PURE__ */ jsx8("a", { className: "home-link", href: "/", "aria-label": "Home", children: /* @__PURE__ */ jsx8(Logo, {}) }),
|
|
3569
|
+
(shown?.mode ?? selectedMode ?? options.mode) === "pexels" && /* @__PURE__ */ jsx8("a", { className: "media-credit", href: "https://www.pexels.com", target: "_blank", rel: "noopener noreferrer", children: "Pexels" })
|
|
3552
3570
|
] }),
|
|
3553
|
-
/* @__PURE__ */
|
|
3554
|
-
/* @__PURE__ */
|
|
3571
|
+
/* @__PURE__ */ jsxs7("div", { className: "group", children: [
|
|
3572
|
+
/* @__PURE__ */ jsxs7(
|
|
3555
3573
|
"button",
|
|
3556
3574
|
{
|
|
3557
3575
|
ref: historyButtonRef,
|
|
@@ -3601,9 +3619,9 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3601
3619
|
)
|
|
3602
3620
|
] })
|
|
3603
3621
|
] }),
|
|
3604
|
-
/* @__PURE__ */
|
|
3605
|
-
/* @__PURE__ */
|
|
3606
|
-
openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: openingTitle.length > 120 ? `${openingTitle.slice(0, 117).trimEnd()}\u2026` : openingTitle }, shown.id),
|
|
3622
|
+
/* @__PURE__ */ jsxs7("div", { className: "stage-area", children: [
|
|
3623
|
+
/* @__PURE__ */ jsxs7("div", { className: "stage", style: { background: "#000" }, children: [
|
|
3624
|
+
openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { preparing: preparingKey === shown.id && !chat.speaking, title: openingTitle.length > 120 ? `${openingTitle.slice(0, 117).trimEnd()}\u2026` : openingTitle }, shown.id),
|
|
3607
3625
|
!showing && chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
|
|
3608
3626
|
chat.playerProps && /* @__PURE__ */ jsx8("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx8(
|
|
3609
3627
|
VideoPlayer,
|
|
@@ -3617,29 +3635,29 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3617
3635
|
},
|
|
3618
3636
|
chat.playerKey
|
|
3619
3637
|
) }),
|
|
3620
|
-
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */
|
|
3638
|
+
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "ending", children: [
|
|
3621
3639
|
/* @__PURE__ */ jsx8("div", { className: "ending-wash", "aria-hidden": "true" }),
|
|
3622
|
-
/* @__PURE__ */
|
|
3640
|
+
/* @__PURE__ */ jsxs7("div", { className: "ending-body", children: [
|
|
3623
3641
|
/* @__PURE__ */ jsx8("p", { className: "ending-label", children: "Ask next" }),
|
|
3624
3642
|
/* @__PURE__ */ jsx8(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
|
|
3625
3643
|
] })
|
|
3626
3644
|
] })
|
|
3627
3645
|
] }),
|
|
3628
|
-
historyOpen && /* @__PURE__ */
|
|
3629
|
-
/* @__PURE__ */
|
|
3646
|
+
historyOpen && /* @__PURE__ */ jsxs7("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", role: "dialog", "aria-modal": "true", "aria-label": "Sessions", children: [
|
|
3647
|
+
/* @__PURE__ */ jsxs7("div", { className: "popover-heading", children: [
|
|
3630
3648
|
/* @__PURE__ */ jsx8("h2", { children: "Sessions" }),
|
|
3631
3649
|
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close sessions", onClick: closeHistory, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3632
3650
|
] }),
|
|
3633
|
-
/* @__PURE__ */
|
|
3651
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", className: "history-row session-new", "aria-label": "New session", onClick: newSession, children: [
|
|
3634
3652
|
/* @__PURE__ */ jsx8(Plus, {}),
|
|
3635
|
-
/* @__PURE__ */
|
|
3653
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3636
3654
|
"New session",
|
|
3637
3655
|
/* @__PURE__ */ jsx8("small", { children: "Start a fresh conversation" })
|
|
3638
3656
|
] })
|
|
3639
3657
|
] }),
|
|
3640
3658
|
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Current session" }),
|
|
3641
3659
|
chat.turns.length === 0 && /* @__PURE__ */ jsx8("p", { className: "history-empty", children: "Your questions will appear here." }),
|
|
3642
|
-
chat.turns.map((turn, index) => /* @__PURE__ */
|
|
3660
|
+
chat.turns.map((turn, index) => /* @__PURE__ */ jsxs7(
|
|
3643
3661
|
"button",
|
|
3644
3662
|
{
|
|
3645
3663
|
type: "button",
|
|
@@ -3656,7 +3674,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3656
3674
|
},
|
|
3657
3675
|
children: [
|
|
3658
3676
|
/* @__PURE__ */ jsx8("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
|
|
3659
|
-
/* @__PURE__ */
|
|
3677
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3660
3678
|
turn.prompt,
|
|
3661
3679
|
/* @__PURE__ */ jsx8("small", { children: turn.id === shown?.id ? "Now showing" : turn.completed ? "Play answer" : "Unfinished answer" })
|
|
3662
3680
|
] })
|
|
@@ -3664,9 +3682,9 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3664
3682
|
},
|
|
3665
3683
|
turn.id
|
|
3666
3684
|
)),
|
|
3667
|
-
savedSessions.length > 0 && /* @__PURE__ */
|
|
3685
|
+
savedSessions.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3668
3686
|
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Earlier sessions" }),
|
|
3669
|
-
savedSessions.map((session) => /* @__PURE__ */
|
|
3687
|
+
savedSessions.map((session) => /* @__PURE__ */ jsxs7("button", { type: "button", className: "history-row", onClick: () => {
|
|
3670
3688
|
listen.stop();
|
|
3671
3689
|
setDraft("");
|
|
3672
3690
|
setEditing(false);
|
|
@@ -3678,9 +3696,9 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3678
3696
|
setCaptionsExpanded(false);
|
|
3679
3697
|
}, children: [
|
|
3680
3698
|
/* @__PURE__ */ jsx8(Replay, {}),
|
|
3681
|
-
/* @__PURE__ */
|
|
3699
|
+
/* @__PURE__ */ jsxs7("span", { className: "prompt", children: [
|
|
3682
3700
|
session.turns[0]?.prompt,
|
|
3683
|
-
/* @__PURE__ */
|
|
3701
|
+
/* @__PURE__ */ jsxs7("small", { children: [
|
|
3684
3702
|
session.turns.length,
|
|
3685
3703
|
" ",
|
|
3686
3704
|
session.turns.length === 1 ? "answer" : "answers"
|
|
@@ -3689,7 +3707,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3689
3707
|
] }, session.id))
|
|
3690
3708
|
] })
|
|
3691
3709
|
] }),
|
|
3692
|
-
settingsOpen && /* @__PURE__ */
|
|
3710
|
+
settingsOpen && /* @__PURE__ */ jsxs7(
|
|
3693
3711
|
"div",
|
|
3694
3712
|
{
|
|
3695
3713
|
ref: settingsRef,
|
|
@@ -3699,14 +3717,14 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3699
3717
|
"aria-label": "Settings",
|
|
3700
3718
|
"aria-modal": "true",
|
|
3701
3719
|
children: [
|
|
3702
|
-
/* @__PURE__ */
|
|
3720
|
+
/* @__PURE__ */ jsxs7("div", { className: "popover-heading", children: [
|
|
3703
3721
|
/* @__PURE__ */ jsx8("h2", { children: "Settings" }),
|
|
3704
3722
|
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3705
3723
|
] }),
|
|
3706
|
-
/* @__PURE__ */
|
|
3724
|
+
/* @__PURE__ */ jsxs7("fieldset", { className: "playback-options", children: [
|
|
3707
3725
|
/* @__PURE__ */ jsx8("legend", { children: "Video source" }),
|
|
3708
|
-
visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */
|
|
3709
|
-
/* @__PURE__ */
|
|
3726
|
+
visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3727
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3710
3728
|
/* @__PURE__ */ jsx8("strong", { children: mode.label }),
|
|
3711
3729
|
/* @__PURE__ */ jsx8("small", { children: mode.note })
|
|
3712
3730
|
] }),
|
|
@@ -3722,10 +3740,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3722
3740
|
)
|
|
3723
3741
|
] }, mode.id))
|
|
3724
3742
|
] }),
|
|
3725
|
-
/* @__PURE__ */
|
|
3743
|
+
/* @__PURE__ */ jsxs7("fieldset", { className: "playback-options", children: [
|
|
3726
3744
|
/* @__PURE__ */ jsx8("legend", { children: "Watching" }),
|
|
3727
|
-
/* @__PURE__ */
|
|
3728
|
-
/* @__PURE__ */
|
|
3745
|
+
/* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3746
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3729
3747
|
/* @__PURE__ */ jsx8("strong", { children: "Subtitles" }),
|
|
3730
3748
|
/* @__PURE__ */ jsx8("small", { children: "Read along with the answer" })
|
|
3731
3749
|
] }),
|
|
@@ -3734,26 +3752,26 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3734
3752
|
setCaptionsExpanded(false);
|
|
3735
3753
|
} })
|
|
3736
3754
|
] }),
|
|
3737
|
-
/* @__PURE__ */
|
|
3738
|
-
/* @__PURE__ */
|
|
3755
|
+
/* @__PURE__ */ jsxs7("label", { className: "switch-row", children: [
|
|
3756
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
3739
3757
|
/* @__PURE__ */ jsx8("strong", { children: "Keep controls visible" }),
|
|
3740
3758
|
/* @__PURE__ */ jsx8("small", { children: "Keep the input bar on screen" })
|
|
3741
3759
|
] }),
|
|
3742
3760
|
/* @__PURE__ */ jsx8("input", { type: "checkbox", role: "switch", checked: alwaysShowControls, onChange: (event) => setAlwaysShowControls(event.target.checked) })
|
|
3743
3761
|
] })
|
|
3744
3762
|
] }),
|
|
3745
|
-
/* @__PURE__ */
|
|
3763
|
+
/* @__PURE__ */ jsxs7("nav", { className: "developer-links", "aria-label": "Build with VanillaSky", children: [
|
|
3746
3764
|
/* @__PURE__ */ jsx8("p", { className: "section-label", children: "Build with VanillaSky" }),
|
|
3747
|
-
/* @__PURE__ */
|
|
3765
|
+
/* @__PURE__ */ jsxs7("a", { href: "https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3748
3766
|
"Docs",
|
|
3749
3767
|
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3750
3768
|
] }),
|
|
3751
|
-
/* @__PURE__ */
|
|
3769
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", "aria-expanded": aboutOpen, "aria-controls": `${instanceId}-about`, onClick: () => setAboutOpen((open) => !open), children: [
|
|
3752
3770
|
"About",
|
|
3753
3771
|
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: aboutOpen ? "\u2212" : "+" })
|
|
3754
3772
|
] }),
|
|
3755
3773
|
/* @__PURE__ */ jsx8("div", { id: `${instanceId}-about`, className: "developer-about", role: "region", "aria-label": "About VanillaSky", hidden: !aboutOpen, children: /* @__PURE__ */ jsx8("p", { children: "VanillaSky is an open-source SDK for conversations that answer in video. Developers connect their own AI providers through their application server." }) }),
|
|
3756
|
-
/* @__PURE__ */
|
|
3774
|
+
/* @__PURE__ */ jsxs7("a", { href: "https://github.com/VanillaSkyAi/video", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3757
3775
|
"GitHub",
|
|
3758
3776
|
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3759
3777
|
] })
|
|
@@ -3762,8 +3780,8 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3762
3780
|
}
|
|
3763
3781
|
)
|
|
3764
3782
|
] }),
|
|
3765
|
-
/* @__PURE__ */ jsx8("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */
|
|
3766
|
-
/* @__PURE__ */ jsx8("div", { className: "caption-slot", "data-captions": captionsOn && Boolean(line), "aria-hidden": !captionsOn || !line, children: /* @__PURE__ */ jsx8("div", { className: "caption-clip", children: /* @__PURE__ */
|
|
3783
|
+
/* @__PURE__ */ jsx8("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */ jsxs7("div", { className: "panel-inner", children: [
|
|
3784
|
+
/* @__PURE__ */ jsx8("div", { className: "caption-slot", "data-captions": captionsOn && Boolean(line), "aria-hidden": !captionsOn || !line, children: /* @__PURE__ */ jsx8("div", { className: "caption-clip", children: /* @__PURE__ */ jsxs7(
|
|
3767
3785
|
"div",
|
|
3768
3786
|
{
|
|
3769
3787
|
className: "line-row",
|
|
@@ -3776,8 +3794,8 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3776
3794
|
onFocusCapture: captionControls.onFocusCapture,
|
|
3777
3795
|
onBlurCapture: captionControls.onBlurCapture,
|
|
3778
3796
|
children: [
|
|
3779
|
-
captionsOn && line && /* @__PURE__ */
|
|
3780
|
-
/* @__PURE__ */
|
|
3797
|
+
captionsOn && line && /* @__PURE__ */ jsxs7("div", { className: "caption-actions", children: [
|
|
3798
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", className: "caption-action", "aria-label": captionsExpanded ? "Collapse subtitles" : "Expand subtitles", "aria-expanded": captionsExpanded, onClick: () => setCaptionsExpanded((open) => !open), children: [
|
|
3781
3799
|
captionsExpanded ? "Collapse" : "Expand",
|
|
3782
3800
|
/* @__PURE__ */ jsx8(ChevronUp, {})
|
|
3783
3801
|
] }),
|
|
@@ -3790,17 +3808,17 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
|
|
|
3790
3808
|
]
|
|
3791
3809
|
}
|
|
3792
3810
|
) }) }),
|
|
3793
|
-
showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */
|
|
3811
|
+
showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */ jsxs7("div", { className: "recovery-notice", children: [
|
|
3794
3812
|
/* @__PURE__ */ jsx8("p", { role: "status", children: MEDIA_RECOVERY_NOTICE }),
|
|
3795
3813
|
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Dismiss notice", onClick: () => setDismissedNoticeTurn(chat.shownTurn?.id), children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3796
3814
|
] }),
|
|
3797
|
-
(chat.error || listen.error) && /* @__PURE__ */
|
|
3815
|
+
(chat.error || listen.error) && /* @__PURE__ */ jsxs7("p", { className: "error", role: "status", children: [
|
|
3798
3816
|
/* @__PURE__ */ jsx8(Warning, {}),
|
|
3799
3817
|
/* @__PURE__ */ jsx8("span", { children: chat.error?.message ?? listen.error })
|
|
3800
3818
|
] }),
|
|
3801
|
-
/* @__PURE__ */
|
|
3819
|
+
/* @__PURE__ */ jsxs7("div", { ref: composerRef, className: "conversation-composer", "data-editing": editing, ...controlEvents, children: [
|
|
3802
3820
|
(listen.listening || listen.thinking) && /* @__PURE__ */ jsx8("div", { className: "composer-meta", children: /* @__PURE__ */ jsx8("span", { role: "status", children: listen.listening ? "Listening\u2026 Tap the mic to finish, then review and send." : "Turning your words into a draft\u2026" }) }),
|
|
3803
|
-
/* @__PURE__ */
|
|
3821
|
+
/* @__PURE__ */ jsxs7("form", { className: "composer", "aria-label": "Ask a question", onSubmit: (event) => {
|
|
3804
3822
|
event.preventDefault();
|
|
3805
3823
|
ask(draft);
|
|
3806
3824
|
}, children: [
|
package/dist/server.js
CHANGED
|
@@ -1074,6 +1074,13 @@ function createChatShotPlanner(options) {
|
|
|
1074
1074
|
for (; cursor < buffer.length; cursor++) {
|
|
1075
1075
|
const character = buffer[cursor];
|
|
1076
1076
|
if (quoted) {
|
|
1077
|
+
if ((character === "\n" || character === "\r") && brief && depth === 1 && buffer[0] === "{") {
|
|
1078
|
+
const raw = buffer.slice(0, cursor + 1);
|
|
1079
|
+
buffer = buffer.slice(cursor + 1);
|
|
1080
|
+
cursor = depth = 0;
|
|
1081
|
+
quoted = escaped = false;
|
|
1082
|
+
return raw;
|
|
1083
|
+
}
|
|
1077
1084
|
if (escaped) escaped = false;
|
|
1078
1085
|
else if (character === "\\") escaped = true;
|
|
1079
1086
|
else if (character === '"') quoted = false;
|
|
@@ -1094,17 +1101,22 @@ function createChatShotPlanner(options) {
|
|
|
1094
1101
|
for await (const delta of upstream) {
|
|
1095
1102
|
context.signal.throwIfAborted();
|
|
1096
1103
|
if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1104
|
+
for (let offset = 0; offset < delta.length; ) {
|
|
1105
|
+
const capacity = 32768 - buffer.length;
|
|
1106
|
+
if (capacity <= 0) throw new Error("Chat plan line exceeds the bounded stream limit");
|
|
1107
|
+
const piece = delta.slice(offset, offset + capacity);
|
|
1108
|
+
buffer += piece;
|
|
1109
|
+
offset += piece.length;
|
|
1110
|
+
let raw = takeFrame();
|
|
1111
|
+
while (raw !== void 0) {
|
|
1112
|
+
try {
|
|
1113
|
+
const part = line(raw);
|
|
1114
|
+
if (part) yield JSON.stringify(part) + "\n";
|
|
1115
|
+
} catch (cause) {
|
|
1116
|
+
reject(cause);
|
|
1117
|
+
}
|
|
1118
|
+
raw = takeFrame();
|
|
1106
1119
|
}
|
|
1107
|
-
raw = takeFrame();
|
|
1108
1120
|
}
|
|
1109
1121
|
}
|
|
1110
1122
|
if (buffer.trim()) {
|
|
@@ -1223,6 +1235,7 @@ function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlr
|
|
|
1223
1235
|
`The selected footage mode is ${mode === "pexels" ? "Pexels stock search: use literal filmable subjects; never imply stock proves a mechanism or depicts fictional events exactly" : `AI video, with at most ${generatedVideoAvailable ? maxGeneratedVideos : 0} generation attempts`}. Missing footage becomes the authored chapter title, with complete narration. Never truncate already-authored narration when footage fails. The host selects providers; do not make source choices.`,
|
|
1224
1236
|
...mode === "cinematic" ? [generatedVideoAvailable && maxGeneratedVideos > 0 ? `Plan at most ${maxGeneratedVideos} generated-video beats in total, including the saved ending. Use at most ${maxGeneratedVideos - 1} developing shot records; the ending uses the remaining beat. The opening chapter does not consume a generated clip. Before writing, choose a concise, complete treatment that fits this budget: combine related ideas, preserve essential facts and qualifiers, and finish the requested answer. Do not plan an extra chapter tail simply because generation attempts will run out.${maxGeneratedVideos === 1 ? " Put the complete answer in the saved ending, set development to an empty string, and emit no developing shot records." : ""}` : "No generated-video attempts are available; plan a complete chapter-led answer with a useful ending. Do not omit the answer to satisfy a zero clip budget."] : [],
|
|
1225
1237
|
...mode === "pexels" ? ["Stock queries must retain the essential subject, activity and distinguishing equipment in the shot's subject field, within its word limit. That field alone is the search query; action and visualDirection do not refine it. Prefer common observable actions with usable framing. Do not replace the required actor or activity with scenery, a different sport or a loosely related setting. Preserve fictional or comic narration, but do not depend on stock showing an exact invented expression or sequence; choose an illustrative action that supports the beat."] : [],
|
|
1238
|
+
...mode === "pexels" ? ["Choose a separate stock subject for each beat, describing footage that can realistically exist in a stock library. For historical, abstract or unseen events, use relevant present-day evidence, objects, environments or analogous visible processes as clearly illustrative support. Do not require literal footage of events or subjects that cannot realistically be filmed. Keep the causal explanation in narration; do not claim illustrative footage records the historical event or proves the mechanism. Make stockSelection describe the chosen visible subject, not the overall topic. Do not use illustrative freedom to replace a required practical action, person, sport or distinguishing equipment with unrelated scenery."] : [],
|
|
1226
1239
|
...mode === "pexels" ? ['Include stockSelection on every shot and the saved ending when the essential subject is known: "stockSelection":{"subject":"essential actor or object category","activity":"optional literal activity","equipment":"optional distinguishing equipment","exclude":["optional contradictory subject or activity"]}. Each phrase must be 1\u20134 words and at most 48 characters; exclude has at most 3 phrases. The essential subject is separate from the setting: do not use scenery, mood, camera framing or incidental appearance as the actor. Keep the search query broad enough to find footage; the optional hint helps select results without substituting a different actor or task. Use exclusions only for actual contradictions, not every detail absent from the story. Omit unknown fields or the whole hint rather than inventing an anchor. This is selection guidance, not verification that footage depicts the exact narration.'] : [],
|
|
1227
1240
|
"Keep development to one concise sentence and visualDirection to the few details needed for consistency. Emit the complete brief, then the first developing shot immediately when developing shots are needed and allowed by the budget; otherwise end after the brief. Continue the same stream without an outline, recap or second planning pass. The saved ending must still contain the complete payoff before the brief is emitted.",
|
|
1228
1241
|
"For a very short answer whose ending alone fulfills the request, development may be empty and no developing shots are needed. Otherwise, develop the essential content before the ending.",
|
package/package.json
CHANGED
|
@@ -53,19 +53,21 @@ export async function findStockFootage(query: string, orientation: VideoOrientat
|
|
|
53
53
|
const normalized = query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
54
54
|
const tokens = words(normalized);
|
|
55
55
|
const selection = selectionHint(rawSelection);
|
|
56
|
-
const key = JSON.stringify({version:
|
|
56
|
+
const key = JSON.stringify({version: 4, orientation, query: normalized, selection});
|
|
57
57
|
const apiKey = process.env.PEXELS_API_KEY;
|
|
58
58
|
if (!apiKey || !tokens.length || normalized.length > 80 || tokens.length > 8) return null;
|
|
59
59
|
const existing = cache.get(key);
|
|
60
60
|
if (existing && existing.expires > Date.now()) return existing.media;
|
|
61
61
|
const url = new URL("https://api.pexels.com/v1/videos/search");
|
|
62
|
-
url.search = new URLSearchParams({ query: normalized,
|
|
62
|
+
url.search = new URLSearchParams({ query: normalized, per_page: "12", size: "medium" }).toString();
|
|
63
63
|
const response = await fetch(url, { headers: { Authorization: apiKey }, signal });
|
|
64
64
|
signal.throwIfAborted();
|
|
65
65
|
if (!response.ok) return null;
|
|
66
66
|
const result = await response.json() as { videos?: PexelsVideo[] };
|
|
67
67
|
signal.throwIfAborted();
|
|
68
|
-
let selected: StockVideo | null = null, bestScore = -1;
|
|
68
|
+
let selected: StockVideo | null = null, bestScore = -1, bestOrientation = -1;
|
|
69
|
+
const matchesOrientation = (file: {width?: number; height?: number}) => orientation === "portrait"
|
|
70
|
+
? file.height! > file.width! : file.width! >= file.height!;
|
|
69
71
|
for (const video of (Array.isArray(result.videos) ? result.videos : []).slice(0, 12)) {
|
|
70
72
|
if (!pexelsUrl(video.url)) continue;
|
|
71
73
|
const slug = new URL(video.url).pathname.replace(/^\/video\//, "");
|
|
@@ -88,12 +90,15 @@ export async function findStockFootage(query: string, orientation: VideoOrientat
|
|
|
88
90
|
const files = (Array.isArray(video.video_files) ? video.video_files : []).filter(file =>
|
|
89
91
|
file.file_type === "video/mp4" && pexelsUrl(file.link)
|
|
90
92
|
&& Number.isFinite(file.width) && Number.isFinite(file.height)
|
|
91
|
-
&& Math.min(file.width!, file.height!) >= 360
|
|
92
|
-
|
|
93
|
-
).sort((a, b) => Math.abs(Math.max(a.width!, a.height!) - 1280) - Math.abs(Math.max(b.width!, b.height!) - 1280));
|
|
93
|
+
&& Math.min(file.width!, file.height!) >= 360,
|
|
94
|
+
).sort((a, b) => Number(matchesOrientation(b)) - Number(matchesOrientation(a)) || Math.abs(Math.max(a.width!, a.height!) - 1280) - Math.abs(Math.max(b.width!, b.height!) - 1280));
|
|
94
95
|
const file = files[0];
|
|
95
|
-
if (!file
|
|
96
|
+
if (!file) continue;
|
|
97
|
+
const orientationScore = Number(matchesOrientation(file));
|
|
98
|
+
// Prefer composition fit only when subject relevance is equal.
|
|
99
|
+
if (matches < bestScore || (matches === bestScore && orientationScore <= bestOrientation)) continue;
|
|
96
100
|
bestScore = matches;
|
|
101
|
+
bestOrientation = orientationScore;
|
|
97
102
|
selected = { url: file.link!, type: "video", ...(pexelsUrl(video.image) ? { posterUrl: video.image } : {}) };
|
|
98
103
|
}
|
|
99
104
|
// Bounded process-local cache; no request signal or credentials are retained.
|
package/styles/video-chat.css
CHANGED
|
@@ -370,16 +370,6 @@
|
|
|
370
370
|
text-shadow: 0 1px 3px oklch(0.14 0.05 265 / 70%);
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
.vanillasky-video-chat .ending .cards {
|
|
374
|
-
margin-top: 0;
|
|
375
|
-
display: grid;
|
|
376
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
377
|
-
flex-shrink: 0;
|
|
378
|
-
overflow: visible;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.vanillasky-video-chat .ending .card-dots { align-self: flex-start; margin-left: -0.35rem; }
|
|
382
|
-
|
|
383
373
|
.vanillasky-video-chat .panel { display: grid; align-items: center; min-height: 0; background: var(--vs-bg); }
|
|
384
374
|
|
|
385
375
|
.vanillasky-video-chat .panel-inner {
|
|
@@ -620,7 +610,7 @@
|
|
|
620
610
|
|
|
621
611
|
.vanillasky-video-chat .cards { gap: 14px; margin: auto -12px 0; width: calc(100% + 24px); max-width: calc(100% + 24px); padding: 12px; }
|
|
622
612
|
|
|
623
|
-
.vanillasky-video-chat .cards button { width: clamp(160px, 18vw, 260px); aspect-ratio: 1.65; border-radius: 14px; box-shadow: 0 8px 30px rgb(0 0 0 / 24%); background: #202438; }
|
|
613
|
+
.vanillasky-video-chat .cards button { --vs-card-width: clamp(160px, 18vw, 260px); --vs-card-ratio: 1.65; width: var(--vs-card-width); aspect-ratio: 1.65; border-radius: 14px; box-shadow: 0 8px 30px rgb(0 0 0 / 24%); background: #202438; }
|
|
624
614
|
|
|
625
615
|
.vanillasky-video-chat .card-prompt { font-size: clamp(13px, 1.1vw, 16px); padding: 14px 16px; letter-spacing: -.015em; font-weight: 550; }
|
|
626
616
|
|
|
@@ -642,7 +632,7 @@
|
|
|
642
632
|
.vanillasky-video-chat .welcome-body { padding: max(135px, 17dvh) 20px 118px; gap: 24px; }
|
|
643
633
|
.vanillasky-video-chat .welcome-title { font-size: clamp(36px, 9.6vw, 58px); line-height: 1.12; letter-spacing: -.055em; }
|
|
644
634
|
.vanillasky-video-chat .cards { gap: 12px; width: calc(100% + 20px); max-width: calc(100% + 20px); padding: 12px 20px 12px 12px; }
|
|
645
|
-
.vanillasky-video-chat .cards button { width: 172px; aspect-ratio: .95; }
|
|
635
|
+
.vanillasky-video-chat .cards button { --vs-card-width: 172px; --vs-card-ratio: .95; width: var(--vs-card-width); aspect-ratio: .95; }
|
|
646
636
|
.vanillasky-video-chat .card-prompt { font-size: 14px; padding: 14px; }
|
|
647
637
|
|
|
648
638
|
}
|
|
@@ -650,13 +640,13 @@
|
|
|
650
640
|
@media (max-height: 650px) and (min-width: 701px) {
|
|
651
641
|
.vanillasky-video-chat .welcome-body { padding-top: 110px; padding-bottom: 118px; }
|
|
652
642
|
.vanillasky-video-chat .welcome-title { font-size: clamp(32px, 4vw, 54px); }
|
|
653
|
-
.vanillasky-video-chat .cards button { width: 160px; }
|
|
643
|
+
.vanillasky-video-chat .cards button { --vs-card-width: 160px; width: var(--vs-card-width); }
|
|
654
644
|
}
|
|
655
645
|
|
|
656
646
|
@media (max-height: 650px) and (max-width: 700px) {
|
|
657
647
|
.vanillasky-video-chat .welcome-body { padding-top: 98px; }
|
|
658
648
|
.vanillasky-video-chat .welcome-title { font-size: 34px; }
|
|
659
|
-
.vanillasky-video-chat .cards button { width: 145px; aspect-ratio: 1.5; }
|
|
649
|
+
.vanillasky-video-chat .cards button { --vs-card-width: 145px; --vs-card-ratio: 1.5; width: var(--vs-card-width); aspect-ratio: 1.5; }
|
|
660
650
|
}
|
|
661
651
|
|
|
662
652
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1079,31 +1069,58 @@
|
|
|
1079
1069
|
@keyframes vanillasky-chapter-enter { from { opacity: 0; } to { opacity: 1; } }
|
|
1080
1070
|
@media (prefers-reduced-motion: reduce) { .vanillasky-video-chat .opening-chapter { animation: none; } }
|
|
1081
1071
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
.vanillasky-video-chat .ending .cards button {
|
|
1085
|
-
display: flex;
|
|
1086
|
-
flex-direction: column;
|
|
1087
|
-
justify-content: flex-end;
|
|
1088
|
-
width: 100%;
|
|
1089
|
-
height: 100%;
|
|
1090
|
-
min-height: 180px;
|
|
1091
|
-
aspect-ratio: auto;
|
|
1072
|
+
@media (max-height: 650px) {
|
|
1073
|
+
.vanillasky-video-chat .ending-body { inset-block: 80px 180px; }
|
|
1092
1074
|
}
|
|
1093
1075
|
|
|
1094
|
-
.vanillasky-video-chat .
|
|
1095
|
-
position:
|
|
1096
|
-
inset:
|
|
1097
|
-
|
|
1098
|
-
|
|
1076
|
+
.vanillasky-video-chat .video-preparation {
|
|
1077
|
+
position: absolute;
|
|
1078
|
+
inset-inline: 0;
|
|
1079
|
+
z-index: 3;
|
|
1080
|
+
display: flex;
|
|
1081
|
+
align-items: center;
|
|
1082
|
+
justify-content: center;
|
|
1083
|
+
gap: 10px;
|
|
1084
|
+
pointer-events: none;
|
|
1085
|
+
color: rgb(255 255 255 / 62%);
|
|
1086
|
+
font-size: 17px;
|
|
1099
1087
|
line-height: 1.4;
|
|
1100
1088
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1089
|
+
.vanillasky-video-chat .video-preparation-spinner {
|
|
1090
|
+
width: 16px;
|
|
1091
|
+
height: 16px;
|
|
1092
|
+
border: 1.5px solid rgb(255 255 255 / 18%);
|
|
1093
|
+
border-top-color: rgb(255 255 255 / 75%);
|
|
1094
|
+
border-radius: 50%;
|
|
1095
|
+
animation: vanillasky-video-preparation 1s linear infinite;
|
|
1096
|
+
}
|
|
1097
|
+
@keyframes vanillasky-video-preparation { to { transform: rotate(360deg); } }
|
|
1098
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1099
|
+
.vanillasky-video-chat .video-preparation-spinner { animation: none; }
|
|
1105
1100
|
}
|
|
1106
1101
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1102
|
+
.vanillasky-video-chat .video-preparation-text {
|
|
1103
|
+
color: transparent;
|
|
1104
|
+
background: linear-gradient(105deg, rgb(255 255 255 / 55%) 35%, #fff 50%, rgb(255 255 255 / 55%) 65%);
|
|
1105
|
+
background-size: 250% 100%;
|
|
1106
|
+
background-clip: text;
|
|
1107
|
+
-webkit-background-clip: text;
|
|
1108
|
+
animation: vanillasky-preparation-shimmer 2.4s ease-in-out infinite;
|
|
1109
|
+
}
|
|
1110
|
+
@keyframes vanillasky-preparation-shimmer {
|
|
1111
|
+
from { background-position: 100% 0; }
|
|
1112
|
+
to { background-position: 0% 0; }
|
|
1113
|
+
}
|
|
1114
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1115
|
+
.vanillasky-video-chat .video-preparation-text { animation: none; background: none; color: inherit; }
|
|
1109
1116
|
}
|
|
1117
|
+
@media (forced-colors: active) {
|
|
1118
|
+
.vanillasky-video-chat .video-preparation-text { animation: none; background: none; color: CanvasText; }
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.vanillasky-video-chat .home-link { display: inline-flex; border-radius: 4px; }
|
|
1122
|
+
.vanillasky-video-chat .home-link:focus-visible { outline: 2px solid currentColor; outline-offset: 6px; }
|
|
1123
|
+
.vanillasky-video-chat .cards button { display: flex; flex-direction: column; justify-content: flex-end; aspect-ratio: auto; min-height: calc(var(--vs-card-width) / var(--vs-card-ratio)); }
|
|
1124
|
+
.vanillasky-video-chat .card-prompt { position: relative; inset: auto; overflow-wrap: anywhere; flex-shrink: 0; }
|
|
1125
|
+
|
|
1126
|
+
.vanillasky-video-chat .cards { flex-shrink: 0; }
|