@mevdragon/vidfarm-devcli 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +1184 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
- package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +15 -0
- package/dist/src/account-pages-legacy.js +9396 -0
- package/dist/src/account-pages.js +61 -0
- package/dist/src/app.js +14378 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/composition-runtime.js +613 -0
- package/dist/src/config.js +166 -0
- package/dist/src/context.js +447 -0
- package/dist/src/dev-app-legacy.js +739 -0
- package/dist/src/dev-app.js +6 -0
- package/dist/src/domain.js +2 -0
- package/dist/src/editor-chat-history.js +82 -0
- package/dist/src/editor-chat.js +449 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/frontend/debug.js +71 -0
- package/dist/src/frontend/flockposter-cache-store.js +124 -0
- package/dist/src/frontend/homepage-client.js +182 -0
- package/dist/src/frontend/homepage-shared.js +4 -0
- package/dist/src/frontend/homepage-store.js +28 -0
- package/dist/src/frontend/homepage-view.js +547 -0
- package/dist/src/frontend/page-runtime-client.js +132 -0
- package/dist/src/frontend/page-runtime-store.js +9 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/template-editor-chat.js +3960 -0
- package/dist/src/help-page.js +346 -0
- package/dist/src/homepage.js +1235 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/index.js +16 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/page-runtime.js +2 -0
- package/dist/src/page-shell.js +1381 -0
- package/dist/src/primitive-context.js +357 -0
- package/dist/src/primitive-registry.js +2436 -0
- package/dist/src/primitive-sdk.js +4 -0
- package/dist/src/primitives/hyperframes-media.js +108 -0
- package/dist/src/react-page-shell.js +35 -0
- package/dist/src/ready-post-schedule-component.js +1540 -0
- package/dist/src/registry.js +296 -0
- package/dist/src/runtime.js +35 -0
- package/dist/src/services/api-call-history.js +249 -0
- package/dist/src/services/auth.js +152 -0
- package/dist/src/services/billing-pricing.js +39 -0
- package/dist/src/services/billing.js +228 -0
- package/dist/src/services/cast.js +127 -0
- package/dist/src/services/chat-threads.js +92 -0
- package/dist/src/services/composition-sanitize.js +124 -0
- package/dist/src/services/composition-watch.js +79 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-manifest.js +42 -0
- package/dist/src/services/ghostcut.js +179 -0
- package/dist/src/services/hyperframes.js +2307 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-logs.js +197 -0
- package/dist/src/services/jobs.js +136 -0
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +766 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/providers.js +2926 -0
- package/dist/src/services/rate-limits.js +262 -0
- package/dist/src/services/serverless-auth.js +382 -0
- package/dist/src/services/serverless-jobs.js +1082 -0
- package/dist/src/services/serverless-provider-keys.js +409 -0
- package/dist/src/services/serverless-records.js +1385 -0
- package/dist/src/services/serverless-template-configs.js +75 -0
- package/dist/src/services/storage.js +383 -0
- package/dist/src/services/template-certification.js +413 -0
- package/dist/src/services/template-loader.js +99 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-sources.js +1017 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/webhooks.js +62 -0
- package/dist/src/template-editor-pages.js +2576 -0
- package/dist/src/template-editor-shell.js +2840 -0
- package/dist/src/template-sdk.js +4 -0
- package/dist/src/worker.js +17 -0
- package/package.json +6 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
- package/src/assets/SELLING_WITH_HOOKS.md +377 -0
- package/src/assets/SELLING_WITH_VSLS.md +606 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
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: "inpaint", href: withAccountQuery("/inpaint", account.userId), label: "Inpaint" },
|
|
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 (_jsxs("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, {}), _jsx("span", { className: "discover-mute-toggle-label", children: muted ? "Muted" : "Sound on" })] }));
|
|
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), 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), 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.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 [tagline, setTagline] = useState("");
|
|
429
|
+
const [notes, setNotes] = useState("");
|
|
430
|
+
const [submitting, setSubmitting] = useState(false);
|
|
431
|
+
const [error, setError] = useState(null);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
const handleKeyDown = (event) => {
|
|
434
|
+
if (event.key === "Escape") {
|
|
435
|
+
onClose();
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const previousOverflow = document.body.style.overflow;
|
|
439
|
+
document.body.style.overflow = "hidden";
|
|
440
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
441
|
+
return () => {
|
|
442
|
+
document.body.style.overflow = previousOverflow;
|
|
443
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
444
|
+
};
|
|
445
|
+
}, [onClose]);
|
|
446
|
+
const handleSubmit = async () => {
|
|
447
|
+
const trimmedUrl = sourceUrl.trim();
|
|
448
|
+
if (!trimmedUrl) {
|
|
449
|
+
setError("Paste a TikTok, YouTube, Twitter/X, or Instagram video URL.");
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
setSubmitting(true);
|
|
453
|
+
setError(null);
|
|
454
|
+
const result = await (input.onAddTemplate?.({ sourceUrl: trimmedUrl, tagline: tagline.trim(), notes: notes.trim() })
|
|
455
|
+
?? Promise.resolve({ ok: false, error: "Adding templates is unavailable right now." }));
|
|
456
|
+
setSubmitting(false);
|
|
457
|
+
if (result.ok) {
|
|
458
|
+
onClose();
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
setError(result.error || "Unable to add that video. Try again.");
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
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) => {
|
|
465
|
+
event.preventDefault();
|
|
466
|
+
void handleSubmit();
|
|
467
|
+
}, children: [_jsx("p", { className: "discover-add-template-hint", children: "Paste a link to a TikTok, YouTube, Twitter/X, or Instagram video. We download it and add it to your Discover feed \u2014 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: true, 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-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 ? "Adding…" : "Add Template" })] })] })] }) }));
|
|
468
|
+
}
|
|
469
|
+
export function HomepageShell(input) {
|
|
470
|
+
const showEmpty = !input.loading && !input.error && input.templates.length > 0 && input.visibleTemplates.length === 0;
|
|
471
|
+
const [previewTemplate, setPreviewTemplate] = useState(null);
|
|
472
|
+
const [isAddTemplateOpen, setIsAddTemplateOpen] = useState(false);
|
|
473
|
+
const searchPlaceholder = input.loading || input.error
|
|
474
|
+
? "Search Templates"
|
|
475
|
+
: `Search ${input.visibleTemplates.length} Template${input.visibleTemplates.length === 1 ? "" : "s"}`;
|
|
476
|
+
// Chat rail is always visible on /discover so logged-out visitors see the copilot
|
|
477
|
+
// affordance and understand what unlocks after login. When logged in, the real chat
|
|
478
|
+
// React tree portals into the [data-template-editor-chat-root] element (see
|
|
479
|
+
// page-runtime-client). When logged out, we render an inert placeholder with a
|
|
480
|
+
// disabled textarea and a "Log in to chat" CTA in the same slot.
|
|
481
|
+
const hasChatRail = true;
|
|
482
|
+
const isChatEnabled = input.account.isLoggedIn;
|
|
483
|
+
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: "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) })] }), _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" })] })] }), 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(DiscoverMuteToggle, {})] }), _jsx("section", { className: "discover-results", children: _jsx("div", { className: "discover-results-scroll", children: _jsxs("div", { className: "discover-list-scroll", 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: {
|
|
484
|
+
display: "flex",
|
|
485
|
+
flexDirection: "column",
|
|
486
|
+
height: "100%",
|
|
487
|
+
padding: "20px",
|
|
488
|
+
gap: "16px",
|
|
489
|
+
boxSizing: "border-box"
|
|
490
|
+
}, children: [_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [_jsx("span", { className: "label", style: {
|
|
491
|
+
fontSize: "0.68rem",
|
|
492
|
+
fontWeight: 800,
|
|
493
|
+
letterSpacing: "0.14em",
|
|
494
|
+
textTransform: "uppercase",
|
|
495
|
+
color: "#a9884a"
|
|
496
|
+
}, 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: {
|
|
497
|
+
display: "flex",
|
|
498
|
+
flexDirection: "column",
|
|
499
|
+
gap: "8px",
|
|
500
|
+
marginTop: "14px"
|
|
501
|
+
}, "aria-hidden": "true", children: [
|
|
502
|
+
"Give me 10 hooks for this coffee-shop template",
|
|
503
|
+
"Which awareness stage fits my product?",
|
|
504
|
+
"Remix this into an ad for my brand"
|
|
505
|
+
].map((prompt) => (_jsxs("span", { style: {
|
|
506
|
+
display: "inline-flex",
|
|
507
|
+
alignItems: "center",
|
|
508
|
+
gap: "8px",
|
|
509
|
+
padding: "10px 14px",
|
|
510
|
+
borderRadius: "14px",
|
|
511
|
+
border: "1px dashed rgba(191, 164, 109, 0.45)",
|
|
512
|
+
background: "rgba(250, 245, 234, 0.65)",
|
|
513
|
+
color: "#74829c",
|
|
514
|
+
fontSize: "0.85rem",
|
|
515
|
+
lineHeight: 1.35
|
|
516
|
+
}, children: [_jsx("span", { style: { color: "#bfa46d", fontWeight: 700 }, children: "\u201C" }), prompt] }, prompt))) }), _jsxs("div", { style: {
|
|
517
|
+
marginTop: "auto",
|
|
518
|
+
display: "flex",
|
|
519
|
+
flexDirection: "column",
|
|
520
|
+
gap: "10px"
|
|
521
|
+
}, 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: {
|
|
522
|
+
width: "100%",
|
|
523
|
+
resize: "none",
|
|
524
|
+
padding: "10px 12px",
|
|
525
|
+
borderRadius: "10px",
|
|
526
|
+
border: "1px solid rgba(191, 164, 109, 0.36)",
|
|
527
|
+
background: "rgba(250, 245, 234, 0.9)",
|
|
528
|
+
color: "#74829c",
|
|
529
|
+
fontFamily: "inherit",
|
|
530
|
+
fontSize: "0.9rem",
|
|
531
|
+
cursor: "not-allowed",
|
|
532
|
+
boxSizing: "border-box"
|
|
533
|
+
} }), _jsx("a", { href: "/login", style: {
|
|
534
|
+
display: "inline-flex",
|
|
535
|
+
alignItems: "center",
|
|
536
|
+
justifyContent: "center",
|
|
537
|
+
minHeight: "38px",
|
|
538
|
+
padding: "0 16px",
|
|
539
|
+
borderRadius: "999px",
|
|
540
|
+
background: "#bfa46d",
|
|
541
|
+
color: "#1a1409",
|
|
542
|
+
fontWeight: 700,
|
|
543
|
+
fontSize: "0.9rem",
|
|
544
|
+
textDecoration: "none"
|
|
545
|
+
}, 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] }));
|
|
546
|
+
}
|
|
547
|
+
//# sourceMappingURL=homepage-view.js.map
|