@mevdragon/vidfarm-devcli 0.18.0 → 0.19.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/.agents/skills/editor-capabilities/SKILL.md +166 -0
- package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
- package/.agents/skills/vidfarm-media/SKILL.md +43 -4
- package/SKILL.director.md +190 -18
- package/SKILL.platform.md +8 -4
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +77 -75
- package/demo/dist/favicon.ico +0 -0
- package/dist/src/app.js +3031 -300
- package/dist/src/cli.js +1549 -69
- package/dist/src/config.js +7 -0
- package/dist/src/devcli/clip-store.js +29 -2
- package/dist/src/devcli/clips.js +55 -9
- package/dist/src/devcli/composition-edit.js +658 -8
- package/dist/src/devcli/local-backend.js +123 -0
- package/dist/src/devcli/migrate-local.js +140 -0
- package/dist/src/devcli/sync.js +311 -0
- package/dist/src/devcli/timeline-edit.js +490 -0
- package/dist/src/editor-chat.js +56 -17
- package/dist/src/frontend/discover-client.js +130 -0
- package/dist/src/frontend/discover-store.js +23 -0
- package/dist/src/frontend/file-directory.js +995 -0
- package/dist/src/frontend/homepage-view.js +3 -3
- package/dist/src/frontend/template-editor-chat.js +28 -22
- package/dist/src/landing-page.js +24 -7
- package/dist/src/page-shell.js +26 -2
- package/dist/src/primitive-registry.js +409 -4
- package/dist/src/reskin/agency-page.js +1 -1
- package/dist/src/reskin/calendar-page.js +2 -1
- package/dist/src/reskin/chat-page.js +420 -85
- package/dist/src/reskin/discover-page.js +731 -39
- package/dist/src/reskin/document.js +1311 -387
- package/dist/src/reskin/help-page.js +1 -0
- package/dist/src/reskin/inpaint-clipper-page.js +649 -0
- package/dist/src/reskin/inpaint-page.js +2459 -446
- package/dist/src/reskin/inpaint-video-page.js +1339 -0
- package/dist/src/reskin/library-page.js +1168 -228
- package/dist/src/reskin/login-page.js +6 -6
- package/dist/src/reskin/pricing-page.js +2 -0
- package/dist/src/reskin/settings-page.js +55 -10
- package/dist/src/reskin/theme.js +365 -20
- package/dist/src/services/billing.js +4 -0
- package/dist/src/services/clip-curation/gemini.js +5 -0
- package/dist/src/services/clip-curation/hunt.js +81 -1
- package/dist/src/services/clip-curation/index.js +2 -1
- package/dist/src/services/clip-curation/local-agent.js +4 -3
- package/dist/src/services/clip-curation/media-select.js +85 -0
- package/dist/src/services/clip-curation/query.js +5 -1
- package/dist/src/services/clip-curation/refine.js +50 -20
- package/dist/src/services/clip-curation/scan.js +10 -3
- package/dist/src/services/clip-curation/taxonomy.js +3 -1
- package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
- package/dist/src/services/clip-records.js +42 -1
- package/dist/src/services/clip-search.js +43 -13
- package/dist/src/services/file-directory.js +117 -0
- package/dist/src/services/hyperframes.js +283 -3
- package/dist/src/services/serverless-records.js +43 -0
- package/dist/src/services/storage.js +47 -2
- package/dist/src/services/upstream.js +5 -5
- package/dist/src/template-editor-shell.js +16 -2
- package/package.json +1 -1
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/file-directory-app.js +2 -0
- package/public/assets/homepage-client-app.js +12 -12
- package/public/assets/page-runtime-client-app.js +24 -24
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -66,7 +66,7 @@ function PrimaryNav({ account }) {
|
|
|
66
66
|
? [
|
|
67
67
|
{ id: "chat", href: withAccountQuery("/chat", account.userId), label: "Chat" },
|
|
68
68
|
{ id: "job-runs", href: withAccountQuery("/job-runs", account.userId), label: "Run History" },
|
|
69
|
-
{ id: "
|
|
69
|
+
{ id: "tools", href: withAccountQuery("/tools/image", account.userId), label: "Tools" },
|
|
70
70
|
{ id: "help", href: "/help", label: "Help" },
|
|
71
71
|
{ id: "logout", action: "logout", label: "Log Out" }
|
|
72
72
|
]
|
|
@@ -286,7 +286,7 @@ function editorHref(templateId, accountUserId) {
|
|
|
286
286
|
: withAccountQuery(`/templates/${encodeURIComponent(templateId)}/editor/docs`, accountUserId);
|
|
287
287
|
}
|
|
288
288
|
function CreateButton({ templateId, accountUserId }) {
|
|
289
|
-
return _jsx("a", { className: "cta-button", href: editorHref(templateId, accountUserId), children: "Editor" });
|
|
289
|
+
return _jsx("a", { className: "cta-button", href: editorHref(templateId, accountUserId), target: "_blank", rel: "noopener", children: "Editor" });
|
|
290
290
|
}
|
|
291
291
|
function CardMenu(input) {
|
|
292
292
|
const { template } = input;
|
|
@@ -343,7 +343,7 @@ function CardMenu(input) {
|
|
|
343
343
|
window.alert(result.error || "Unable to delete this template.");
|
|
344
344
|
}
|
|
345
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: () => {
|
|
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
347
|
input.onToggleBookmark?.(template.templateId);
|
|
348
348
|
setIsOpen(false);
|
|
349
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] }));
|
|
@@ -2,6 +2,24 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { debugError, debugLog, debugWarn } from "./debug.js";
|
|
5
|
+
// Parse the fork id out of the canonical editor path
|
|
6
|
+
// /editor/<template_id>/fork/<fork_id> → "<fork_id>" (else null). The editor
|
|
7
|
+
// URL scheme moved from ?fork= to a path segment; this reads the new form.
|
|
8
|
+
function forkIdFromLocation() {
|
|
9
|
+
if (typeof window === "undefined")
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
const match = new URL(window.location.href).pathname.match(/^\/editor\/[^/]+\/fork\/([^/]+)\/?$/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
const forkId = decodeURIComponent(match[1]);
|
|
16
|
+
// /editor/original/fork/new (blank new project) has no real fork yet.
|
|
17
|
+
return forkId === "new" ? null : forkId;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
5
23
|
const MB = 1024 * 1024;
|
|
6
24
|
const MY_FILES_UPLOAD_LIMIT_BYTES = 50 * MB;
|
|
7
25
|
const CHAT_INLINE_IMAGE_TARGET_BYTES = 2 * MB;
|
|
@@ -372,8 +390,11 @@ function collectLabeledUrls(value, output, seen = new Set(), path = []) {
|
|
|
372
390
|
}
|
|
373
391
|
}
|
|
374
392
|
function buildJobQueuedMessage(job) {
|
|
393
|
+
// MUST stay string-identical to buildAsyncJobHandoffText in src/editor-chat.ts —
|
|
394
|
+
// the server streams that text and this client copy dedupes it by substring.
|
|
395
|
+
// Job-KIND-agnostic ("job", not "render"): fires for any queued async job.
|
|
375
396
|
return [
|
|
376
|
-
`Started
|
|
397
|
+
`Started job \`${job.job_id}\`${job.tracer ? ` with tracer \`${job.tracer}\`` : ""}.`,
|
|
377
398
|
"If it takes a while, ask for an update later and I can check it for you."
|
|
378
399
|
].join("\n\n");
|
|
379
400
|
}
|
|
@@ -1461,7 +1482,7 @@ function mediaInpaintHref(preview, ensureTracer = false) {
|
|
|
1461
1482
|
if (typeof window === "undefined" || preview.kind !== "image" || !preview.url) {
|
|
1462
1483
|
return "";
|
|
1463
1484
|
}
|
|
1464
|
-
const pageUrl = new URL("/
|
|
1485
|
+
const pageUrl = new URL("/tools/image", window.location.origin);
|
|
1465
1486
|
const bridge = window.__vidfarmEditorTracers;
|
|
1466
1487
|
const existingTracers = bridge?.get() ?? readTracersFromLocation();
|
|
1467
1488
|
const tracer = existingTracers[0] || (ensureTracer ? bridge?.add() ?? "" : "");
|
|
@@ -1482,7 +1503,7 @@ function MediaPreviewModal(props) {
|
|
|
1482
1503
|
const activePreview = gallery[activeIndex] || props.preview;
|
|
1483
1504
|
const canNavigate = gallery.length > 1;
|
|
1484
1505
|
const canInpaint = activePreview.kind === "image";
|
|
1485
|
-
const initialInpaintHref = canInpaint ? mediaInpaintHref(activePreview, false) || "/
|
|
1506
|
+
const initialInpaintHref = canInpaint ? mediaInpaintHref(activePreview, false) || "/tools/image" : "/tools/image";
|
|
1486
1507
|
useEffect(() => {
|
|
1487
1508
|
setIsMounted(true);
|
|
1488
1509
|
}, []);
|
|
@@ -2417,25 +2438,10 @@ export function TemplateEditorChat({ boot }) {
|
|
|
2417
2438
|
useEffect(() => {
|
|
2418
2439
|
threadsRef.current = threads;
|
|
2419
2440
|
}, [threads]);
|
|
2420
|
-
const [currentForkId, setCurrentForkId] = useState(() =>
|
|
2421
|
-
if (typeof window === "undefined") {
|
|
2422
|
-
return null;
|
|
2423
|
-
}
|
|
2424
|
-
try {
|
|
2425
|
-
return new URL(window.location.href).searchParams.get("fork");
|
|
2426
|
-
}
|
|
2427
|
-
catch {
|
|
2428
|
-
return null;
|
|
2429
|
-
}
|
|
2430
|
-
});
|
|
2441
|
+
const [currentForkId, setCurrentForkId] = useState(() => forkIdFromLocation());
|
|
2431
2442
|
useEffect(() => {
|
|
2432
2443
|
const syncForkFromLocation = () => {
|
|
2433
|
-
|
|
2434
|
-
setCurrentForkId(new URL(window.location.href).searchParams.get("fork"));
|
|
2435
|
-
}
|
|
2436
|
-
catch {
|
|
2437
|
-
setCurrentForkId(null);
|
|
2438
|
-
}
|
|
2444
|
+
setCurrentForkId(forkIdFromLocation());
|
|
2439
2445
|
};
|
|
2440
2446
|
window.addEventListener("popstate", syncForkFromLocation);
|
|
2441
2447
|
return () => {
|
|
@@ -3704,7 +3710,7 @@ export function TemplateEditorChat({ boot }) {
|
|
|
3704
3710
|
if (!forkRes.ok)
|
|
3705
3711
|
throw new Error(fork?.error || `couldn't create an editable copy (${forkRes.status})`);
|
|
3706
3712
|
const forkId = fork?.fork_id;
|
|
3707
|
-
const editorUrl = `${origin}/editor/${encodeURIComponent(templateId)}${forkId ?
|
|
3713
|
+
const editorUrl = `${origin}/editor/${encodeURIComponent(templateId)}${forkId ? `/fork/${encodeURIComponent(forkId)}` : ""}`;
|
|
3708
3714
|
patch("Your video is ready.", `✅ Your video is ready — [Open it in the editor](${editorUrl}).`);
|
|
3709
3715
|
}
|
|
3710
3716
|
catch (error) {
|
|
@@ -4142,7 +4148,7 @@ export function TemplateEditorChat({ boot }) {
|
|
|
4142
4148
|
left: openThreadMenu.left
|
|
4143
4149
|
}, onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", onClick: () => copyThreadId(openThreadMenuThread.id), children: "Copy Chat ID" }), _jsx("button", { type: "button", onClick: () => editThreadTracers(openThreadMenuThread.id), children: "Edit tracers" }), _jsx("button", { type: "button", onClick: () => removeThreadFromView(openThreadMenuThread.id), children: "Remove from view" }), _jsx("button", { type: "button", onClick: () => archiveThread(openThreadMenuThread.id), children: "Archive thread" }), _jsx("button", { type: "button", "data-danger": "true", onClick: () => deleteThread(openThreadMenuThread.id), children: "Delete" })] }), document.body) : null] }), _jsx("div", { className: "vf-editor-chat-viewport", ref: viewportRef, children: showHistoryPanel ? (_jsx("div", { className: "vf-editor-chat-history-panel", children: threadListContent })) : showBranchesPanel ? (_jsxs("div", { className: "vf-editor-chat-history-panel vf-editor-chat-branches-panel", children: [_jsxs("div", { className: "vf-editor-chat-branches-header", children: [_jsxs("div", { className: "vf-editor-chat-branches-header-main", children: [_jsx("div", { className: "vf-editor-chat-branches-kicker", children: "Forks" }), _jsxs("div", { className: "vf-editor-chat-branches-title", children: [branches?.length ?? 0, " fork", (branches?.length ?? 0) === 1 ? "" : "s", " of this template"] })] }), _jsx("button", { type: "button", className: "vf-editor-chat-branches-refresh", onClick: () => { void loadBranches({ force: true }); }, disabled: branchesLoading, "aria-label": "Refresh forks", children: branchesLoading ? "Loading…" : "Refresh" })] }), branchesError ? (_jsx("div", { className: "vf-editor-chat-thread-empty", role: "status", children: branchesError })) : branchesLoading && !branches ? (_jsx("div", { className: "vf-editor-chat-thread-empty", children: "Loading forks\u2026" })) : branches && branches.length ? (_jsx("div", { className: "vf-editor-chat-thread-list", children: branches.map((fork) => {
|
|
4144
4150
|
const isCurrent = currentForkId === fork.forkId;
|
|
4145
|
-
const forkUrl = `/editor/${encodeURIComponent(boot.template.templateId)}
|
|
4151
|
+
const forkUrl = `/editor/${encodeURIComponent(boot.template.templateId)}/fork/${encodeURIComponent(fork.forkId)}`;
|
|
4146
4152
|
const label = fork.title && fork.title.trim() ? fork.title : fork.forkId;
|
|
4147
4153
|
const parentSuffix = fork.parentForkId ? ` · forked from ${fork.parentForkId.slice(0, 12)}${fork.parentVersion ? `#v${fork.parentVersion}` : ""}` : "";
|
|
4148
4154
|
const versionSuffix = fork.latestVersion && fork.latestVersion > 0 ? ` · v${fork.latestVersion}` : "";
|
package/dist/src/landing-page.js
CHANGED
|
@@ -33,8 +33,24 @@ export function renderLandingPage(input) {
|
|
|
33
33
|
<head>
|
|
34
34
|
<meta charset="utf-8">
|
|
35
35
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
36
|
-
<title>
|
|
37
|
-
<meta name="description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser.
|
|
36
|
+
<title>VidFarm — short-form video that makes itself</title>
|
|
37
|
+
<meta name="description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
|
|
38
|
+
<meta name="theme-color" content="#171717">
|
|
39
|
+
<meta name="robots" content="index, follow">
|
|
40
|
+
<meta name="application-name" content="VidFarm">
|
|
41
|
+
<link rel="canonical" href="https://vidfarm.cc/">
|
|
42
|
+
<meta property="og:type" content="website">
|
|
43
|
+
<meta property="og:site_name" content="VidFarm">
|
|
44
|
+
<meta property="og:title" content="VidFarm — short-form video that makes itself">
|
|
45
|
+
<meta property="og:description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
|
|
46
|
+
<meta property="og:url" content="https://vidfarm.cc/">
|
|
47
|
+
<meta property="og:image" content="https://vidfarm.cc/assets/logo-vidfarm.png">
|
|
48
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
49
|
+
<meta name="twitter:title" content="VidFarm — short-form video that makes itself">
|
|
50
|
+
<meta name="twitter:description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
|
|
51
|
+
<meta name="twitter:image" content="https://vidfarm.cc/assets/logo-vidfarm.png">
|
|
52
|
+
<link rel="icon" href="/assets/favicon.ico" sizes="any">
|
|
53
|
+
<link rel="apple-touch-icon" href="/assets/logo-vidfarm.png">
|
|
38
54
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
39
55
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
40
56
|
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -80,7 +96,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
|
|
|
80
96
|
.glass{background:rgba(255,255,255,.72);backdrop-filter:blur(var(--glass-blur));-webkit-backdrop-filter:blur(var(--glass-blur));border:1px solid var(--border);box-shadow:var(--shadow-nav)}
|
|
81
97
|
.nav{width:min(var(--container),100%);display:flex;align-items:center;gap:24px;padding:10px 12px 10px 22px;border-radius:var(--r-full)}
|
|
82
98
|
.brand{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-display);font-weight:800;font-size:22px;color:var(--ink);letter-spacing:-.02em}
|
|
83
|
-
.brand-mark{width:
|
|
99
|
+
.brand-mark{display:inline-block;vertical-align:middle;width:48px;height:48px;border-radius:12px;background:center/contain no-repeat url("/assets/logo-vidfarm.png");font-size:0;color:transparent;text-indent:-9999px;overflow:hidden}
|
|
84
100
|
.nav-links{display:flex;gap:2px;margin:0 auto;list-style:none}
|
|
85
101
|
.nav-links a{display:inline-flex;align-items:center;gap:4px;padding:8px 12px;border-radius:var(--r-lg);font-size:15px;font-weight:500;color:var(--n-700);transition:background var(--dur) var(--ease)}
|
|
86
102
|
.nav-links a:hover{background:#0000000a}
|
|
@@ -186,7 +202,8 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
|
|
|
186
202
|
.footer-inner{width:min(var(--container),100%);margin:0 auto}
|
|
187
203
|
.footer-top{display:grid;grid-template-columns:1.4fr 3fr;gap:40px;margin-bottom:40px}
|
|
188
204
|
@media(max-width:820px){.footer-top{grid-template-columns:1fr}}
|
|
189
|
-
.footer-brand{font-family:var(--font-display);font-weight:800;font-size:28px;color:var(--gold-500);display:inline-flex;align-items:center;gap:
|
|
205
|
+
.footer-brand{font-family:var(--font-display);font-weight:800;font-size:28px;color:var(--gold-500);display:inline-flex;align-items:center;gap:12px}
|
|
206
|
+
.footer-brand .brand-mark{width:60px;height:60px;border-radius:14px}
|
|
190
207
|
.footer-tagline{color:var(--n-500);margin-top:10px;max-width:280px}
|
|
191
208
|
.footer-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}
|
|
192
209
|
@media(max-width:560px){.footer-cols{grid-template-columns:repeat(2,1fr)}}
|
|
@@ -204,7 +221,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
|
|
|
204
221
|
<!-- NAV (body-level so it stays sticky across the whole page) -->
|
|
205
222
|
<header class="nav-wrap">
|
|
206
223
|
<nav class="nav glass">
|
|
207
|
-
<a class="brand" href="/"><span class="brand-mark">🎬</span>
|
|
224
|
+
<a class="brand" href="/"><span class="brand-mark">🎬</span>VidFarm</a>
|
|
208
225
|
<ul class="nav-links">
|
|
209
226
|
<li><a href="${HREF.studio}">Templates</a></li>
|
|
210
227
|
<li><a href="${HREF.clips}">Clips</a></li>
|
|
@@ -239,7 +256,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
|
|
|
239
256
|
<div class="hero-shot">
|
|
240
257
|
<div class="shot-head">
|
|
241
258
|
<span class="dot dot-r"></span><span class="dot dot-y"></span><span class="dot dot-g"></span>
|
|
242
|
-
<span class="shot-title">
|
|
259
|
+
<span class="shot-title">VidFarm · trackpad editor</span>
|
|
243
260
|
</div>
|
|
244
261
|
<div class="pipeline">
|
|
245
262
|
<div class="node"><span class="tile tile-sky">📼</span><div><div class="node-title">Fork template</div><div class="node-sub">Proven hook</div></div></div>
|
|
@@ -344,7 +361,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
|
|
|
344
361
|
<div class="footer-inner">
|
|
345
362
|
<div class="footer-top">
|
|
346
363
|
<div>
|
|
347
|
-
<div class="footer-brand"><span class="brand-mark">🎬</span>
|
|
364
|
+
<div class="footer-brand"><span class="brand-mark">🎬</span>VidFarm</div>
|
|
348
365
|
<div class="footer-tagline">The serverless studio for short-form video.</div>
|
|
349
366
|
</div>
|
|
350
367
|
<div class="footer-cols">
|
package/dist/src/page-shell.js
CHANGED
|
@@ -82,7 +82,7 @@ export function renderPrimaryNav(input) {
|
|
|
82
82
|
? [
|
|
83
83
|
{ id: "chat", href: withAccountQuery("/chat", input.accountId), label: "Chat" },
|
|
84
84
|
{ id: "job-runs", href: withAccountQuery("/job-runs", input.accountId), label: "Run History" },
|
|
85
|
-
{ id: "
|
|
85
|
+
{ id: "tools", href: withAccountQuery("/tools/image", input.accountId), label: "Tools" },
|
|
86
86
|
{ id: "help", href: "/help", label: "Help" },
|
|
87
87
|
{ id: "logout", action: "logout", label: "Log Out" }
|
|
88
88
|
]
|
|
@@ -168,14 +168,38 @@ export function renderBrandLockup(input) {
|
|
|
168
168
|
</a>
|
|
169
169
|
`;
|
|
170
170
|
}
|
|
171
|
+
const BRAND_NAME = "VidFarm";
|
|
172
|
+
const BRAND_ORIGIN = "https://vidfarm.cc";
|
|
173
|
+
const BRAND_OG_IMAGE = `${BRAND_ORIGIN}/assets/logo-vidfarm.png`;
|
|
171
174
|
export function renderPageShell(input) {
|
|
172
175
|
const isEditorTheme = input.bodyClass?.split(/\s+/).includes("is-editor-theme") ?? false;
|
|
176
|
+
// Brand the tab title unless the page already names VidFarm.
|
|
177
|
+
const pageTitle = /vidfarm/i.test(input.title) ? input.title : `${input.title} · ${BRAND_NAME}`;
|
|
178
|
+
const description = input.description ?? `${BRAND_NAME} — serverless short-form video studio. Fork a proven template, remix it, and render a share-ready MP4 in your browser.`;
|
|
179
|
+
const ogImage = input.social?.image ?? BRAND_OG_IMAGE;
|
|
180
|
+
const ogType = input.social?.type ?? "website";
|
|
181
|
+
const ogUrl = input.social?.url ?? BRAND_ORIGIN;
|
|
182
|
+
const metaTags = `
|
|
183
|
+
<meta name="description" content="${escapeHtml(description)}" />
|
|
184
|
+
<meta name="theme-color" content="#f8f4eb" />
|
|
185
|
+
<meta name="robots" content="index, follow" />
|
|
186
|
+
<meta name="application-name" content="${BRAND_NAME}" />
|
|
187
|
+
<meta property="og:type" content="${escapeHtml(ogType)}" />
|
|
188
|
+
<meta property="og:site_name" content="${BRAND_NAME}" />
|
|
189
|
+
<meta property="og:title" content="${escapeHtml(pageTitle)}" />
|
|
190
|
+
<meta property="og:description" content="${escapeHtml(description)}" />
|
|
191
|
+
<meta property="og:url" content="${escapeHtml(ogUrl)}" />
|
|
192
|
+
<meta property="og:image" content="${escapeHtml(ogImage)}" />
|
|
193
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
194
|
+
<meta name="twitter:title" content="${escapeHtml(pageTitle)}" />
|
|
195
|
+
<meta name="twitter:description" content="${escapeHtml(description)}" />
|
|
196
|
+
<meta name="twitter:image" content="${escapeHtml(ogImage)}" />`;
|
|
173
197
|
return `<!doctype html>
|
|
174
198
|
<html lang="en">
|
|
175
199
|
<head>
|
|
176
200
|
<meta charset="utf-8" />
|
|
177
201
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
178
|
-
<title>${escapeHtml(
|
|
202
|
+
<title>${escapeHtml(pageTitle)}</title>${metaTags}
|
|
179
203
|
<link rel="icon" href="/assets/favicon.ico" sizes="any" />
|
|
180
204
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
181
205
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|