@mevdragon/vidfarm-devcli 0.15.0 → 0.16.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/demo/dist/app.css +1 -1
- package/demo/dist/app.js +74 -74
- package/dist/src/app.js +313 -7
- package/dist/src/config.js +13 -0
- package/dist/src/editor-chat.js +1 -0
- package/dist/src/frontend/homepage-client.js +50 -1
- package/dist/src/frontend/homepage-shared.js +197 -0
- package/dist/src/frontend/homepage-store.js +10 -1
- package/dist/src/frontend/homepage-view.js +43 -5
- package/dist/src/homepage.js +51 -18
- package/dist/src/services/serverless-records.js +9 -0
- package/package.json +1 -1
- package/public/assets/homepage-client-app.js +23 -23
|
@@ -1,4 +1,201 @@
|
|
|
1
1
|
export function isVideoPreview(url) {
|
|
2
2
|
return Boolean(url && /\.(mp4|webm|ogg|mov)(\?|#|$)/i.test(url));
|
|
3
3
|
}
|
|
4
|
+
export const POPULAR_INSPIRATION_GROUPS = [
|
|
5
|
+
{
|
|
6
|
+
label: "Talking Head & Personal",
|
|
7
|
+
items: [
|
|
8
|
+
{ title: "Vlog style", url: "https://vt.tiktok.com/ZSCWTFBSe/" },
|
|
9
|
+
{ title: "Talking head monologue", url: "https://vt.tiktok.com/ZSCWw9wqf/" },
|
|
10
|
+
{ title: "AI Avatar presenter", url: "https://vt.tiktok.com/ZSC7RVW1o/" },
|
|
11
|
+
{ title: "Day in the life", url: "https://www.tiktok.com/@rubygrifiths/video/7657529386131868950" },
|
|
12
|
+
{ title: "GRWM", url: "https://vt.tiktok.com/ZSCWKv8Xb/" },
|
|
13
|
+
{ title: "Reaction video", url: "https://vt.tiktok.com/ZSCWEF8Rv/" },
|
|
14
|
+
{ title: "Q&A", url: "https://vt.tiktok.com/ZSCWExEeV/" },
|
|
15
|
+
{ title: "Behind the Scenes", url: "https://vt.tiktok.com/ZSCWTFYew/" },
|
|
16
|
+
{ title: "Story time narrated", url: "https://vt.tiktok.com/ZSCW3kAT5/" },
|
|
17
|
+
{ title: "VTuber", url: "https://vt.tiktok.com/ZSC7TQyVM/" },
|
|
18
|
+
{ title: "Female Thirst Trap", url: "https://vt.tiktok.com/ZSCvRLyMf/" },
|
|
19
|
+
{ title: "Male Thirst Trap", url: "https://vt.tiktok.com/ZSCvdUUE2/" },
|
|
20
|
+
{ title: "Masked Host", url: "https://vt.tiktok.com/ZSCvdNvxG/" },
|
|
21
|
+
{ title: "Hot Take", url: "https://vt.tiktok.com/ZSCvewSbB/" }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: "Text / Story-Driven",
|
|
26
|
+
items: [
|
|
27
|
+
{ title: "Reddit story + gameplay", url: "https://vt.tiktok.com/ZSCWpeSRy/" },
|
|
28
|
+
{ title: "Fake text message story", url: "https://vt.tiktok.com/ZSCWov56s/" },
|
|
29
|
+
{ title: "Twitter/X Thread Video", url: "https://vt.tiktok.com/ZSC7199q6/" },
|
|
30
|
+
{ title: "Quote / motivational card", url: "https://vt.tiktok.com/ZSC71D9QD/" },
|
|
31
|
+
{ title: "Facts / did you know", url: "https://vt.tiktok.com/ZSCW3QrKA/" },
|
|
32
|
+
{ title: "Bible verse / spiritual", url: "https://vt.tiktok.com/ZSCWsP8r7/" },
|
|
33
|
+
{ title: "Horoscope / Astrology", url: "https://vt.tiktok.com/ZSC71497u/" },
|
|
34
|
+
{ title: "Minecraft Reddit Brainrot", url: "https://youtube.com/shorts/AmdI56dl1z4" }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "Educational / Explainer",
|
|
39
|
+
items: [
|
|
40
|
+
{ title: "Educational explainer", url: "https://vt.tiktok.com/ZSC7e5fbr/" },
|
|
41
|
+
{ title: "Whiteboard animation", url: "https://vt.tiktok.com/ZSCWpLa4p/" },
|
|
42
|
+
{ title: "Kinetic typography", url: "https://vt.tiktok.com/ZSC7dJnJP/" },
|
|
43
|
+
{ title: "Data viz / Infographic", url: "https://vt.tiktok.com/ZSC7RvdWR/" },
|
|
44
|
+
{ title: "Science explainer", url: "https://youtu.be/WxmSJbG134k" },
|
|
45
|
+
{ title: "Vox News Style", url: "https://www.tiktok.com/@vox/video/7619108001424723231" },
|
|
46
|
+
{ title: "Low Poly Animation", url: "https://vt.tiktok.com/ZSC7RwmGR/" },
|
|
47
|
+
{ title: "Math tutorial", url: "https://youtu.be/6yYnr8FL1fk" },
|
|
48
|
+
{ title: "Historic retelling", url: "https://vt.tiktok.com/ZSC7N6Y68/" },
|
|
49
|
+
{ title: "Book Summary", url: "https://vt.tiktok.com/ZSC7Fmemb/" },
|
|
50
|
+
{ title: "Documentary Style", url: "https://vt.tiktok.com/ZSC7YBXm9/" },
|
|
51
|
+
{ title: "Course lecture", url: "https://youtube.com/shorts/QoQUfVEp-d0" },
|
|
52
|
+
{ title: "Scientific Selling", url: "https://www.tiktok.com/@maxcern/video/7604237124036037919" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: "Product / Marketing",
|
|
57
|
+
items: [
|
|
58
|
+
{ title: "Software product demo", url: "https://vt.tiktok.com/ZSC7rbNcV/" },
|
|
59
|
+
{ title: "Product commercial", url: "https://vt.tiktok.com/ZSCv6C1Ms/" },
|
|
60
|
+
{ title: "UGC Creator Ad", url: "https://vt.tiktok.com/ZSC7hwraf/" },
|
|
61
|
+
{ title: "Product Spin Show", url: "https://vt.tiktok.com/ZSCWsuxxp/" },
|
|
62
|
+
{ title: "Testimonial", url: "https://vt.tiktok.com/ZSC7kRvxk/" },
|
|
63
|
+
{ title: "Explainer commercial", url: "https://vt.tiktok.com/ZSC7SEeHA/" },
|
|
64
|
+
{ title: "Case Study", url: "https://www.tiktok.com/@juliejonesdesigns/video/7298542413948243243" },
|
|
65
|
+
{ title: "Brand story", url: "https://vt.tiktok.com/ZSC7a7wvb/" },
|
|
66
|
+
{ title: "Real Estate tour", url: "https://www.instagram.com/reel/DY19EdHGcDk/" },
|
|
67
|
+
{ title: "Sales pitch", url: "https://www.tiktok.com/@mindfullallie/video/7633917841774742815" },
|
|
68
|
+
{ title: "Logo reveal / brand intro", url: "https://vt.tiktok.com/ZSC7avGmH/" }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: "News / Journalism",
|
|
73
|
+
items: [
|
|
74
|
+
{ title: "News anchor style", url: "https://vt.tiktok.com/ZSC7VtHNS/" },
|
|
75
|
+
{ title: "Breaking news", url: "https://vt.tiktok.com/ZSCWWUCqS/" },
|
|
76
|
+
{ title: "Weather Report", url: "https://vt.tiktok.com/ZSCWWBEEY/" },
|
|
77
|
+
{ title: "Financial / stock update", url: "https://vt.tiktok.com/ZSC7Vt6rP/" },
|
|
78
|
+
{ title: "Sports scores/recap", url: "https://vt.tiktok.com/ZSC7V7Pqf/" }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: "Entertainment / Creative",
|
|
83
|
+
items: [
|
|
84
|
+
{ title: "Anime / manga", url: "https://vt.tiktok.com/ZSC7qnwWn/" },
|
|
85
|
+
{ title: "Cartoon Animation", url: "https://vt.tiktok.com/ZSC7qvEfe/" },
|
|
86
|
+
{ title: "Music Video", url: "https://vt.tiktok.com/ZSC7beC8P/" },
|
|
87
|
+
{ title: "Lyric Video", url: "https://vt.tiktok.com/ZSC7bP5x7/" },
|
|
88
|
+
{ title: "Meme video", url: "https://vt.tiktok.com/ZSC7g8sEn/" },
|
|
89
|
+
{ title: "Meme recaptioning", url: "https://vt.tiktok.com/ZSCv6eeKD/" },
|
|
90
|
+
{ title: "Hype Edit", url: "https://www.tiktok.com/@goldenhype0/video/7645035734901984543" },
|
|
91
|
+
{ title: "Fantasy Hype Edit", url: "https://www.tiktok.com/@hype.edits/video/7538337237453344013" },
|
|
92
|
+
{ title: "UGC Comedy", url: "https://www.tiktok.com/@very.manipulative/video/7644074719737646366" },
|
|
93
|
+
{ title: "Parody", url: "https://vt.tiktok.com/ZSC7g4aa8/" },
|
|
94
|
+
{ title: "ASMR", url: "https://vt.tiktok.com/ZSCvJvseN/" },
|
|
95
|
+
{ title: "Cinematic short film", url: "https://vt.tiktok.com/ZSCvJV2mM/" },
|
|
96
|
+
{ title: "Oddly satisfying", url: "https://vt.tiktok.com/ZSC7gqn9o/" },
|
|
97
|
+
{ title: "POV video", url: "https://vt.tiktok.com/ZSCveMjMp/" },
|
|
98
|
+
{ title: "Nostalgia", url: "https://vt.tiktok.com/ZSCv2DALg/" },
|
|
99
|
+
{ title: "Musical", url: "https://vt.tiktok.com/ZSCvYwKBQ/" },
|
|
100
|
+
{ title: "Gossip", url: "https://vt.tiktok.com/ZSCvYvrh7/" }
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "Data / Analytical",
|
|
105
|
+
items: [
|
|
106
|
+
{ title: "Comparison \"X vs Y\"", url: "https://youtube.com/shorts/jFvemXPxayg" },
|
|
107
|
+
{ title: "Ranking / Tier List", url: "https://youtube.com/shorts/Ku5xNHs7sAE" },
|
|
108
|
+
{ title: "Countdown / top 10", url: "https://vt.tiktok.com/ZSC7psUDB/" },
|
|
109
|
+
{ title: "Poll Results Reveal", url: "https://www.tiktok.com/@carterjstockman/video/7477653012874267947" },
|
|
110
|
+
{ title: "Stats Comparison", url: "https://www.tiktok.com/@everythingecon/video/7650833928726564118" },
|
|
111
|
+
{ title: "Leaderboard Reveal", url: "https://vt.tiktok.com/ZSC7sseno/" },
|
|
112
|
+
{ title: "Sports stat breakdown", url: "https://vt.tiktok.com/ZSCvFUTHN/" },
|
|
113
|
+
{ title: "Before/after Transformation", url: "https://vt.tiktok.com/ZSC7GRLT2/" }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "Gaming",
|
|
118
|
+
items: [
|
|
119
|
+
{ title: "Gameplay walkthrough", url: "https://vt.tiktok.com/ZSCWWE5X5/" },
|
|
120
|
+
{ title: "Game review", url: "https://youtube.com/shorts/nrb0t9cRw3g" },
|
|
121
|
+
{ title: "Game trailer", url: "https://vt.tiktok.com/ZSCW7LqKV/" },
|
|
122
|
+
{ title: "Esports highlights", url: "https://vt.tiktok.com/ZSC7y4BEF/" }
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
label: "Kids / Educational",
|
|
127
|
+
items: [
|
|
128
|
+
{ title: "Nursery Rhyme", url: "https://vt.tiktok.com/ZSC7SWUUr/" },
|
|
129
|
+
{ title: "Alphabet / counting", url: "https://vt.tiktok.com/ZSCWvhRNd/" },
|
|
130
|
+
{ title: "Cartoon lesson", url: "https://vt.tiktok.com/ZSC7y9wHp/" },
|
|
131
|
+
{ title: "Storybook read-along", url: "https://vt.tiktok.com/ZSC7yrnN8/" }
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: "Ambient / Background",
|
|
136
|
+
items: [
|
|
137
|
+
{ title: "Lo-fi study loop", url: "https://vt.tiktok.com/ZSCWcFgAh/" },
|
|
138
|
+
{ title: "Nature + Music", url: "https://vt.tiktok.com/ZSCWc4Egr/" },
|
|
139
|
+
{ title: "Fireplace / rain loop", url: "https://vt.tiktok.com/ZSCWcrF7v/" },
|
|
140
|
+
{ title: "Aesthetic vibe/mood", url: "https://vt.tiktok.com/ZSCWcK2uQ/" }
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "Fitness / Wellness",
|
|
145
|
+
items: [
|
|
146
|
+
{ title: "Workout tutorial", url: "https://vt.tiktok.com/ZSC7fLGUL/" },
|
|
147
|
+
{ title: "Yoga / meditation", url: "https://vt.tiktok.com/ZSC7fTkJr/" },
|
|
148
|
+
{ title: "Recipe / cooking", url: "https://vt.tiktok.com/ZSC7ffpcX/" },
|
|
149
|
+
{ title: "Health tips", url: "https://vt.tiktok.com/ZSC7P6T8y/" }
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: "Travel / Location",
|
|
154
|
+
items: [
|
|
155
|
+
{ title: "Travel vlog", url: "https://vt.tiktok.com/ZSCWthBcp/" },
|
|
156
|
+
{ title: "Destination showcase", url: "https://vt.tiktok.com/ZSC7aVDUv/" },
|
|
157
|
+
{ title: "Review Guides", url: "https://vt.tiktok.com/ZSC7PpXUR/" },
|
|
158
|
+
{ title: "City tour", url: "https://vt.tiktok.com/ZSC7ajj14/" }
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: "Interview / Conversation",
|
|
163
|
+
items: [
|
|
164
|
+
{ title: "Interview format", url: "https://vt.tiktok.com/ZSC74xE8A/" },
|
|
165
|
+
{ title: "Panel discussion", url: "https://vt.tiktok.com/ZSC74xYML/" },
|
|
166
|
+
{ title: "Debate", url: "https://vt.tiktok.com/ZSCvRpCW1/" },
|
|
167
|
+
{ title: "Podcast clip", url: "https://www.tiktok.com/@rogantimes04/video/7659533957049961750" },
|
|
168
|
+
{ title: "Audiogram", url: "https://vt.tiktok.com/ZSCWs6uMm/" },
|
|
169
|
+
{ title: "Diary Confession", url: "https://vt.tiktok.com/ZSCvRcFAn/" }
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
label: "Motion Design",
|
|
174
|
+
items: [
|
|
175
|
+
{ title: "Motion graphics", url: "https://vt.tiktok.com/ZSC7u4VD8/" },
|
|
176
|
+
{ title: "2D character animation", url: "https://vt.tiktok.com/ZSC7uCayM/" },
|
|
177
|
+
{ title: "Stop motion", url: "https://vt.tiktok.com/ZSCWseh2r/" },
|
|
178
|
+
{ title: "Static Fast Forward", url: "https://vt.tiktok.com/ZSC7HRUVa/" },
|
|
179
|
+
{ title: "Montage Video", url: "https://vt.tiktok.com/ZSCv8QPRY/" },
|
|
180
|
+
{ title: "Memoirs", url: "https://vt.tiktok.com/ZSCv89bXC/" }
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: "Hype / Trailer",
|
|
185
|
+
items: [
|
|
186
|
+
{ title: "Trailer / hype video", url: "https://vt.tiktok.com/ZSC7uHkTc/" },
|
|
187
|
+
{ title: "Event promo", url: "https://vt.tiktok.com/ZSC7uMvBA/" },
|
|
188
|
+
{ title: "Reveal video", url: "https://vt.tiktok.com/ZSCvNxK7d/" }
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
label: "Pure AI",
|
|
193
|
+
items: [
|
|
194
|
+
{ title: "AI Fruit Dramas", url: "https://vt.tiktok.com/ZSCv89Xck/" },
|
|
195
|
+
{ title: "Surreal AI Characters", url: "https://vt.tiktok.com/ZSCv8nN2K/" },
|
|
196
|
+
{ title: "Claymation Story", url: "https://vt.tiktok.com/ZSCvLJhAc/" },
|
|
197
|
+
{ title: "Paper Mache Story", url: "https://www.youtube.com/watch?v=0JKu9TQCLMI" }
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
];
|
|
4
201
|
//# sourceMappingURL=homepage-shared.js.map
|
|
@@ -7,6 +7,11 @@ export function createHomepageStore(boot) {
|
|
|
7
7
|
bookmarks: new Set(),
|
|
8
8
|
loading: boot.templates.length === 0,
|
|
9
9
|
error: null,
|
|
10
|
+
view: "feed",
|
|
11
|
+
popularTemplates: [],
|
|
12
|
+
popularLoading: false,
|
|
13
|
+
popularLoaded: false,
|
|
14
|
+
popularError: null,
|
|
10
15
|
setTemplates: (templates) => set({ templates, loading: false, error: null }),
|
|
11
16
|
setSearchQuery: (searchQuery) => set({ searchQuery }),
|
|
12
17
|
setBookmarksOnly: (bookmarksOnly) => set({ bookmarksOnly }),
|
|
@@ -22,7 +27,11 @@ export function createHomepageStore(boot) {
|
|
|
22
27
|
return { bookmarks: next };
|
|
23
28
|
}),
|
|
24
29
|
setLoading: (loading) => set({ loading }),
|
|
25
|
-
setError: (error) => set({ error, loading: false })
|
|
30
|
+
setError: (error) => set({ error, loading: false }),
|
|
31
|
+
setView: (view) => set({ view }),
|
|
32
|
+
setPopularTemplates: (popularTemplates) => set({ popularTemplates, popularLoading: false, popularLoaded: true, popularError: null }),
|
|
33
|
+
setPopularLoading: (popularLoading) => set({ popularLoading }),
|
|
34
|
+
setPopularError: (popularError) => set({ popularError, popularLoading: false })
|
|
26
35
|
}));
|
|
27
36
|
}
|
|
28
37
|
//# sourceMappingURL=homepage-store.js.map
|
|
@@ -263,7 +263,7 @@ function LazyVideo({ src, controls = true }) {
|
|
|
263
263
|
}
|
|
264
264
|
function DiscoverMuteToggle() {
|
|
265
265
|
const muted = useDiscoverMuted();
|
|
266
|
-
return (
|
|
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
267
|
}
|
|
268
268
|
function CopyInlineButton({ text, label }) {
|
|
269
269
|
const [status, setStatus] = useState("");
|
|
@@ -479,13 +479,51 @@ function AddTemplateModal(input) {
|
|
|
479
479
|
}
|
|
480
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
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 popular formats\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 popular formats match your search." : "No popular formats 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
|
+
}
|
|
482
513
|
export function HomepageShell(input) {
|
|
483
514
|
const showEmpty = !input.loading && !input.error && input.templates.length > 0 && input.visibleTemplates.length === 0;
|
|
484
515
|
const [previewTemplate, setPreviewTemplate] = useState(null);
|
|
485
516
|
const [isAddTemplateOpen, setIsAddTemplateOpen] = useState(false);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
517
|
+
// "feed" = the live inspirations catalog (continuously grown); "popular" = the
|
|
518
|
+
// curated static picks fetched from /discover/popular. View state lives in the
|
|
519
|
+
// store so the client can lazily load the Popular set on first open.
|
|
520
|
+
const isPopular = input.view === "popular";
|
|
521
|
+
const setView = (value) => input.onViewChange?.(value);
|
|
522
|
+
const searchPlaceholder = isPopular
|
|
523
|
+
? "Search popular formats"
|
|
524
|
+
: input.loading || input.error
|
|
525
|
+
? "Search Templates"
|
|
526
|
+
: `Search ${input.visibleTemplates.length} Template${input.visibleTemplates.length === 1 ? "" : "s"}`;
|
|
489
527
|
// Chat rail is always visible on /discover so logged-out visitors see the copilot
|
|
490
528
|
// affordance and understand what unlocks after login. When logged in, the real chat
|
|
491
529
|
// React tree portals into the [data-template-editor-chat-root] element (see
|
|
@@ -493,7 +531,7 @@ export function HomepageShell(input) {
|
|
|
493
531
|
// disabled textarea and a "Log in to chat" CTA in the same slot.
|
|
494
532
|
const hasChatRail = true;
|
|
495
533
|
const isChatEnabled = input.account.isLoggedIn;
|
|
496
|
-
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" }))
|
|
534
|
+
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 ? " is-active" : ""}`, role: "tab", "aria-selected": !isPopular, onClick: () => setView("feed"), children: "Feed" }), _jsx("button", { type: "button", className: `view-toggle-tab${isPopular ? " is-active" : ""}`, role: "tab", "aria-selected": isPopular, onClick: () => setView("popular"), children: "Popular" })] }), _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 ? (_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: 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: {
|
|
497
535
|
display: "flex",
|
|
498
536
|
flexDirection: "column",
|
|
499
537
|
height: "100%",
|
package/dist/src/homepage.js
CHANGED
|
@@ -25,7 +25,7 @@ export function renderHomepage(input) {
|
|
|
25
25
|
}
|
|
26
26
|
: undefined,
|
|
27
27
|
body: `
|
|
28
|
-
<div id="homepage-root">${renderToString(_jsx(HomepageShell, { account: input.account, templates: input.templates, visibleTemplates: input.templates, searchQuery: "", bookmarksOnly: false, bookmarks: new Set(), loading: input.templates.length === 0, error: null, onSearchQueryChange: undefined, onBookmarksOnlyChange: undefined, onToggleBookmark: undefined, onAddTemplate: undefined, onDeleteTemplate: undefined, skillEndpointPrefix: boot.skillEndpointPrefix }))}</div>
|
|
28
|
+
<div id="homepage-root">${renderToString(_jsx(HomepageShell, { account: input.account, templates: input.templates, visibleTemplates: input.templates, searchQuery: "", bookmarksOnly: false, bookmarks: new Set(), loading: input.templates.length === 0, error: null, view: "feed", popularTemplates: [], popularLoading: false, popularError: null, onSearchQueryChange: undefined, onBookmarksOnlyChange: undefined, onViewChange: undefined, onToggleBookmark: undefined, onAddTemplate: undefined, onDeleteTemplate: undefined, skillEndpointPrefix: boot.skillEndpointPrefix }))}</div>
|
|
29
29
|
<script type="application/json" id="homepage-boot">${escapeJsonForHtml(boot)}</script>
|
|
30
30
|
<script type="module" src="${HOMEPAGE_CLIENT_ASSET}"></script>
|
|
31
31
|
`,
|
|
@@ -70,8 +70,7 @@ export function renderHomepage(input) {
|
|
|
70
70
|
position: sticky;
|
|
71
71
|
top: 0;
|
|
72
72
|
z-index: 5;
|
|
73
|
-
display:
|
|
74
|
-
grid-template-columns: minmax(220px, 1fr) auto auto auto;
|
|
73
|
+
display: flex;
|
|
75
74
|
align-items: center;
|
|
76
75
|
gap: 12px;
|
|
77
76
|
padding: 14px 22px;
|
|
@@ -80,6 +79,16 @@ export function renderHomepage(input) {
|
|
|
80
79
|
backdrop-filter: blur(8px);
|
|
81
80
|
}
|
|
82
81
|
|
|
82
|
+
.discover-filter-search {
|
|
83
|
+
flex: 1 1 auto;
|
|
84
|
+
min-width: 200px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Feed/Popular segmented switch reuses the shared page-shell .view-toggle
|
|
88
|
+
look; height-match the 44px filter-bar controls. */
|
|
89
|
+
.discover-view-toggle { flex: 0 0 auto; }
|
|
90
|
+
.discover-view-toggle .view-toggle-tab { min-height: 36px; padding: 0 16px; }
|
|
91
|
+
|
|
83
92
|
.discover-filter-search input {
|
|
84
93
|
min-height: 44px;
|
|
85
94
|
padding-left: 46px;
|
|
@@ -134,17 +143,16 @@ export function renderHomepage(input) {
|
|
|
134
143
|
.discover-mute-toggle {
|
|
135
144
|
display: inline-flex;
|
|
136
145
|
align-items: center;
|
|
137
|
-
|
|
146
|
+
justify-content: center;
|
|
147
|
+
width: 44px;
|
|
148
|
+
height: 44px;
|
|
138
149
|
min-height: 44px;
|
|
139
|
-
padding: 0
|
|
150
|
+
padding: 0;
|
|
140
151
|
border: 1px solid rgba(209, 219, 233, 0.96);
|
|
141
152
|
border-radius: 999px;
|
|
142
153
|
background: rgba(255, 255, 255, 0.9);
|
|
143
154
|
color: #55637b;
|
|
144
155
|
cursor: pointer;
|
|
145
|
-
font-size: 0.86rem;
|
|
146
|
-
font-weight: 700;
|
|
147
|
-
white-space: nowrap;
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
.discover-mute-toggle svg {
|
|
@@ -541,6 +549,27 @@ export function renderHomepage(input) {
|
|
|
541
549
|
gap: 22px;
|
|
542
550
|
}
|
|
543
551
|
|
|
552
|
+
/* Popular view: the curated picks render with the same autoplay
|
|
553
|
+
TemplateCard rows as the Feed, just grouped under category headings. */
|
|
554
|
+
.discover-popular {
|
|
555
|
+
display: grid;
|
|
556
|
+
gap: 34px;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.discover-popular-group {
|
|
560
|
+
display: grid;
|
|
561
|
+
gap: 22px;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.discover-popular-group-label {
|
|
565
|
+
margin: 0;
|
|
566
|
+
font-size: 0.78rem;
|
|
567
|
+
font-weight: 800;
|
|
568
|
+
letter-spacing: 0.14em;
|
|
569
|
+
text-transform: uppercase;
|
|
570
|
+
color: #a9884a;
|
|
571
|
+
}
|
|
572
|
+
|
|
544
573
|
.discover-card {
|
|
545
574
|
display: grid;
|
|
546
575
|
grid-template-columns: minmax(220px, 340px) minmax(0, 1fr);
|
|
@@ -919,14 +948,6 @@ export function renderHomepage(input) {
|
|
|
919
948
|
@media (max-width: 1180px) {
|
|
920
949
|
.discover-frame.has-chat-rail { padding-left: 0; }
|
|
921
950
|
|
|
922
|
-
.discover-mute-toggle-label {
|
|
923
|
-
display: none;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
.discover-mute-toggle {
|
|
927
|
-
padding: 0 12px;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
951
|
.discover-card {
|
|
931
952
|
grid-template-columns: minmax(200px, 300px) minmax(0, 1fr);
|
|
932
953
|
gap: 20px;
|
|
@@ -1209,12 +1230,24 @@ export function renderHomepage(input) {
|
|
|
1209
1230
|
}
|
|
1210
1231
|
|
|
1211
1232
|
.discover-filter-toggle,
|
|
1212
|
-
.discover-add-template-button
|
|
1213
|
-
.discover-mute-toggle {
|
|
1233
|
+
.discover-add-template-button {
|
|
1214
1234
|
flex: 1 1 auto;
|
|
1215
1235
|
justify-content: center;
|
|
1216
1236
|
}
|
|
1217
1237
|
|
|
1238
|
+
.discover-mute-toggle {
|
|
1239
|
+
flex: 0 0 auto;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/* Feed/Popular leads its own full-width row on phones. */
|
|
1243
|
+
.discover-view-toggle {
|
|
1244
|
+
flex: 1 1 100%;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.discover-view-toggle .view-toggle-tab {
|
|
1248
|
+
flex: 1 1 auto;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1218
1251
|
.discover-media-modal-backdrop {
|
|
1219
1252
|
align-items: stretch;
|
|
1220
1253
|
padding: 12px;
|
|
@@ -591,6 +591,15 @@ class ServerlessRecordsServiceImpl {
|
|
|
591
591
|
// so the coarse keyword filter never fights the TTL cache.
|
|
592
592
|
return filterAndRankBySearch(this.templatesFeedCache.records, input.query).slice(0, limit);
|
|
593
593
|
}
|
|
594
|
+
// Curated "Popular" picks: the handful of public templates flagged with a
|
|
595
|
+
// popularGroup. Drawn from the same cached feed as listTemplatesFeed (the set
|
|
596
|
+
// is tiny), ordered by popularRank so the editorial sequence is preserved.
|
|
597
|
+
async listPopularTemplates() {
|
|
598
|
+
const feed = await this.listTemplatesFeed({ limit: ServerlessRecordsServiceImpl.TEMPLATES_FEED_MAX });
|
|
599
|
+
return feed
|
|
600
|
+
.filter((record) => typeof record.popularGroup === "string" && record.popularGroup.trim().length > 0)
|
|
601
|
+
.sort((a, b) => (a.popularRank ?? 0) - (b.popularRank ?? 0));
|
|
602
|
+
}
|
|
594
603
|
async queryTemplatesFeed() {
|
|
595
604
|
const response = await dynamodb.send(new QueryCommand({
|
|
596
605
|
TableName: requireMainTableName(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mevdragon/vidfarm-devcli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|