@mevdragon/vidfarm-devcli 0.20.5 → 0.20.8
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/.agents/skills/editor-capabilities/SKILL.md +15 -1
- package/.agents/skills/vidfarm-director/SKILL.md +106 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +111 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +243 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +265 -0
- package/SKILL.director.md +521 -261
- package/dist/src/cli.js +404 -96
- package/dist/src/devcli/clips.js +15 -10
- package/dist/src/devcli/doctor.js +2 -2
- package/dist/src/devcli/local-backend.js +46 -2
- package/dist/src/services/clip-curation/cost.js +5 -1
- package/dist/src/services/clip-curation/gemini.js +10 -0
- package/package.json +30 -10
- package/SKILL.platform.md +0 -432
- package/demo/README.md +0 -28
- package/demo/dist/app.css +0 -1
- package/demo/dist/app.js +0 -1850
- package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
- package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
- package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
- package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
- package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +0 -7
- package/demo/dist/icons/timeline/captions.svg +0 -5
- package/demo/dist/icons/timeline/composition.svg +0 -12
- package/demo/dist/icons/timeline/image.svg +0 -18
- package/demo/dist/icons/timeline/music.svg +0 -10
- package/demo/dist/icons/timeline/text.svg +0 -3
- package/demo/dist/index.html +0 -15
- package/dist/src/account-pages-legacy.js +0 -9396
- package/dist/src/account-pages.js +0 -61
- package/dist/src/app.js +0 -21602
- package/dist/src/composition-runtime.js +0 -1053
- package/dist/src/config.js +0 -217
- package/dist/src/context.js +0 -447
- package/dist/src/dev-app-legacy.js +0 -739
- package/dist/src/dev-app.js +0 -6
- package/dist/src/devcli/migrate-local.js +0 -140
- package/dist/src/devcli/sync.js +0 -368
- package/dist/src/domain.js +0 -5
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat.js +0 -756
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/frontend/debug.js +0 -71
- package/dist/src/frontend/discover-client.js +0 -130
- package/dist/src/frontend/discover-store.js +0 -23
- package/dist/src/frontend/file-directory.js +0 -1018
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/homepage-client.js +0 -446
- package/dist/src/frontend/homepage-shared.js +0 -201
- package/dist/src/frontend/homepage-store.js +0 -66
- package/dist/src/frontend/homepage-view.js +0 -705
- package/dist/src/frontend/page-runtime-client.js +0 -132
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/template-editor-chat.js +0 -4181
- package/dist/src/help-page.js +0 -346
- package/dist/src/homepage.js +0 -1458
- package/dist/src/index.js +0 -16
- package/dist/src/instrument.js +0 -30
- package/dist/src/landing-page.js +0 -384
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-shell.js +0 -1452
- package/dist/src/primitive-context.js +0 -416
- package/dist/src/primitive-registry.js +0 -3940
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitives/hyperframes-media.js +0 -108
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/ready-post-schedule-component.js +0 -1540
- package/dist/src/registry.js +0 -296
- package/dist/src/reskin/agency-page.js +0 -299
- package/dist/src/reskin/calendar-page.js +0 -568
- package/dist/src/reskin/chat-page.js +0 -942
- package/dist/src/reskin/discover-page.js +0 -1788
- package/dist/src/reskin/document.js +0 -1587
- package/dist/src/reskin/help-page.js +0 -357
- package/dist/src/reskin/index-page.js +0 -62
- package/dist/src/reskin/inpaint-clipper-page.js +0 -890
- package/dist/src/reskin/inpaint-page.js +0 -2554
- package/dist/src/reskin/inpaint-video-page.js +0 -1339
- package/dist/src/reskin/job-runs-page.js +0 -477
- package/dist/src/reskin/library-page.js +0 -1634
- package/dist/src/reskin/login-page.js +0 -262
- package/dist/src/reskin/portfolio-page.js +0 -687
- package/dist/src/reskin/pricing-page.js +0 -390
- package/dist/src/reskin/settings-page.js +0 -732
- package/dist/src/reskin/theme.js +0 -711
- package/dist/src/runtime.js +0 -35
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/billing-pricing.js +0 -39
- package/dist/src/services/billing.js +0 -241
- package/dist/src/services/cast.js +0 -127
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/clip-records.js +0 -250
- package/dist/src/services/clip-search.js +0 -77
- package/dist/src/services/clip-vectors.js +0 -125
- package/dist/src/services/composition-sanitize.js +0 -124
- package/dist/src/services/composition-watch.js +0 -79
- package/dist/src/services/elevenlabs.js +0 -222
- package/dist/src/services/file-directory.js +0 -117
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-manifest.js +0 -42
- package/dist/src/services/ghostcut.js +0 -179
- package/dist/src/services/hyperframes.js +0 -3517
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +0 -766
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/providers.js +0 -2748
- package/dist/src/services/rate-limits.js +0 -262
- package/dist/src/services/scene-annotations.js +0 -32
- package/dist/src/services/serverless-auth.js +0 -382
- package/dist/src/services/serverless-jobs.js +0 -1084
- package/dist/src/services/serverless-provider-keys.js +0 -409
- package/dist/src/services/serverless-records.js +0 -1515
- package/dist/src/services/serverless-template-configs.js +0 -75
- package/dist/src/services/storage.js +0 -461
- package/dist/src/services/swipe-customize.js +0 -437
- package/dist/src/services/template-certification.js +0 -413
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/upstream.js +0 -248
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/template-editor-pages.js +0 -2576
- package/dist/src/template-editor-shell.js +0 -2893
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/worker.js +0 -17
- package/public/assets/discover-client-app.js +0 -1
- package/public/assets/file-directory-app.js +0 -3
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
- package/src/assets/SELLING_WITH_HOOKS.md +0 -377
- package/src/assets/SELLING_WITH_VSLS.md +0 -606
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -1,705 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
-
import { resolveAccountDisplayName, withAccountQuery } from "../page-shell.js";
|
|
4
|
-
import { isVideoPreview } from "./homepage-shared.js";
|
|
5
|
-
function DiscoverIcon() {
|
|
6
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M10 3v4" }), _jsx("path", { d: "M10 13v4" }), _jsx("path", { d: "M3 10h4" }), _jsx("path", { d: "M13 10h4" }), _jsx("path", { d: "m5.8 5.8 2.3 2.3" }), _jsx("path", { d: "m11.9 11.9 2.3 2.3" }), _jsx("path", { d: "m14.2 5.8-2.3 2.3" }), _jsx("path", { d: "m8.1 11.9-2.3 2.3" })] });
|
|
7
|
-
}
|
|
8
|
-
function LibraryIcon() {
|
|
9
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M4.5 4.5h3v11h-3z" }), _jsx("path", { d: "M8.7 4.5h3v11h-3z" }), _jsx("path", { d: "m13.4 5.1 2.3-.6 2.2 10.7-2.3.6z" })] });
|
|
10
|
-
}
|
|
11
|
-
function CalendarIcon() {
|
|
12
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "10", cy: "10", r: "7.2" }), _jsx("path", { d: "M10 6.3v4.1l2.8 1.7" })] });
|
|
13
|
-
}
|
|
14
|
-
function ChatIcon() {
|
|
15
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M4.2 5.2a2 2 0 0 1 2-2h7.6a2 2 0 0 1 2 2v5.6a2 2 0 0 1-2 2H9.2l-4.1 3.1v-3.1a2 2 0 0 1-.9-1.7Z" }), _jsx("path", { d: "M7.3 7.1h5.4" }), _jsx("path", { d: "M7.3 9.6h3.7" })] });
|
|
16
|
-
}
|
|
17
|
-
function SettingsIcon() {
|
|
18
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14.8 15.2a5.4 5.4 0 1 0-9.6 0" }), _jsx("circle", { cx: "10", cy: "6.8", r: "3.1" })] });
|
|
19
|
-
}
|
|
20
|
-
function MoreIcon() {
|
|
21
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "currentColor", children: [_jsx("circle", { cx: "4.2", cy: "10", r: "1.45" }), _jsx("circle", { cx: "10", cy: "10", r: "1.45" }), _jsx("circle", { cx: "15.8", cy: "10", r: "1.45" })] });
|
|
22
|
-
}
|
|
23
|
-
function MenuIcon() {
|
|
24
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", children: [_jsx("path", { d: "M3.5 5.5h13" }), _jsx("path", { d: "M3.5 10h13" }), _jsx("path", { d: "M3.5 14.5h13" })] });
|
|
25
|
-
}
|
|
26
|
-
function SearchIcon() {
|
|
27
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "8.5", cy: "8.5", r: "5.8" }), _jsx("path", { d: "m13 13 4.2 4.2" })] });
|
|
28
|
-
}
|
|
29
|
-
function BookmarkIcon() {
|
|
30
|
-
return _jsx("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M6 3.8h8a1 1 0 0 1 1 1v11.4l-5-2.9-5 2.9V4.8a1 1 0 0 1 1-1Z" }) });
|
|
31
|
-
}
|
|
32
|
-
function clearLogoutAccountCache(accountId) {
|
|
33
|
-
const account = accountId?.trim();
|
|
34
|
-
if (!account || typeof window === "undefined") {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
try {
|
|
38
|
-
const registryIndexKey = "vidfarm:agency:accounts:index";
|
|
39
|
-
const rawIds = window.localStorage.getItem(registryIndexKey);
|
|
40
|
-
const ids = rawIds ? JSON.parse(rawIds) : [];
|
|
41
|
-
if (Array.isArray(ids)) {
|
|
42
|
-
window.localStorage.setItem(registryIndexKey, JSON.stringify(ids.filter((value) => typeof value === "string" && value !== account)));
|
|
43
|
-
}
|
|
44
|
-
window.localStorage.removeItem(`vidfarm:agency:account_${account}`);
|
|
45
|
-
window.localStorage.removeItem(`vidfarm:flockposter-cache:v1_${account}`);
|
|
46
|
-
window.localStorage.removeItem(`vidfarm:discover-bookmarks:v1_${account}`);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// Ignore storage failures.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function PrimaryNav({ account }) {
|
|
53
|
-
const logoutDialogRef = useRef(null);
|
|
54
|
-
const items = account.isLoggedIn
|
|
55
|
-
? [
|
|
56
|
-
{ id: "discover", href: withAccountQuery("/discover", account.userId), label: "Discover", icon: _jsx(DiscoverIcon, {}) },
|
|
57
|
-
{ id: "library", href: withAccountQuery("/library", account.userId), label: "Library", icon: _jsx(LibraryIcon, {}) },
|
|
58
|
-
{ id: "calendar", href: withAccountQuery("/calendar", account.userId), label: "Calendar", icon: _jsx(CalendarIcon, {}) },
|
|
59
|
-
{ id: "settings", href: withAccountQuery("/settings", account.userId), label: "Settings", icon: _jsx(SettingsIcon, {}) }
|
|
60
|
-
]
|
|
61
|
-
: [
|
|
62
|
-
{ id: "discover", href: "/discover", label: "Discover", icon: _jsx(DiscoverIcon, {}) },
|
|
63
|
-
{ id: "login", href: "/login", label: "Login", icon: _jsx(SettingsIcon, {}) }
|
|
64
|
-
];
|
|
65
|
-
const menuItems = account.isLoggedIn
|
|
66
|
-
? [
|
|
67
|
-
{ id: "chat", href: withAccountQuery("/chat", account.userId), label: "Chat" },
|
|
68
|
-
{ id: "job-runs", href: withAccountQuery("/job-runs", account.userId), label: "Run History" },
|
|
69
|
-
{ id: "tools", href: withAccountQuery("/tools/image", account.userId), label: "Tools" },
|
|
70
|
-
{ id: "help", href: "/help", label: "Help" },
|
|
71
|
-
{ id: "logout", action: "logout", label: "Log Out" }
|
|
72
|
-
]
|
|
73
|
-
: [
|
|
74
|
-
{ id: "help", href: "/help", label: "Help" },
|
|
75
|
-
{ id: "login", href: "/login", label: "Login" }
|
|
76
|
-
];
|
|
77
|
-
const mobileMenuPrimaryItems = items.filter((item) => !menuItems.some((menuItem) => menuItem.id === item.id));
|
|
78
|
-
return (_jsxs(_Fragment, { children: [_jsxs("nav", { className: "primary-nav", "aria-label": "Primary", children: [items.map((item) => (_jsxs("a", { className: `nav-link${item.id === "discover" ? " is-active" : ""}`, href: item.href, children: [_jsx("span", { className: "nav-icon", "aria-hidden": "true", children: item.icon }), _jsx("span", { children: item.label })] }, item.id))), menuItems.map((item) => ("href" in item ? (_jsx("a", { className: "nav-link nav-mobile-extra", href: item.href, children: _jsx("span", { children: item.label }) }, `mobile-${item.id}`)) : (_jsx("button", { type: "button", className: "nav-link nav-mobile-extra nav-action-link", onClick: () => {
|
|
79
|
-
if (!logoutDialogRef.current?.open) {
|
|
80
|
-
logoutDialogRef.current?.showModal();
|
|
81
|
-
}
|
|
82
|
-
}, children: _jsx("span", { children: item.label }) }, `mobile-${item.id}`)))), menuItems.length ? (_jsxs("details", { className: "nav-menu", children: [_jsxs("summary", { className: "nav-link nav-menu-trigger", "aria-label": "Open navigation menu", children: [_jsx("span", { className: "nav-icon nav-menu-desktop-icon", "aria-hidden": "true", children: _jsx(MoreIcon, {}) }), _jsx("span", { className: "nav-icon nav-menu-mobile-icon", "aria-hidden": "true", children: _jsx(MenuIcon, {}) }), _jsx("span", { className: "nav-menu-label", children: "Menu" })] }), _jsxs("div", { className: "nav-menu-panel", role: "menu", children: [mobileMenuPrimaryItems.map((item) => (_jsx("a", { className: `nav-menu-item nav-menu-mobile-only${item.id === "discover" ? " is-active" : ""}`, role: "menuitem", href: item.href, children: item.label }, `menu-mobile-${item.id}`))), menuItems.map((item) => ("href" in item ? (_jsx("a", { className: "nav-menu-item", role: "menuitem", href: item.href, children: item.label }, item.id)) : (_jsx("button", { type: "button", className: "nav-menu-item nav-menu-button", role: "menuitem", onClick: () => {
|
|
83
|
-
if (!logoutDialogRef.current?.open) {
|
|
84
|
-
logoutDialogRef.current?.showModal();
|
|
85
|
-
}
|
|
86
|
-
}, children: item.label }, item.id))))] })] })) : null] }), account.isLoggedIn ? (_jsxs("dialog", { className: "logout-dialog", ref: logoutDialogRef, onClick: (event) => {
|
|
87
|
-
const dialog = logoutDialogRef.current;
|
|
88
|
-
if (!dialog) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const rect = dialog.getBoundingClientRect();
|
|
92
|
-
const isBackdropClick = event.clientX < rect.left ||
|
|
93
|
-
event.clientX > rect.right ||
|
|
94
|
-
event.clientY < rect.top ||
|
|
95
|
-
event.clientY > rect.bottom;
|
|
96
|
-
if (isBackdropClick) {
|
|
97
|
-
dialog.close();
|
|
98
|
-
}
|
|
99
|
-
}, children: [_jsxs("div", { className: "logout-dialog-head", children: [_jsx("div", { className: "label logout-dialog-kicker", children: "Log out" }), _jsx("h3", { children: "Log out of this browser?" }), _jsx("p", { children: "This clears the stored developer key, workspace access, and local session state for the current browser." })] }), _jsxs("div", { className: "toolbar logout-dialog-actions", children: [_jsx("button", { type: "button", className: "secondary", onClick: () => logoutDialogRef.current?.close(), children: "Cancel" }), _jsxs("form", { method: "post", action: "/logout", onSubmit: () => clearLogoutAccountCache(account.userId), children: [_jsx("input", { type: "hidden", name: "account", value: account.userId ?? "" }), _jsx("button", { type: "submit", className: "danger", children: "Yes, log out" })] })] })] })) : null] }));
|
|
100
|
-
}
|
|
101
|
-
function BrandLockup({ account }) {
|
|
102
|
-
const email = account.email?.trim() || "";
|
|
103
|
-
const derivedName = resolveAccountDisplayName({ displayName: account.displayName, email: account.email });
|
|
104
|
-
const href = withAccountQuery("/discover", account.userId);
|
|
105
|
-
return (_jsxs("a", { className: "brand-lockup", href: href, children: [_jsx("img", { className: "brand-logo-image", src: "/assets/logo-vidfarm.png", alt: "VidFarm AI" }), _jsxs("div", { className: "brand-copy", children: [_jsxs("div", { className: "brand-title brand-title-static", children: ["VidFarm", _jsx("span", { className: "brand-title-mobile-hidden", children: " AI" })] }), email ? (_jsxs("div", { className: "brand-account-meta", children: [derivedName, " ", _jsxs("span", { children: ["(", email, ")"] })] })) : null] })] }));
|
|
106
|
-
}
|
|
107
|
-
function CloseIcon() {
|
|
108
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [_jsx("path", { d: "m5 5 10 10" }), _jsx("path", { d: "m15 5-10 10" })] });
|
|
109
|
-
}
|
|
110
|
-
function DownloadIcon() {
|
|
111
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M10 3v9" }), _jsx("path", { d: "m6.5 8.5 3.5 3.5 3.5-3.5" }), _jsx("path", { d: "M4 16h12" })] });
|
|
112
|
-
}
|
|
113
|
-
function LinkIcon() {
|
|
114
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M8.4 11.6a3.2 3.2 0 0 0 4.5 0l2.2-2.2a3.2 3.2 0 0 0-4.5-4.5L9.4 6.1" }), _jsx("path", { d: "M11.6 8.4a3.2 3.2 0 0 0-4.5 0L4.9 10.6a3.2 3.2 0 0 0 4.5 4.5l1.2-1.2" })] });
|
|
115
|
-
}
|
|
116
|
-
function SpeakerOnIcon() {
|
|
117
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M3.5 8v4h2.8l3.5 3V5l-3.5 3Z" }), _jsx("path", { d: "M12.4 7.6a3.4 3.4 0 0 1 0 4.8" }), _jsx("path", { d: "M14.6 5.4a6.5 6.5 0 0 1 0 9.2" })] });
|
|
118
|
-
}
|
|
119
|
-
function SpeakerOffIcon() {
|
|
120
|
-
return _jsxs("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M3.5 8v4h2.8l3.5 3V5l-3.5 3Z" }), _jsx("path", { d: "m12.6 8 4 4" }), _jsx("path", { d: "m16.6 8-4 4" })] });
|
|
121
|
-
}
|
|
122
|
-
const DISCOVER_MUTE_STORAGE_KEY = "vidfarm:discover-muted";
|
|
123
|
-
let discoverMuted = typeof window !== "undefined" && window.localStorage?.getItem(DISCOVER_MUTE_STORAGE_KEY) === "1";
|
|
124
|
-
const discoverMuteListeners = new Set();
|
|
125
|
-
function setDiscoverMuted(muted) {
|
|
126
|
-
discoverMuted = muted;
|
|
127
|
-
try {
|
|
128
|
-
window.localStorage.setItem(DISCOVER_MUTE_STORAGE_KEY, muted ? "1" : "0");
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// storage unavailable (private mode); the toggle still works for this page view
|
|
132
|
-
}
|
|
133
|
-
for (const listener of discoverMuteListeners) {
|
|
134
|
-
listener();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function subscribeDiscoverMuted(listener) {
|
|
138
|
-
discoverMuteListeners.add(listener);
|
|
139
|
-
return () => {
|
|
140
|
-
discoverMuteListeners.delete(listener);
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function useDiscoverMuted() {
|
|
144
|
-
return useSyncExternalStore(subscribeDiscoverMuted, () => discoverMuted, () => false);
|
|
145
|
-
}
|
|
146
|
-
// While the preview modal is open, feed videos pause so their audio does not
|
|
147
|
-
// compete with the modal player.
|
|
148
|
-
let discoverPlaybackSuspended = false;
|
|
149
|
-
const discoverSuspendListeners = new Set();
|
|
150
|
-
function setDiscoverPlaybackSuspended(suspended) {
|
|
151
|
-
discoverPlaybackSuspended = suspended;
|
|
152
|
-
for (const listener of discoverSuspendListeners) {
|
|
153
|
-
listener();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function LazyVideo({ src, controls = true }) {
|
|
157
|
-
const ref = useRef(null);
|
|
158
|
-
useEffect(() => {
|
|
159
|
-
const video = ref.current;
|
|
160
|
-
if (!video) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const load = () => {
|
|
164
|
-
if (!video.dataset.src || video.getAttribute("src")) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
video.setAttribute("src", video.dataset.src);
|
|
168
|
-
video.load();
|
|
169
|
-
};
|
|
170
|
-
let inView = false;
|
|
171
|
-
const attemptPlay = () => {
|
|
172
|
-
if (discoverPlaybackSuspended) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
load();
|
|
176
|
-
video.muted = discoverMuted;
|
|
177
|
-
video.play().then(() => {
|
|
178
|
-
delete video.dataset.mutedFallback;
|
|
179
|
-
}).catch(() => {
|
|
180
|
-
if (video.muted) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Unmuted autoplay is blocked until the user interacts with the page.
|
|
184
|
-
video.dataset.mutedFallback = "1";
|
|
185
|
-
video.muted = true;
|
|
186
|
-
video.play().catch(() => { });
|
|
187
|
-
});
|
|
188
|
-
};
|
|
189
|
-
const unmuteOnGesture = () => {
|
|
190
|
-
if (!discoverMuted && video.dataset.mutedFallback === "1" && !video.paused) {
|
|
191
|
-
video.muted = false;
|
|
192
|
-
delete video.dataset.mutedFallback;
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
window.addEventListener("pointerdown", unmuteOnGesture);
|
|
196
|
-
window.addEventListener("keydown", unmuteOnGesture);
|
|
197
|
-
const applyGlobalMute = () => {
|
|
198
|
-
if (discoverMuted) {
|
|
199
|
-
delete video.dataset.mutedFallback;
|
|
200
|
-
video.muted = true;
|
|
201
|
-
}
|
|
202
|
-
else if (!video.paused) {
|
|
203
|
-
// The toggle click is a user gesture, so unmuting live playback is allowed.
|
|
204
|
-
video.muted = false;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const unsubscribeMute = subscribeDiscoverMuted(applyGlobalMute);
|
|
208
|
-
const applySuspend = () => {
|
|
209
|
-
if (discoverPlaybackSuspended) {
|
|
210
|
-
if (!video.paused) {
|
|
211
|
-
video.pause();
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
else if (inView) {
|
|
215
|
-
attemptPlay();
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
discoverSuspendListeners.add(applySuspend);
|
|
219
|
-
const removeListeners = () => {
|
|
220
|
-
window.removeEventListener("pointerdown", unmuteOnGesture);
|
|
221
|
-
window.removeEventListener("keydown", unmuteOnGesture);
|
|
222
|
-
unsubscribeMute();
|
|
223
|
-
discoverSuspendListeners.delete(applySuspend);
|
|
224
|
-
};
|
|
225
|
-
if (!("IntersectionObserver" in window)) {
|
|
226
|
-
inView = true;
|
|
227
|
-
attemptPlay();
|
|
228
|
-
return removeListeners;
|
|
229
|
-
}
|
|
230
|
-
const preloadObserver = new IntersectionObserver((entries) => {
|
|
231
|
-
for (const entry of entries) {
|
|
232
|
-
if (!entry.isIntersecting) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
load();
|
|
236
|
-
preloadObserver.unobserve(entry.target);
|
|
237
|
-
}
|
|
238
|
-
}, {
|
|
239
|
-
rootMargin: "240px 0px"
|
|
240
|
-
});
|
|
241
|
-
const playbackObserver = new IntersectionObserver((entries) => {
|
|
242
|
-
for (const entry of entries) {
|
|
243
|
-
inView = entry.isIntersecting;
|
|
244
|
-
if (entry.isIntersecting) {
|
|
245
|
-
attemptPlay();
|
|
246
|
-
}
|
|
247
|
-
else if (!video.paused) {
|
|
248
|
-
video.pause();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}, {
|
|
252
|
-
threshold: 0.5
|
|
253
|
-
});
|
|
254
|
-
preloadObserver.observe(video);
|
|
255
|
-
playbackObserver.observe(video);
|
|
256
|
-
return () => {
|
|
257
|
-
preloadObserver.disconnect();
|
|
258
|
-
playbackObserver.disconnect();
|
|
259
|
-
removeListeners();
|
|
260
|
-
};
|
|
261
|
-
}, []);
|
|
262
|
-
return _jsx("video", { ref: ref, className: "preview", "data-src": src, preload: "metadata", playsInline: true, loop: true, controls: controls });
|
|
263
|
-
}
|
|
264
|
-
function DiscoverMuteToggle() {
|
|
265
|
-
const muted = useDiscoverMuted();
|
|
266
|
-
return (_jsx("button", { type: "button", className: "discover-mute-toggle", "aria-pressed": muted, "aria-label": muted ? "Unmute video previews" : "Mute video previews", title: muted ? "Unmute previews" : "Mute previews", onClick: () => setDiscoverMuted(!muted), children: muted ? _jsx(SpeakerOffIcon, {}) : _jsx(SpeakerOnIcon, {}) }));
|
|
267
|
-
}
|
|
268
|
-
function CopyInlineButton({ text, label }) {
|
|
269
|
-
const [status, setStatus] = useState("");
|
|
270
|
-
const handleClick = async () => {
|
|
271
|
-
try {
|
|
272
|
-
await navigator.clipboard.writeText(text);
|
|
273
|
-
setStatus("done");
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
setStatus("error");
|
|
277
|
-
}
|
|
278
|
-
window.setTimeout(() => setStatus(""), 1600);
|
|
279
|
-
};
|
|
280
|
-
return (_jsxs("button", { className: "pill-tag copy-inline", type: "button", "data-state": status, "aria-label": status === "done" ? `${label} copied` : label, onClick: () => void handleClick(), children: [_jsx("span", { className: "copy-inline-label", children: label }), _jsx("span", { className: "copy-inline-feedback", "aria-hidden": "true", children: status === "error" ? "Couldn’t copy" : "Copied" })] }));
|
|
281
|
-
}
|
|
282
|
-
function editorHref(templateId, accountUserId) {
|
|
283
|
-
const usesNewEditor = templateId.startsWith("hyperframes:") || templateId.startsWith("template_");
|
|
284
|
-
return usesNewEditor
|
|
285
|
-
? `/editor/${encodeURIComponent(templateId.replace(/^hyperframes:/, ""))}${accountUserId ? `?account=${encodeURIComponent(accountUserId)}` : ""}`
|
|
286
|
-
: withAccountQuery(`/templates/${encodeURIComponent(templateId)}/editor/docs`, accountUserId);
|
|
287
|
-
}
|
|
288
|
-
function CreateButton({ templateId, accountUserId }) {
|
|
289
|
-
return _jsx("a", { className: "cta-button", href: editorHref(templateId, accountUserId), target: "_blank", rel: "noopener", children: "Editor" });
|
|
290
|
-
}
|
|
291
|
-
function CardMenu(input) {
|
|
292
|
-
const { template } = input;
|
|
293
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
294
|
-
const [copied, setCopied] = useState(false);
|
|
295
|
-
const [deleting, setDeleting] = useState(false);
|
|
296
|
-
const menuRef = useRef(null);
|
|
297
|
-
const isPending = template.status === "processing" || template.status === "failed";
|
|
298
|
-
const canDelete = template.visibility === "private" && Boolean(input.onDeleteTemplate);
|
|
299
|
-
useEffect(() => {
|
|
300
|
-
if (!isOpen) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const handleClickOutside = (event) => {
|
|
304
|
-
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
305
|
-
setIsOpen(false);
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
const handleKey = (event) => {
|
|
309
|
-
if (event.key === "Escape")
|
|
310
|
-
setIsOpen(false);
|
|
311
|
-
};
|
|
312
|
-
window.addEventListener("mousedown", handleClickOutside);
|
|
313
|
-
window.addEventListener("keydown", handleKey);
|
|
314
|
-
return () => {
|
|
315
|
-
window.removeEventListener("mousedown", handleClickOutside);
|
|
316
|
-
window.removeEventListener("keydown", handleKey);
|
|
317
|
-
};
|
|
318
|
-
}, [isOpen]);
|
|
319
|
-
const handleCopyId = async () => {
|
|
320
|
-
try {
|
|
321
|
-
await navigator.clipboard.writeText(template.templateId);
|
|
322
|
-
setCopied(true);
|
|
323
|
-
window.setTimeout(() => {
|
|
324
|
-
setCopied(false);
|
|
325
|
-
setIsOpen(false);
|
|
326
|
-
}, 900);
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
setIsOpen(false);
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
const handleDelete = async () => {
|
|
333
|
-
if (!window.confirm(`Delete "${template.title}"? This removes it from your Discover feed permanently.`)) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
setDeleting(true);
|
|
337
|
-
const result = await (input.onDeleteTemplate?.(template.templateId) ?? Promise.resolve({ ok: false, error: "Delete unavailable." }));
|
|
338
|
-
setDeleting(false);
|
|
339
|
-
if (result.ok) {
|
|
340
|
-
setIsOpen(false);
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
window.alert(result.error || "Unable to delete this template.");
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
return (_jsxs("div", { className: "discover-card-menu", ref: menuRef, children: [_jsx("button", { type: "button", className: "discover-card-menu-trigger", "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-label": `More actions for ${template.title}`, title: "More actions", onClick: () => setIsOpen((prev) => !prev), children: _jsx(MoreIcon, {}) }), isOpen ? (_jsxs("div", { className: "discover-card-menu-panel", role: "menu", children: [_jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => void handleCopyId(), children: copied ? "Copied!" : "Copy ID" }), isPending ? (_jsx("span", { className: "discover-card-menu-item is-disabled", role: "menuitem", "aria-disabled": "true", children: "Open in Editor" })) : (_jsx("a", { className: "discover-card-menu-item", role: "menuitem", href: editorHref(template.templateId, input.accountUserId), target: "_blank", rel: "noopener", children: "Open in Editor" })), _jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => {
|
|
347
|
-
input.onToggleBookmark?.(template.templateId);
|
|
348
|
-
setIsOpen(false);
|
|
349
|
-
}, children: input.isBookmarked ? "Remove Bookmark" : "Bookmark" }), canDelete ? (_jsx("button", { type: "button", className: "discover-card-menu-item is-danger", role: "menuitem", disabled: deleting, onClick: () => void handleDelete(), children: deleting ? "Deleting…" : "Delete" })) : null] })) : null] }));
|
|
350
|
-
}
|
|
351
|
-
function BookmarkButton(input) {
|
|
352
|
-
const label = `${input.isBookmarked ? "Remove bookmark for " : "Bookmark "}${input.title}`;
|
|
353
|
-
return (_jsx("button", { className: "bookmark-button", type: "button", "data-active": input.isBookmarked ? "true" : "false", "aria-label": label, "aria-pressed": input.isBookmarked, title: label, onClick: () => input.onToggleBookmark?.(input.templateId), children: _jsx(BookmarkIcon, {}) }));
|
|
354
|
-
}
|
|
355
|
-
function formatDifficulty(difficulty) {
|
|
356
|
-
return difficulty.charAt(0).toUpperCase() + difficulty.slice(1);
|
|
357
|
-
}
|
|
358
|
-
function formatTemplateMeta(template) {
|
|
359
|
-
const layerCount = typeof template.layerCount === "number" ? template.layerCount : null;
|
|
360
|
-
const sourceCount = typeof template.sourceAssetCount === "number" ? template.sourceAssetCount : null;
|
|
361
|
-
const duration = typeof template.durationSeconds === "number" && Number.isFinite(template.durationSeconds)
|
|
362
|
-
? template.durationSeconds >= 60
|
|
363
|
-
? `${Math.floor(template.durationSeconds / 60)}:${String(Math.round(template.durationSeconds % 60)).padStart(2, "0")}`
|
|
364
|
-
: `${template.durationSeconds.toFixed(template.durationSeconds >= 10 ? 1 : 2)}s`
|
|
365
|
-
: null;
|
|
366
|
-
const parts = [
|
|
367
|
-
layerCount === null ? null : `${layerCount} layer${layerCount === 1 ? "" : "s"}`,
|
|
368
|
-
sourceCount === null ? null : `${sourceCount} source${sourceCount === 1 ? "" : "s"}`,
|
|
369
|
-
duration
|
|
370
|
-
].filter(Boolean);
|
|
371
|
-
return parts.length ? parts.join(" / ") : template.previewUrl ? "Draft composition with editable layers." : "Layer recipe with editable timeline.";
|
|
372
|
-
}
|
|
373
|
-
function TemplateCard(input) {
|
|
374
|
-
const { template } = input;
|
|
375
|
-
const isPending = template.status === "processing" || template.status === "failed";
|
|
376
|
-
const media = template.previewUrl
|
|
377
|
-
? isVideoPreview(template.previewUrl)
|
|
378
|
-
? _jsx(LazyVideo, { src: template.previewUrl, controls: false })
|
|
379
|
-
: _jsx("img", { className: "preview", src: template.previewUrl, alt: `${template.title} preview`, loading: "lazy", decoding: "async" })
|
|
380
|
-
: _jsx("div", { className: "preview preview-empty", children: template.status === "processing" ? "Downloading…" : "No preview" });
|
|
381
|
-
return (_jsxs("li", { className: "discover-card row", "data-template-id": template.templateId, children: [template.previewUrl ? (_jsxs("button", { className: "discover-card-media discover-card-media-button", type: "button", "aria-label": `Open ${template.title} preview`, onClick: () => input.onOpenPreview?.(template), children: [media, _jsx("span", { className: "discover-card-media-affordance", "aria-hidden": "true", children: "View" })] })) : (_jsx("div", { className: "discover-card-media", children: media })), _jsxs("div", { className: "discover-card-copy", children: [_jsxs("div", { className: "discover-card-head", children: [_jsxs("div", { className: "discover-card-title-wrap", children: [_jsx("h2", { children: template.title }), _jsx("p", { className: "discover-card-meta", children: formatTemplateMeta(template) })] }), _jsxs("div", { className: "discover-card-actions", children: [_jsx(BookmarkButton, { templateId: template.templateId, title: template.title, isBookmarked: input.isBookmarked, onToggleBookmark: input.onToggleBookmark }), isPending ? (_jsx("span", { className: "cta-button discover-card-cta-disabled", "aria-disabled": "true", children: template.status === "failed" ? "Failed" : "Preparing…" })) : (_jsx(CreateButton, { templateId: template.templateId, accountUserId: input.accountUserId })), _jsx(CardMenu, { template: template, isBookmarked: input.isBookmarked, onToggleBookmark: input.onToggleBookmark, onDeleteTemplate: input.onDeleteTemplate, accountUserId: input.accountUserId })] })] }), _jsx("div", { className: "discover-card-body", children: _jsx("p", { children: String(template.viralDna || "").trim() || "No layer strategy provided for this composition yet." }) }), _jsxs("div", { className: "discover-card-tags", children: [template.origin === "raw" ? (_jsx("div", { className: "pill-tag origin-pill", title: "Your own project \u2014 created from a prompt or your raw clips, not an imported inspiration", children: "Your project" })) : null, template.visibility === "private" ? (_jsx("div", { className: "pill-tag private-pill", title: "Only visible to your account", children: "Only you" })) : null, template.status === "processing" ? (_jsx("div", { className: "pill-tag processing-pill", children: "Processing" })) : template.status === "failed" ? (_jsx("div", { className: "pill-tag failed-pill", children: "Download failed" })) : null, _jsx("div", { className: "pill-tag difficulty-pill", "data-difficulty": template.difficulty, children: formatDifficulty(template.difficulty) }), _jsx("div", { className: "pill-tag", children: template.sourceType || (isVideoPreview(template.previewUrl) ? "Video source" : "Image sequence") }), _jsx(CopyInlineButton, { text: template.templateId, label: template.templateId })] })] })] }));
|
|
382
|
-
}
|
|
383
|
-
function PreviewModal(input) {
|
|
384
|
-
const { onClose, template } = input;
|
|
385
|
-
const previewUrl = template.previewUrl || "";
|
|
386
|
-
const isVideo = isVideoPreview(previewUrl);
|
|
387
|
-
const [copyStatus, setCopyStatus] = useState("");
|
|
388
|
-
const muted = useDiscoverMuted();
|
|
389
|
-
useEffect(() => {
|
|
390
|
-
setDiscoverPlaybackSuspended(true);
|
|
391
|
-
return () => setDiscoverPlaybackSuspended(false);
|
|
392
|
-
}, []);
|
|
393
|
-
const handleCopyLink = async () => {
|
|
394
|
-
try {
|
|
395
|
-
await navigator.clipboard.writeText(previewUrl);
|
|
396
|
-
setCopyStatus("done");
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
399
|
-
setCopyStatus("error");
|
|
400
|
-
}
|
|
401
|
-
window.setTimeout(() => setCopyStatus(""), 1600);
|
|
402
|
-
};
|
|
403
|
-
useEffect(() => {
|
|
404
|
-
const handleKeyDown = (event) => {
|
|
405
|
-
if (event.key === "Escape") {
|
|
406
|
-
onClose();
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
const previousOverflow = document.body.style.overflow;
|
|
410
|
-
document.body.style.overflow = "hidden";
|
|
411
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
412
|
-
return () => {
|
|
413
|
-
document.body.style.overflow = previousOverflow;
|
|
414
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
415
|
-
};
|
|
416
|
-
}, [onClose]);
|
|
417
|
-
return (_jsx("div", { className: "discover-media-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "discover-media-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "discover-media-modal-title", onClick: (event) => event.stopPropagation(), children: [_jsxs("div", { className: "discover-media-modal-head", children: [_jsx("h2", { id: "discover-media-modal-title", children: template.title }), _jsxs("div", { className: "discover-media-modal-actions", children: [_jsx("a", { className: "discover-media-modal-action", href: previewUrl, download: true, "aria-label": `Download ${template.title} preview`, title: "Download", onClick: (event) => event.stopPropagation(), children: _jsx(DownloadIcon, {}) }), _jsx("button", { className: "discover-media-modal-action", type: "button", "data-state": copyStatus, "aria-label": copyStatus === "done" ? "Media URL copied" : copyStatus === "error" ? "Unable to copy media URL" : `Copy ${template.title} preview URL`, title: copyStatus === "done" ? "Copied" : copyStatus === "error" ? "Copy failed" : "Copy link", onClick: () => void handleCopyLink(), children: _jsx(LinkIcon, {}) }), _jsx("button", { className: "discover-media-modal-action", type: "button", "aria-label": "Close preview", title: "Close", onClick: onClose, children: _jsx(CloseIcon, {}) })] })] }), _jsx("div", { className: "discover-media-modal-stage", children: isVideo ? (_jsx("video", { src: previewUrl, controls: true, autoPlay: true, playsInline: true, preload: "metadata", muted: muted, onVolumeChange: (event) => {
|
|
418
|
-
// The modal player's mute button doubles as the page-wide toggle.
|
|
419
|
-
const nowMuted = event.currentTarget.muted;
|
|
420
|
-
if (nowMuted !== discoverMuted) {
|
|
421
|
-
setDiscoverMuted(nowMuted);
|
|
422
|
-
}
|
|
423
|
-
} })) : (_jsx("img", { src: previewUrl, alt: `${template.title} preview` })) })] }) }));
|
|
424
|
-
}
|
|
425
|
-
function AddTemplateModal(input) {
|
|
426
|
-
const { onClose } = input;
|
|
427
|
-
const [sourceUrl, setSourceUrl] = useState("");
|
|
428
|
-
const [file, setFile] = useState(null);
|
|
429
|
-
const [title, setTitle] = useState("");
|
|
430
|
-
const [tagline, setTagline] = useState("");
|
|
431
|
-
const [notes, setNotes] = useState("");
|
|
432
|
-
const [submitting, setSubmitting] = useState(false);
|
|
433
|
-
const [error, setError] = useState(null);
|
|
434
|
-
useEffect(() => {
|
|
435
|
-
const handleKeyDown = (event) => {
|
|
436
|
-
if (event.key === "Escape") {
|
|
437
|
-
onClose();
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
const previousOverflow = document.body.style.overflow;
|
|
441
|
-
document.body.style.overflow = "hidden";
|
|
442
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
443
|
-
return () => {
|
|
444
|
-
document.body.style.overflow = previousOverflow;
|
|
445
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
446
|
-
};
|
|
447
|
-
}, [onClose]);
|
|
448
|
-
const handleSubmit = async () => {
|
|
449
|
-
const trimmedUrl = sourceUrl.trim();
|
|
450
|
-
if (!trimmedUrl && !file) {
|
|
451
|
-
setError("Paste a TikTok, YouTube, Twitter/X, or Instagram video URL — or upload a video file.");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (file && file.size > 200 * 1024 * 1024) {
|
|
455
|
-
setError("Video uploads can be at most 200 MB.");
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
setSubmitting(true);
|
|
459
|
-
setError(null);
|
|
460
|
-
const result = await (input.onAddTemplate?.({ sourceUrl: file ? "" : trimmedUrl, file, title: title.trim(), tagline: tagline.trim(), notes: notes.trim() })
|
|
461
|
-
?? Promise.resolve({ ok: false, error: "Adding templates is unavailable right now." }));
|
|
462
|
-
setSubmitting(false);
|
|
463
|
-
if (result.ok) {
|
|
464
|
-
onClose();
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
setError(result.error || "Unable to add that video. Try again.");
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
return (_jsx("div", { className: "discover-media-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "discover-media-modal discover-add-template-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "discover-add-template-title", onClick: (event) => event.stopPropagation(), children: [_jsxs("div", { className: "discover-media-modal-head", children: [_jsx("h2", { id: "discover-add-template-title", children: "Add Template" }), _jsx("div", { className: "discover-media-modal-actions", children: _jsx("button", { className: "discover-media-modal-action", type: "button", "aria-label": "Close", title: "Close", onClick: onClose, children: _jsx(CloseIcon, {}) }) })] }), _jsxs("form", { className: "discover-add-template-form", onSubmit: (event) => {
|
|
471
|
-
event.preventDefault();
|
|
472
|
-
void handleSubmit();
|
|
473
|
-
}, children: [_jsx("p", { className: "discover-add-template-hint", children: "Paste a link to a TikTok, YouTube, Twitter/X, or Instagram video \u2014 or upload a video file from your computer. We add it to your Discover feed, visible only to your account." }), _jsxs("div", { className: "discover-add-template-field", children: [_jsx("label", { className: "label", htmlFor: "add-template-url", children: "Video URL" }), _jsx("input", { id: "add-template-url", type: "url", required: !file, disabled: Boolean(file), placeholder: "https://www.tiktok.com/@creator/video/\u2026", autoComplete: "off", value: sourceUrl, onChange: (event) => setSourceUrl(event.target.value) })] }), _jsxs("div", { className: "discover-add-template-field", children: [_jsxs("label", { className: "label", htmlFor: "add-template-file", children: ["Or upload a video ", _jsx("span", { className: "discover-add-template-optional", children: "MP4 / MOV / WebM, up to 200 MB" })] }), file ? (_jsxs("div", { className: "discover-add-template-file-chip", children: [_jsx("span", { className: "discover-add-template-file-name", children: file.name }), _jsxs("span", { className: "discover-add-template-file-size", children: [(file.size / (1024 * 1024)).toFixed(1), " MB"] }), _jsx("button", { type: "button", className: "discover-add-template-file-clear", "aria-label": "Remove selected file", onClick: () => setFile(null), children: "\u2715" })] })) : (_jsx("input", { id: "add-template-file", type: "file", accept: "video/mp4,video/quicktime,video/webm,.mp4,.m4v,.mov,.webm", onChange: (event) => {
|
|
474
|
-
const selected = event.target.files?.[0] ?? null;
|
|
475
|
-
if (selected) {
|
|
476
|
-
setFile(selected);
|
|
477
|
-
setSourceUrl("");
|
|
478
|
-
setError(null);
|
|
479
|
-
}
|
|
480
|
-
} }))] }), file ? (_jsxs("div", { className: "discover-add-template-field", children: [_jsxs("label", { className: "label", htmlFor: "add-template-title", children: ["Title ", _jsx("span", { className: "discover-add-template-optional", children: "optional \u2014 defaults to the template id" })] }), _jsx("input", { id: "add-template-title", type: "text", placeholder: "Name this template\u2026", autoComplete: "off", maxLength: 140, value: title, onChange: (event) => setTitle(event.target.value) })] })) : null, _jsxs("div", { className: "discover-add-template-field", children: [_jsxs("label", { className: "label", htmlFor: "add-template-tagline", children: ["Tagline ", _jsx("span", { className: "discover-add-template-optional", children: "optional" })] }), _jsx("input", { id: "add-template-tagline", type: "text", placeholder: "What makes this video work?", autoComplete: "off", maxLength: 140, value: tagline, onChange: (event) => setTagline(event.target.value) })] }), _jsxs("div", { className: "discover-add-template-field", children: [_jsxs("label", { className: "label", htmlFor: "add-template-notes", children: ["Notes ", _jsx("span", { className: "discover-add-template-optional", children: "optional" })] }), _jsx("textarea", { id: "add-template-notes", rows: 4, placeholder: "Ideas for how you want to remix this\u2026", maxLength: 2000, value: notes, onChange: (event) => setNotes(event.target.value) })] }), error ? _jsx("p", { className: "discover-add-template-error", role: "alert", children: error }) : null, _jsxs("div", { className: "toolbar discover-add-template-actions", children: [_jsx("button", { type: "button", className: "secondary", onClick: onClose, disabled: submitting, children: "Cancel" }), _jsx("button", { type: "submit", className: "cta-button discover-add-template-submit", disabled: submitting, children: submitting ? (file ? "Uploading…" : "Adding…") : "Add Template" })] })] })] }) }));
|
|
481
|
-
}
|
|
482
|
-
// The curated "Popular" picks. They are real public templates (seeded once from
|
|
483
|
-
// POPULAR_INSPIRATION_GROUPS), so they render with the exact same TemplateCard
|
|
484
|
-
// as the Feed — inline autoplay video that obeys the page mute toggle — just
|
|
485
|
-
// grouped under their editorial category headings and ordered by the server.
|
|
486
|
-
function PopularTemplates(input) {
|
|
487
|
-
if (input.loading) {
|
|
488
|
-
return _jsx("ul", { className: "discover-list", children: _jsx("li", { className: "empty-state discover-loading-card", children: "Loading categories\u2026" }) });
|
|
489
|
-
}
|
|
490
|
-
if (input.error) {
|
|
491
|
-
return _jsx("ul", { className: "discover-list", children: _jsx("li", { className: "empty-state discover-loading-card", children: input.error }) });
|
|
492
|
-
}
|
|
493
|
-
const query = input.searchQuery.trim().toLowerCase();
|
|
494
|
-
const filtered = query
|
|
495
|
-
? input.templates.filter((template) => `${template.title} ${template.templateId} ${template.popularGroup ?? ""}`.toLowerCase().includes(query))
|
|
496
|
-
: input.templates;
|
|
497
|
-
if (filtered.length === 0) {
|
|
498
|
-
return (_jsx("ul", { className: "discover-list", children: _jsx("li", { className: "empty-state discover-loading-card", children: query ? "No categories match your search." : "No categories yet." }) }));
|
|
499
|
-
}
|
|
500
|
-
// Group by popularGroup, preserving the server's rank order (already sorted).
|
|
501
|
-
const groups = [];
|
|
502
|
-
for (const template of filtered) {
|
|
503
|
-
const label = (template.popularGroup || "Popular").trim() || "Popular";
|
|
504
|
-
let group = groups.find((entry) => entry.label === label);
|
|
505
|
-
if (!group) {
|
|
506
|
-
group = { label, items: [] };
|
|
507
|
-
groups.push(group);
|
|
508
|
-
}
|
|
509
|
-
group.items.push(template);
|
|
510
|
-
}
|
|
511
|
-
return (_jsx("div", { className: "discover-popular", children: groups.map((group) => (_jsxs("section", { className: "discover-popular-group", children: [_jsx("h3", { className: "discover-popular-group-label", children: group.label }), _jsx("ul", { className: "discover-list", children: group.items.map((template) => (_jsx(TemplateCard, { template: template, isBookmarked: input.bookmarks.has(template.templateId), onToggleBookmark: input.onToggleBookmark, onOpenPreview: input.onOpenPreview, onDeleteTemplate: input.onDeleteTemplate, accountUserId: input.accountUserId }, template.templateId))) })] }, group.label))) }));
|
|
512
|
-
}
|
|
513
|
-
// Tinder-style deck of the customer's own product, auto-recut from decomposed
|
|
514
|
-
// templates. Previews are LIVE HyperFrames compositions in an iframe (no baked
|
|
515
|
-
// MP4) — only the top two cards mount an iframe (the concurrent-decoder ceiling);
|
|
516
|
-
// the rest are static placeholders. Drag or tap the buttons to approve/reject.
|
|
517
|
-
function SwipeDeck(input) {
|
|
518
|
-
const [drag, setDrag] = useState(null);
|
|
519
|
-
const [flyOut, setFlyOut] = useState(null);
|
|
520
|
-
const [busy, setBusy] = useState(false);
|
|
521
|
-
const [actionError, setActionError] = useState(null);
|
|
522
|
-
// Deck previews start muted (the only autoplay browsers allow); this toggle
|
|
523
|
-
// is the user gesture that unlocks audio. The same-origin preview iframes
|
|
524
|
-
// poll these window flags (see injectSwipePreviewFit) — the top-card id gate
|
|
525
|
-
// keeps the preloaded second iframe silent so audio never doubles.
|
|
526
|
-
const [soundOn, setSoundOn] = useState(false);
|
|
527
|
-
const topCardId = input.stack.length ? input.stack[0].id : null;
|
|
528
|
-
useEffect(() => {
|
|
529
|
-
window.__VF_SWIPE_SOUND__ = soundOn;
|
|
530
|
-
window.__VF_SWIPE_SOUND_TOP__ = topCardId;
|
|
531
|
-
return () => {
|
|
532
|
-
window.__VF_SWIPE_SOUND__ = false;
|
|
533
|
-
window.__VF_SWIPE_SOUND_TOP__ = null;
|
|
534
|
-
};
|
|
535
|
-
}, [soundOn, topCardId]);
|
|
536
|
-
const startRef = useRef(null);
|
|
537
|
-
const THRESHOLD = 110;
|
|
538
|
-
const decide = (card, dir) => {
|
|
539
|
-
if (!card || busy)
|
|
540
|
-
return;
|
|
541
|
-
setBusy(true);
|
|
542
|
-
setActionError(null);
|
|
543
|
-
setFlyOut({ id: card.id, dir });
|
|
544
|
-
// Let the fly-out animation play, then commit (parent removes the card and
|
|
545
|
-
// refills the deck).
|
|
546
|
-
window.setTimeout(async () => {
|
|
547
|
-
const action = dir === "right" ? input.onApprove : input.onReject;
|
|
548
|
-
try {
|
|
549
|
-
const result = await action?.(card.id);
|
|
550
|
-
if (result && !result.ok)
|
|
551
|
-
setActionError(result.error ?? "Action failed.");
|
|
552
|
-
}
|
|
553
|
-
finally {
|
|
554
|
-
setFlyOut(null);
|
|
555
|
-
setDrag(null);
|
|
556
|
-
startRef.current = null;
|
|
557
|
-
setBusy(false);
|
|
558
|
-
}
|
|
559
|
-
}, 260);
|
|
560
|
-
};
|
|
561
|
-
if (!input.isLoggedIn) {
|
|
562
|
-
return (_jsxs("div", { className: "swipe-empty", children: [_jsx("p", { className: "swipe-empty-title", children: "Swipe is for your product" }), _jsx("p", { className: "swipe-empty-sub", children: "Log in to auto-generate ads from viral templates and swipe to approve." }), _jsx("a", { className: "swipe-empty-cta", href: "/login", children: "Log in" })] }));
|
|
563
|
-
}
|
|
564
|
-
if (!input.productSet) {
|
|
565
|
-
return (_jsxs("div", { className: "swipe-empty", children: [_jsx("p", { className: "swipe-empty-title", children: "Tell us about your product" }), _jsx("p", { className: "swipe-empty-sub", children: "Swipe auto-recuts viral templates into ads for your product. Add a product / offer description in Settings to start the deck." }), _jsx("a", { className: "swipe-empty-cta", href: "/settings", children: "Open Settings" })] }));
|
|
566
|
-
}
|
|
567
|
-
if (input.error) {
|
|
568
|
-
return (_jsxs("div", { className: "swipe-empty", children: [_jsx("p", { className: "swipe-empty-title", children: "Couldn't load your deck" }), _jsx("p", { className: "swipe-empty-sub", children: input.error })] }));
|
|
569
|
-
}
|
|
570
|
-
if (input.stack.length === 0) {
|
|
571
|
-
if (input.loading || input.customizing > 0 || input.refilling) {
|
|
572
|
-
return (_jsxs("div", { className: "swipe-empty", children: [_jsx("div", { className: "swipe-spinner", "aria-hidden": "true" }), _jsx("p", { className: "swipe-empty-title", children: "Customizing fresh clips\u2026" }), _jsx("p", { className: "swipe-empty-sub", children: "Recutting viral templates into ads for your product." })] }));
|
|
573
|
-
}
|
|
574
|
-
return (_jsxs("div", { className: "swipe-empty", children: [_jsx("p", { className: "swipe-empty-title", children: "You're all caught up" }), _jsx("p", { className: "swipe-empty-sub", children: "No more templates to customize right now. Check back as new ones are added." })] }));
|
|
575
|
-
}
|
|
576
|
-
const visible = input.stack.slice(0, 4);
|
|
577
|
-
const top = visible[0];
|
|
578
|
-
return (_jsxs("div", { className: "swipe-deck-wrap", children: [_jsxs("div", { className: "swipe-deck-row", children: [_jsx("button", { type: "button", className: "swipe-action swipe-action-reject", disabled: busy, onClick: () => decide(top, "left"), "aria-label": "Reject", children: "\u2715" }), _jsxs("div", { className: "swipe-deck", children: [_jsx("button", { type: "button", className: "swipe-sound", onClick: () => setSoundOn((value) => !value), "aria-label": soundOn ? "Mute preview" : "Play preview with sound", "aria-pressed": soundOn, title: soundOn ? "Mute" : "Sound on", children: soundOn ? "🔊" : "🔇" }), visible.map((card, index) => {
|
|
579
|
-
const isTop = index === 0;
|
|
580
|
-
const isFlying = flyOut?.id === card.id;
|
|
581
|
-
const isDragging = isTop && drag?.id === card.id;
|
|
582
|
-
const useIframe = index <= 1 && Boolean(card.previewUrl);
|
|
583
|
-
const dx = isDragging ? drag.dx : 0;
|
|
584
|
-
const dy = isDragging ? drag.dy : 0;
|
|
585
|
-
let transform;
|
|
586
|
-
let transition;
|
|
587
|
-
if (isFlying) {
|
|
588
|
-
transform = `translate(${flyOut.dir === "right" ? 130 : -130}%, ${dy}px) rotate(${flyOut.dir === "right" ? 22 : -22}deg)`;
|
|
589
|
-
transition = "transform 0.26s ease-in";
|
|
590
|
-
}
|
|
591
|
-
else if (isTop) {
|
|
592
|
-
transform = `translate(${dx}px, ${dy * 0.25}px) rotate(${dx / 22}deg)`;
|
|
593
|
-
transition = isDragging ? "none" : "transform 0.2s ease";
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
transform = `translateY(${index * 12}px) scale(${1 - index * 0.04})`;
|
|
597
|
-
transition = "transform 0.2s ease";
|
|
598
|
-
}
|
|
599
|
-
const hint = isDragging ? (drag.dx > 40 ? "approve" : drag.dx < -40 ? "reject" : null) : null;
|
|
600
|
-
return (_jsxs("div", { className: "swipe-card", style: { transform, transition, zIndex: 100 - index }, onPointerDown: isTop && !busy ? (event) => {
|
|
601
|
-
startRef.current = { x: event.clientX, y: event.clientY };
|
|
602
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
603
|
-
} : undefined, onPointerMove: isTop && !busy ? (event) => {
|
|
604
|
-
if (!startRef.current)
|
|
605
|
-
return;
|
|
606
|
-
setDrag({ id: card.id, dx: event.clientX - startRef.current.x, dy: event.clientY - startRef.current.y });
|
|
607
|
-
} : undefined, onPointerUp: isTop && !busy ? () => {
|
|
608
|
-
const moved = drag?.id === card.id ? drag.dx : 0;
|
|
609
|
-
startRef.current = null;
|
|
610
|
-
if (moved > THRESHOLD)
|
|
611
|
-
decide(card, "right");
|
|
612
|
-
else if (moved < -THRESHOLD)
|
|
613
|
-
decide(card, "left");
|
|
614
|
-
else
|
|
615
|
-
setDrag(null);
|
|
616
|
-
} : undefined, onPointerCancel: isTop ? () => { startRef.current = null; setDrag(null); } : undefined, children: [useIframe ? (_jsx("iframe", { className: "swipe-card-preview", src: card.previewUrl ?? undefined, title: card.title, loading: "lazy", scrolling: "no", allow: "autoplay", style: { pointerEvents: "none" } })) : (_jsx("div", { className: "swipe-card-fallback", children: _jsx("span", { children: card.title }) })), _jsxs("div", { className: "swipe-card-overlay", children: [_jsx("div", { className: "swipe-card-title", children: card.title }), card.caption ? _jsx("div", { className: "swipe-card-caption", children: card.caption }) : null] }), hint === "approve" ? _jsx("div", { className: "swipe-stamp swipe-stamp-yes", children: "Approve" }) : null, hint === "reject" ? _jsx("div", { className: "swipe-stamp swipe-stamp-no", children: "Reject" }) : null] }, card.id));
|
|
617
|
-
})] }), _jsx("button", { type: "button", className: "swipe-action swipe-action-approve", disabled: busy, onClick: () => decide(top, "right"), "aria-label": "Approve", children: "\u2713" })] }), _jsx("div", { className: "swipe-status", children: input.customizing > 0 || input.refilling ? "Customizing more…" : `${input.stack.length} ready` }), actionError ? _jsx("div", { className: "swipe-action-error", children: actionError }) : null] }));
|
|
618
|
-
}
|
|
619
|
-
export function HomepageShell(input) {
|
|
620
|
-
const showEmpty = !input.loading && !input.error && input.templates.length > 0 && input.visibleTemplates.length === 0;
|
|
621
|
-
const [previewTemplate, setPreviewTemplate] = useState(null);
|
|
622
|
-
const [isAddTemplateOpen, setIsAddTemplateOpen] = useState(false);
|
|
623
|
-
// "feed" = the live inspirations catalog (continuously grown); "popular" = the
|
|
624
|
-
// curated static picks fetched from /discover/popular. View state lives in the
|
|
625
|
-
// store so the client can lazily load the Popular set on first open.
|
|
626
|
-
const isPopular = input.view === "popular";
|
|
627
|
-
const isSwipe = input.view === "swipe";
|
|
628
|
-
const setView = (value) => input.onViewChange?.(value);
|
|
629
|
-
const searchPlaceholder = isPopular
|
|
630
|
-
? "Search categories"
|
|
631
|
-
: input.loading || input.error
|
|
632
|
-
? "Search Templates"
|
|
633
|
-
: `Search ${input.visibleTemplates.length} Template${input.visibleTemplates.length === 1 ? "" : "s"}`;
|
|
634
|
-
// Chat rail is always visible on /discover so logged-out visitors see the copilot
|
|
635
|
-
// affordance and understand what unlocks after login. When logged in, the real chat
|
|
636
|
-
// React tree portals into the [data-template-editor-chat-root] element (see
|
|
637
|
-
// page-runtime-client). When logged out, we render an inert placeholder with a
|
|
638
|
-
// disabled textarea and a "Log in to chat" CTA in the same slot.
|
|
639
|
-
const hasChatRail = true;
|
|
640
|
-
const isChatEnabled = input.account.isLoggedIn;
|
|
641
|
-
return (_jsxs("section", { className: `frame discover-frame${hasChatRail ? " has-chat-rail" : ""}`, children: [_jsxs("header", { className: "topbar", children: [_jsx(BrandLockup, { account: input.account }), _jsx("div", { className: "topbar-actions", children: _jsx(PrimaryNav, { account: input.account }) })] }), _jsx("div", { className: "frame-body", children: _jsxs("section", { className: "discover-shell", children: [_jsxs("div", { className: "discover-filter-bar", role: "toolbar", "aria-label": "Composition filters", children: [_jsxs("div", { className: "view-toggle discover-view-toggle", role: "tablist", "aria-label": "Discover view", children: [_jsx("button", { type: "button", className: `view-toggle-tab${!isPopular && !isSwipe ? " is-active" : ""}`, role: "tab", "aria-selected": !isPopular && !isSwipe, onClick: () => setView("feed"), children: "Feed" }), _jsx("button", { type: "button", className: `view-toggle-tab${isSwipe ? " is-active" : ""}`, role: "tab", "aria-selected": isSwipe, onClick: () => setView("swipe"), children: "Swipe" }), _jsx("button", { type: "button", className: `view-toggle-tab${isPopular ? " is-active" : ""}`, role: "tab", "aria-selected": isPopular, onClick: () => setView("popular"), children: "Category" })] }), _jsx(DiscoverMuteToggle, {}), _jsxs("div", { className: "discover-filter-search search-field", children: [_jsx("span", { className: "search-icon", "aria-hidden": "true", children: _jsx(SearchIcon, {}) }), _jsx("input", { id: "search", type: "search", placeholder: searchPlaceholder, autoComplete: "off", value: input.searchQuery, onChange: (event) => input.onSearchQueryChange?.(event.target.value) })] }), !isPopular && !isSwipe ? (_jsxs("label", { className: "discover-filter-toggle", htmlFor: "bookmarks-only", "data-active": input.bookmarksOnly ? "true" : "false", children: [_jsx("span", { className: "discover-filter-toggle-label", children: "Saved" }), _jsxs("span", { className: "switch-shell", "data-active": input.bookmarksOnly ? "true" : "false", children: [_jsx("input", { id: "bookmarks-only", className: "switch-input", type: "checkbox", checked: input.bookmarksOnly, onChange: (event) => input.onBookmarksOnlyChange?.(event.target.checked) }), _jsx("span", { className: "switch-knob" })] })] })) : null, input.account.isLoggedIn ? (_jsx("button", { type: "button", className: "discover-add-template-button", onClick: () => setIsAddTemplateOpen(true), children: "Add Template" })) : (_jsx("a", { className: "discover-add-template-button", href: "/login", children: "Add Template" }))] }), _jsx("section", { className: "discover-results", children: _jsx("div", { className: "discover-results-scroll", children: _jsx("div", { className: "discover-list-scroll", children: isSwipe ? (_jsx(SwipeDeck, { stack: input.swipeStack, loading: input.swipeLoading, loaded: input.swipeLoaded, error: input.swipeError, customizing: input.swipeCustomizing, targetDepth: input.swipeTargetDepth, productSet: input.swipeProductSet, refilling: input.swipeRefilling, isLoggedIn: input.account.isLoggedIn, onApprove: input.onSwipeApprove, onReject: input.onSwipeReject })) : isPopular ? (_jsx(PopularTemplates, { templates: input.popularTemplates, loading: input.popularLoading, error: input.popularError, searchQuery: input.searchQuery, bookmarks: input.bookmarks, onToggleBookmark: input.onToggleBookmark, onOpenPreview: setPreviewTemplate, onDeleteTemplate: input.onDeleteTemplate, accountUserId: input.account.userId })) : (_jsxs(_Fragment, { children: [_jsx("ul", { className: "discover-list", id: "template-list", children: input.loading ? (_jsx("li", { className: "empty-state discover-loading-card", children: "Loading compositions..." })) : input.error ? (_jsx("li", { className: "empty-state discover-loading-card", children: "Unable to load compositions." })) : input.templates.length === 0 ? (_jsx("li", { className: "empty-state discover-loading-card", children: "No compositions available." })) : (input.visibleTemplates.map((template) => (_jsx(TemplateCard, { template: template, isBookmarked: input.bookmarks.has(template.templateId), onToggleBookmark: input.onToggleBookmark, onOpenPreview: setPreviewTemplate, onDeleteTemplate: input.onDeleteTemplate, accountUserId: input.account.userId }, template.templateId)))) }), _jsx("div", { className: "empty-state empty", id: "empty-state", "data-visible": showEmpty ? "true" : "false", children: "No compositions match the current search and filter combination." })] })) }) }) })] }) }), hasChatRail ? (_jsx("aside", { className: "editor-right-rail", "aria-label": "Discover copilot", children: isChatEnabled ? (_jsx("div", { className: "editor-chat-panel", "data-template-editor-chat-root": true })) : (_jsxs(_Fragment, { children: [_jsx("a", { className: "vf-editor-chat-mobile-fab", href: "/login", "aria-label": "Log in to chat with Vidfarm", children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M20 11.5a7.5 7.5 0 0 1-9.8 7.1L4 20l1.5-5.3A7.5 7.5 0 1 1 20 11.5Z" }) }) }), _jsxs("div", { className: "editor-chat-panel editor-chat-panel--logged-out", style: {
|
|
642
|
-
display: "flex",
|
|
643
|
-
flexDirection: "column",
|
|
644
|
-
height: "100%",
|
|
645
|
-
padding: "20px",
|
|
646
|
-
gap: "16px",
|
|
647
|
-
boxSizing: "border-box"
|
|
648
|
-
}, children: [_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [_jsx("span", { className: "label", style: {
|
|
649
|
-
fontSize: "0.68rem",
|
|
650
|
-
fontWeight: 800,
|
|
651
|
-
letterSpacing: "0.14em",
|
|
652
|
-
textTransform: "uppercase",
|
|
653
|
-
color: "#a9884a"
|
|
654
|
-
}, children: "Vidfarm copilot" }), _jsx("h3", { style: { margin: 0, fontSize: "1.05rem", color: "#2f3748" }, children: "Chat with Vidfarm" }), _jsx("p", { style: { margin: 0, fontSize: "0.9rem", color: "#74829c" }, children: "Ask for hooks, angles, and remixes tailored to compositions in your library." })] }), _jsx("div", { style: {
|
|
655
|
-
display: "flex",
|
|
656
|
-
flexDirection: "column",
|
|
657
|
-
gap: "8px",
|
|
658
|
-
marginTop: "14px"
|
|
659
|
-
}, "aria-hidden": "true", children: [
|
|
660
|
-
"Give me 10 hooks for this coffee-shop template",
|
|
661
|
-
"Which awareness stage fits my product?",
|
|
662
|
-
"Remix this into an ad for my brand"
|
|
663
|
-
].map((prompt) => (_jsxs("span", { style: {
|
|
664
|
-
display: "inline-flex",
|
|
665
|
-
alignItems: "center",
|
|
666
|
-
gap: "8px",
|
|
667
|
-
padding: "10px 14px",
|
|
668
|
-
borderRadius: "14px",
|
|
669
|
-
border: "1px dashed rgba(191, 164, 109, 0.45)",
|
|
670
|
-
background: "rgba(250, 245, 234, 0.65)",
|
|
671
|
-
color: "#74829c",
|
|
672
|
-
fontSize: "0.85rem",
|
|
673
|
-
lineHeight: 1.35
|
|
674
|
-
}, children: [_jsx("span", { style: { color: "#bfa46d", fontWeight: 700 }, children: "\u201C" }), prompt] }, prompt))) }), _jsxs("div", { style: {
|
|
675
|
-
marginTop: "auto",
|
|
676
|
-
display: "flex",
|
|
677
|
-
flexDirection: "column",
|
|
678
|
-
gap: "10px"
|
|
679
|
-
}, children: [_jsx("textarea", { rows: 3, placeholder: "Log in to chat with Vidfarm\u2026", disabled: true, "aria-label": "Chat with Vidfarm (log in to enable)", style: {
|
|
680
|
-
width: "100%",
|
|
681
|
-
resize: "none",
|
|
682
|
-
padding: "10px 12px",
|
|
683
|
-
borderRadius: "10px",
|
|
684
|
-
border: "1px solid rgba(191, 164, 109, 0.36)",
|
|
685
|
-
background: "rgba(250, 245, 234, 0.9)",
|
|
686
|
-
color: "#74829c",
|
|
687
|
-
fontFamily: "inherit",
|
|
688
|
-
fontSize: "0.9rem",
|
|
689
|
-
cursor: "not-allowed",
|
|
690
|
-
boxSizing: "border-box"
|
|
691
|
-
} }), _jsx("a", { href: "/login", style: {
|
|
692
|
-
display: "inline-flex",
|
|
693
|
-
alignItems: "center",
|
|
694
|
-
justifyContent: "center",
|
|
695
|
-
minHeight: "38px",
|
|
696
|
-
padding: "0 16px",
|
|
697
|
-
borderRadius: "999px",
|
|
698
|
-
background: "#bfa46d",
|
|
699
|
-
color: "#1a1409",
|
|
700
|
-
fontWeight: 700,
|
|
701
|
-
fontSize: "0.9rem",
|
|
702
|
-
textDecoration: "none"
|
|
703
|
-
}, children: "Log in to chat" })] })] })] })) })) : null, previewTemplate ? _jsx(PreviewModal, { template: previewTemplate, onClose: () => setPreviewTemplate(null) }) : null, isAddTemplateOpen ? (_jsx(AddTemplateModal, { onClose: () => setIsAddTemplateOpen(false), onAddTemplate: input.onAddTemplate })) : null] }));
|
|
704
|
-
}
|
|
705
|
-
//# sourceMappingURL=homepage-view.js.map
|