@mevdragon/vidfarm-devcli 0.16.0 → 0.17.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.
Files changed (33) hide show
  1. package/.agents/skills/farmville-saas-ux/SKILL.md +156 -0
  2. package/.agents/skills/farmville-saas-ux/assets/starter.html +294 -0
  3. package/.agents/skills/farmville-saas-ux/references/components.md +340 -0
  4. package/.agents/skills/farmville-saas-ux/references/porting-guide.md +121 -0
  5. package/.agents/skills/farmville-saas-ux/references/tokens.md +271 -0
  6. package/.agents/skills/vidfarm-media/SKILL.md +2 -1
  7. package/.agents/skills/vidfarm-media/references/tts.md +20 -1
  8. package/dist/src/account-pages-legacy.js +2 -2
  9. package/dist/src/app.js +441 -77
  10. package/dist/src/editor-chat.js +2 -2
  11. package/dist/src/frontend/homepage-client.js +162 -2
  12. package/dist/src/frontend/homepage-store.js +30 -1
  13. package/dist/src/frontend/homepage-view.js +111 -4
  14. package/dist/src/homepage.js +184 -1
  15. package/dist/src/landing-page.js +367 -0
  16. package/dist/src/primitive-registry.js +278 -2
  17. package/dist/src/reskin/agency-page.js +255 -0
  18. package/dist/src/reskin/calendar-page.js +252 -0
  19. package/dist/src/reskin/chat-page.js +414 -0
  20. package/dist/src/reskin/discover-page.js +380 -0
  21. package/dist/src/reskin/document.js +74 -0
  22. package/dist/src/reskin/help-page.js +318 -0
  23. package/dist/src/reskin/index-page.js +62 -0
  24. package/dist/src/reskin/job-runs-page.js +249 -0
  25. package/dist/src/reskin/library-page.js +515 -0
  26. package/dist/src/reskin/login-page.js +225 -0
  27. package/dist/src/reskin/pricing-page.js +359 -0
  28. package/dist/src/reskin/settings-page.js +353 -0
  29. package/dist/src/reskin/theme.js +265 -0
  30. package/dist/src/services/serverless-records.js +54 -0
  31. package/dist/src/services/swipe-customize.js +434 -0
  32. package/package.json +1 -1
  33. package/public/assets/homepage-client-app.js +22 -22
@@ -0,0 +1,380 @@
1
+ // /reskin/discover — the template discovery surface, in the farmville design system.
2
+ // SIMPLIFIED & RESTRUCTURED into THREE server-routed modes, each its own URL:
3
+ // • feed → /reskin/discover/feed — clean 9:16 poster grid
4
+ // • swipe → /reskin/discover/swipe — Tinder-style single-card deck
5
+ // • categories → /reskin/discover/categories — browse-by-format cards
6
+ //
7
+ // The mode is chosen server-side (the `mode` arg) and surfaced as a Feed / Swipe /
8
+ // Categories segmented toggle rendered as THREE real <a> links. Rendered with baked
9
+ // SAMPLE data so every mode views without auth or a live catalog. Uses ONLY shared
10
+ // rk-* primitives + a page-scoped rk-discover-* CSS block + a tiny self-contained
11
+ // module script (deck advance in swipe). Pill density is kept deliberately low: at
12
+ // most a duration overlay + a plain meta line + the single gold Fork CTA per card,
13
+ // so the surface reads calm rather than repetitive.
14
+ import { reskinDocument } from "./document.js";
15
+ const MODES = [
16
+ { mode: "feed", label: "Feed" },
17
+ { mode: "swipe", label: "Swipe" },
18
+ { mode: "categories", label: "Categories" }
19
+ ];
20
+ const MODE_COPY = {
21
+ feed: {
22
+ badge: "Template feed",
23
+ sub: "Browse proven short-form formats, then fork one into a live editor and make it yours in an afternoon."
24
+ },
25
+ swipe: {
26
+ badge: "Swipe to customize",
27
+ sub: "One template at a time — keep the ones that fit and we'll recut them into ads for your brand."
28
+ },
29
+ categories: {
30
+ badge: "Browse by format",
31
+ sub: "Jump straight to the style you need — every short-form format, neatly grouped."
32
+ }
33
+ };
34
+ const CARDS = [
35
+ { emoji: "💄", grad: "linear-gradient(150deg,#ffd76a,#f97316)", hook: "POV: you finally found the serum", title: "Skincare UGC hook", handle: "@glow.recipes", difficulty: "easy", duration: "0:18", cat: "ugc", forks: "2.4k" },
36
+ { emoji: "🎙️", grad: "linear-gradient(160deg,#6cc0f5,#3f9fe0)", hook: "Nobody tells you this about growth", title: "Talking-head monologue", handle: "@founder.notes", difficulty: "easy", duration: "0:32", cat: "talking", forks: "1.9k" },
37
+ { emoji: "🧠", grad: "linear-gradient(155deg,#c4a0ee,#7c3aed)", hook: "How it actually works", title: "Kinetic explainer", handle: "@explain.daily", difficulty: "medium", duration: "0:44", cat: "explainer", forks: "3.1k" },
38
+ { emoji: "🔟", grad: "linear-gradient(160deg,#7de0a3,#16a34a)", hook: "5 tools I can't live without", title: "Listicle countdown", handle: "@stack.picks", difficulty: "easy", duration: "0:27", cat: "listicle", forks: "1.2k" },
39
+ { emoji: "😂", grad: "linear-gradient(150deg,#ffb27a,#f43f5e)", hook: "me explaining this to my boss", title: "Meme recaption", handle: "@office.brainrot", difficulty: "easy", duration: "0:11", cat: "meme", forks: "5.8k" },
40
+ { emoji: "✨", grad: "linear-gradient(155deg,#ffc9d4,#f472b6)", hook: "get ready with me", title: "GRWM voiceover", handle: "@morning.rituals", difficulty: "medium", duration: "0:38", cat: "talking", forks: "980" },
41
+ { emoji: "📱", grad: "linear-gradient(155deg,#4b4b4b,#171717)", hook: "This app saved me 4 hours a day", title: "Software product demo", handle: "@shipfast.co", difficulty: "hard", duration: "0:41", cat: "product", forks: "2.0k" },
42
+ { emoji: "📖", grad: "linear-gradient(160deg,#8fa3ff,#4f46e5)", hook: "AITA for quitting on day one?", title: "Reddit story + gameplay", handle: "@story.loops", difficulty: "medium", duration: "0:52", cat: "story", forks: "4.3k" },
43
+ { emoji: "🏆", grad: "linear-gradient(150deg,#ffd76a,#fb7185)", hook: "Top 10 hooks that actually convert", title: "Ranking / tier list", handle: "@viral.teardown", difficulty: "medium", duration: "0:35", cat: "listicle", forks: "2.7k" },
44
+ { emoji: "💬", grad: "linear-gradient(160deg,#5eead4,#0d9488)", hook: "I doubled my revenue in 60 days", title: "Testimonial UGC", handle: "@case.studies", difficulty: "easy", duration: "0:24", cat: "ugc", forks: "1.5k" },
45
+ { emoji: "📊", grad: "linear-gradient(150deg,#ffe08a,#38bdf8)", hook: "The chart that broke the internet", title: "Data-viz explainer", handle: "@number.go.up", difficulty: "hard", duration: "0:29", cat: "explainer", forks: "890" },
46
+ { emoji: "🎬", grad: "linear-gradient(160deg,#c084fc,#1e1b4b)", hook: "One shot. No cuts.", title: "Cinematic short", handle: "@one.take", difficulty: "expert", duration: "0:47", cat: "story", forks: "670" }
47
+ ];
48
+ // The swipe deck — a curated front-to-back run through the strongest formats.
49
+ const SWIPE_CARDS = [
50
+ CARDS[0], // Skincare UGC
51
+ CARDS[4], // Meme recaption
52
+ CARDS[2], // Kinetic explainer
53
+ CARDS[7], // Reddit story
54
+ CARDS[1] // Talking-head monologue
55
+ ];
56
+ const CATEGORIES = [
57
+ {
58
+ slug: "ugc", label: "UGC ad", emoji: "💄", tile: "rk-tile-coral", count: 128,
59
+ blurb: "Creator-style ads that feel like a recommendation, not an ad.",
60
+ thumbs: [
61
+ { emoji: "💄", grad: "linear-gradient(150deg,#ffd76a,#f97316)" },
62
+ { emoji: "💬", grad: "linear-gradient(160deg,#5eead4,#0d9488)" },
63
+ { emoji: "🧴", grad: "linear-gradient(150deg,#ffc9d4,#f472b6)" },
64
+ { emoji: "🛍️", grad: "linear-gradient(155deg,#ffb27a,#f43f5e)" }
65
+ ]
66
+ },
67
+ {
68
+ slug: "talking", label: "Talking head", emoji: "🎙️", tile: "rk-tile-sky", count: 96,
69
+ blurb: "Straight-to-camera monologues with punchy captioned hooks.",
70
+ thumbs: [
71
+ { emoji: "🎙️", grad: "linear-gradient(160deg,#6cc0f5,#3f9fe0)" },
72
+ { emoji: "✨", grad: "linear-gradient(155deg,#ffc9d4,#f472b6)" },
73
+ { emoji: "🗣️", grad: "linear-gradient(160deg,#8fa3ff,#4f46e5)" }
74
+ ]
75
+ },
76
+ {
77
+ slug: "explainer", label: "Explainer", emoji: "🧠", tile: "rk-tile-violet", count: 74,
78
+ blurb: "Kinetic typography and diagrams that make an idea click.",
79
+ thumbs: [
80
+ { emoji: "🧠", grad: "linear-gradient(155deg,#c4a0ee,#7c3aed)" },
81
+ { emoji: "📊", grad: "linear-gradient(150deg,#ffe08a,#38bdf8)" },
82
+ { emoji: "🔗", grad: "linear-gradient(160deg,#a5b4fc,#6366f1)" },
83
+ { emoji: "💡", grad: "linear-gradient(150deg,#ffe08a,#f59e0b)" }
84
+ ]
85
+ },
86
+ {
87
+ slug: "listicle", label: "Listicle", emoji: "🔟", tile: "rk-tile-green", count: 63,
88
+ blurb: "Countdowns, tier lists and ranked picks that keep watch time high.",
89
+ thumbs: [
90
+ { emoji: "🔟", grad: "linear-gradient(160deg,#7de0a3,#16a34a)" },
91
+ { emoji: "🏆", grad: "linear-gradient(150deg,#ffd76a,#fb7185)" },
92
+ { emoji: "⭐", grad: "linear-gradient(150deg,#ffe08a,#f97316)" }
93
+ ]
94
+ },
95
+ {
96
+ slug: "meme", label: "Meme", emoji: "😂", tile: "rk-tile-gold", count: 141,
97
+ blurb: "Recaptioned reactions and relatable brainrot for reach.",
98
+ thumbs: [
99
+ { emoji: "😂", grad: "linear-gradient(150deg,#ffb27a,#f43f5e)" },
100
+ { emoji: "🤡", grad: "linear-gradient(150deg,#ffd76a,#f97316)" },
101
+ { emoji: "🔥", grad: "linear-gradient(150deg,#fb923c,#ef4444)" },
102
+ { emoji: "💀", grad: "linear-gradient(160deg,#94a3b8,#334155)" }
103
+ ]
104
+ },
105
+ {
106
+ slug: "product", label: "Product demo", emoji: "📱", tile: "rk-tile-ink", count: 52,
107
+ blurb: "Screen-first walkthroughs that sell the feature in 30 seconds.",
108
+ thumbs: [
109
+ { emoji: "📱", grad: "linear-gradient(155deg,#4b4b4b,#171717)" },
110
+ { emoji: "🖥️", grad: "linear-gradient(160deg,#64748b,#0f172a)" },
111
+ { emoji: "⚡", grad: "linear-gradient(150deg,#ffe08a,#38bdf8)" }
112
+ ]
113
+ },
114
+ {
115
+ slug: "story", label: "Story", emoji: "📖", tile: "rk-tile-violet", count: 88,
116
+ blurb: "Narrated stories over satisfying gameplay or B-roll.",
117
+ thumbs: [
118
+ { emoji: "📖", grad: "linear-gradient(160deg,#8fa3ff,#4f46e5)" },
119
+ { emoji: "🎬", grad: "linear-gradient(160deg,#c084fc,#1e1b4b)" },
120
+ { emoji: "🎮", grad: "linear-gradient(160deg,#5eead4,#0d9488)" },
121
+ { emoji: "📜", grad: "linear-gradient(150deg,#ffd76a,#f59e0b)" }
122
+ ]
123
+ }
124
+ ];
125
+ const FORK_ICON = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.4"/><circle cx="6" cy="18" r="2.4"/><circle cx="18" cy="7" r="2.4"/><path d="M6 8.4v7.2M18 9.4c0 3-3 3.6-6 4.2"/></svg>`;
126
+ // ── feed: poster card ──────────────────────────────────────────────────────
127
+ // Deliberately calm: just the duration overlay on the poster, ONE plain muted
128
+ // meta line (handle · forks — not pills), and the single gold Fork CTA.
129
+ function posterCard(c) {
130
+ return `<article class="rk-discover-card rk-card-hover">
131
+ <div class="rk-discover-poster" style="background:${c.grad}">
132
+ <span class="rk-discover-poster-glyph" aria-hidden="true">${c.emoji}</span>
133
+ <span class="rk-discover-dur">${c.duration}</span>
134
+ <div class="rk-discover-scrim"></div>
135
+ <p class="rk-discover-hook">${c.hook}</p>
136
+ </div>
137
+ <div class="rk-discover-body">
138
+ <div class="rk-discover-titlewrap">
139
+ <h3 class="rk-discover-title">${c.title}</h3>
140
+ <p class="rk-discover-metaline">${c.handle} · ${c.forks} forks</p>
141
+ </div>
142
+ <button type="button" class="rk-btn rk-btn-gold rk-btn-sm rk-btn-block rk-discover-fork">${FORK_ICON} Fork template</button>
143
+ </div>
144
+ </article>`;
145
+ }
146
+ // ── swipe: single deck card (full-bleed poster with overlaid info) ──────────
147
+ function swipeCard(c, i) {
148
+ const z = 20 - i;
149
+ const scale = (1 - i * 0.045).toFixed(3);
150
+ const ty = i * 12;
151
+ const op = i === 0 ? 1 : i === 1 ? 0.92 : 0.82;
152
+ const hidden = i > 2 ? "display:none;" : "";
153
+ const style = `z-index:${z};transform:translateY(${ty}px) scale(${scale});opacity:${op};${hidden}`;
154
+ return `<article class="rk-discover-swipe${i === 0 ? " is-front" : ""}" data-swipe style="${style}">
155
+ <div class="rk-discover-swipe-art" style="background:${c.grad}"></div>
156
+ <span class="rk-discover-poster-glyph" aria-hidden="true">${c.emoji}</span>
157
+ <span class="rk-discover-dur">${c.duration}</span>
158
+ <div class="rk-discover-swipe-scrim"></div>
159
+ <div class="rk-discover-swipe-info">
160
+ <p class="rk-discover-hook">${c.hook}</p>
161
+ <div class="rk-discover-swipe-foot">
162
+ <div class="rk-discover-swipe-title">${c.title}</div>
163
+ <div class="rk-discover-swipe-handle">${c.handle} · ${c.forks} forks</div>
164
+ </div>
165
+ </div>
166
+ </article>`;
167
+ }
168
+ // ── categories: one browse card ─────────────────────────────────────────────
169
+ function categoryCard(g) {
170
+ const thumbs = g.thumbs
171
+ .map((t) => `<span class="rk-discover-mini" style="background:${t.grad}">${t.emoji}</span>`)
172
+ .join("");
173
+ return `<a class="rk-discover-cat rk-card-hover" href="/reskin/discover/feed" data-cat="${g.slug}">
174
+ <div class="rk-discover-cat-head">
175
+ <div class="rk-discover-cat-titlewrap">
176
+ <h3 class="rk-discover-cat-title">${g.label}</h3>
177
+ <p class="rk-discover-cat-count">${g.count} templates</p>
178
+ </div>
179
+ <span class="rk-discover-cat-arrow" aria-hidden="true">→</span>
180
+ </div>
181
+ <p class="rk-discover-cat-blurb">${g.blurb}</p>
182
+ <div class="rk-discover-cat-thumbs">${thumbs}</div>
183
+ </a>`;
184
+ }
185
+ // ── the segmented Feed / Swipe / Categories toggle (three real links) ───────
186
+ function modeToggle(active) {
187
+ const links = MODES.map((m) => {
188
+ const on = m.mode === active ? " is-active" : "";
189
+ return `<a class="rk-discover-mode${on}" href="/reskin/discover/${m.mode}"${m.mode === active ? ' aria-current="page"' : ""}>${m.label}</a>`;
190
+ }).join("");
191
+ return `<nav class="rk-discover-modes" aria-label="Discover mode">${links}</nav>`;
192
+ }
193
+ export function renderReskinDiscover(mode = "feed") {
194
+ const copy = MODE_COPY[mode];
195
+ // ── FEED ──────────────────────────────────────────────────────────────────
196
+ let modeBody = "";
197
+ if (mode === "feed") {
198
+ const cards = CARDS.map(posterCard).join("");
199
+ modeBody = `<div class="rk-discover-grid">${cards}</div>`;
200
+ }
201
+ // ── SWIPE ─────────────────────────────────────────────────────────────────
202
+ if (mode === "swipe") {
203
+ const deck = SWIPE_CARDS.map((c, i) => swipeCard(c, i)).join("");
204
+ modeBody = `
205
+ <div class="rk-discover-swipe-wrap">
206
+ <p class="rk-discover-helper">Tap <b>✓ Fork</b> to recut a template into an ad for your brand, or <b>✕ Skip</b> to see the next one.</p>
207
+ <div class="rk-discover-deck" data-deck>
208
+ ${deck}
209
+ <div class="rk-discover-empty" data-empty hidden>
210
+ <h3>You're all caught up</h3>
211
+ <p class="rk-muted">We're lining up fresh templates that match your brand.</p>
212
+ <button type="button" class="rk-btn rk-btn-ghost rk-btn-sm" data-restart>↻ Reshuffle the deck</button>
213
+ </div>
214
+ </div>
215
+ <div class="rk-discover-actions" data-actions>
216
+ <button type="button" class="rk-discover-act rk-discover-act-skip" data-swipe-skip aria-label="Skip">✕</button>
217
+ <button type="button" class="rk-discover-act rk-discover-act-fork" data-swipe-fork aria-label="Fork">✓</button>
218
+ </div>
219
+ <p class="rk-discover-act-hint" data-actions-hint>Swipe to customize into an ad for your brand.</p>
220
+ </div>`;
221
+ }
222
+ // ── CATEGORIES ────────────────────────────────────────────────────────────
223
+ if (mode === "categories") {
224
+ const cats = CATEGORIES.map(categoryCard).join("");
225
+ modeBody = `<div class="rk-discover-cats">${cats}</div>`;
226
+ }
227
+ const body = `
228
+ <main class="rk-container rk-page">
229
+ <div class="rk-page-head rk-discover-head">
230
+ <span class="rk-badge">${copy.badge}</span>
231
+ <h1 class="rk-h1">Discover</h1>
232
+ <p class="rk-sub">${copy.sub}</p>
233
+ </div>
234
+ ${modeToggle(mode)}
235
+ ${modeBody}
236
+ </main>`;
237
+ const pageCss = `
238
+ .rk-discover-head{margin-bottom:22px}
239
+
240
+ /* segmented Feed / Swipe / Categories toggle — three real links, gold active */
241
+ .rk-discover-modes{display:inline-flex;gap:4px;background:var(--rk-n-100);border:1px solid var(--rk-border);
242
+ border-radius:var(--rk-r-full);padding:4px;box-shadow:var(--rk-shadow-xs);margin-bottom:28px;max-width:100%;overflow:auto;scrollbar-width:none}
243
+ .rk-discover-modes::-webkit-scrollbar{display:none}
244
+ .rk-discover-mode{display:inline-flex;align-items:center;gap:7px;padding:9px 18px;border-radius:var(--rk-r-full);
245
+ font-size:14px;font-weight:600;color:var(--rk-n-600);white-space:nowrap;
246
+ transition:background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease)}
247
+ .rk-discover-mode:hover{color:var(--rk-ink)}
248
+ .rk-discover-mode.is-active{background:var(--rk-gold-500);color:var(--rk-ink);font-weight:700;box-shadow:var(--rk-shadow-sm)}
249
+
250
+ /* ── feed: poster grid ── */
251
+ .rk-discover-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(212px,1fr));gap:22px}
252
+ @media(max-width:520px){.rk-discover-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}}
253
+ .rk-discover-card{background:var(--rk-surface);border:1px solid var(--rk-border);border-radius:var(--rk-r-3xl);overflow:hidden;box-shadow:var(--rk-shadow-card);display:flex;flex-direction:column}
254
+ .rk-discover-poster{position:relative;aspect-ratio:9/16;display:flex;align-items:flex-end;overflow:hidden}
255
+ .rk-discover-poster-glyph{position:absolute;inset:0;z-index:0;display:grid;place-items:center;font-size:3.2rem;line-height:1;
256
+ opacity:.92;filter:drop-shadow(0 2px 10px rgba(0,0,0,.2))}
257
+ .rk-discover-dur{position:absolute;top:14px;right:12px;z-index:3;font-family:var(--rk-font-body);font-size:11.5px;font-weight:700;color:#fff;
258
+ background:rgba(0,0,0,.34);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);padding:4px 9px;border-radius:var(--rk-r-full)}
259
+ .rk-discover-scrim{position:absolute;inset:0;z-index:1;background:linear-gradient(to top,rgba(0,0,0,.5) 0%,rgba(0,0,0,.14) 38%,transparent 62%)}
260
+ .rk-discover-hook{position:relative;z-index:2;margin:0;padding:16px 15px 17px;font-family:var(--rk-font-display);font-weight:800;font-size:1.12rem;
261
+ line-height:1.12;letter-spacing:-.02em;color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.4),0 1px 2px rgba(0,0,0,.35)}
262
+ .rk-discover-body{padding:15px 16px 16px;display:grid;gap:13px}
263
+ .rk-discover-titlewrap{display:grid;gap:4px}
264
+ .rk-discover-title{font-family:var(--rk-font-display);font-size:1.02rem;font-weight:700;letter-spacing:-.01em;color:var(--rk-ink);line-height:1.2}
265
+ .rk-discover-metaline{font-size:12.5px;font-weight:500;color:var(--rk-text-muted)}
266
+ .rk-discover-fork{gap:7px}
267
+ .rk-discover-fork svg{margin-top:-1px}
268
+
269
+ /* ── swipe: single-card deck ── */
270
+ .rk-discover-swipe-wrap{display:grid;justify-items:center;gap:18px;padding:4px 0 8px}
271
+ .rk-discover-helper{font-size:14px;color:var(--rk-text-muted);text-align:center;max-width:440px}
272
+ .rk-discover-helper b{color:var(--rk-ink);font-weight:700}
273
+ .rk-discover-deck{position:relative;width:min(322px,84vw);aspect-ratio:9/16;margin:6px 0 44px}
274
+ .rk-discover-swipe{position:absolute;inset:0;border-radius:var(--rk-r-3xl);overflow:hidden;border:1px solid var(--rk-border);
275
+ box-shadow:var(--rk-shadow-xl);transform-origin:center bottom;will-change:transform;
276
+ transition:transform .38s var(--rk-ease),opacity .38s var(--rk-ease)}
277
+ .rk-discover-swipe-art{position:absolute;inset:0;z-index:0}
278
+ .rk-discover-swipe .rk-discover-poster-glyph{font-size:5rem;opacity:.9}
279
+ .rk-discover-swipe-scrim{position:absolute;inset:0;z-index:1;background:linear-gradient(to top,rgba(0,0,0,.62) 0%,rgba(0,0,0,.2) 42%,rgba(0,0,0,.06) 66%,transparent 82%)}
280
+ .rk-discover-swipe-info{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:20px 20px 22px;display:grid;gap:14px}
281
+ .rk-discover-swipe .rk-discover-hook{padding:0;font-size:1.5rem;line-height:1.08}
282
+ .rk-discover-swipe-foot{display:grid;gap:4px;min-width:0}
283
+ .rk-discover-swipe-title{font-family:var(--rk-font-display);font-weight:700;font-size:1.02rem;color:#fff;letter-spacing:-.01em;line-height:1.2}
284
+ .rk-discover-swipe-handle{font-size:12.5px;font-weight:500;color:rgba(255,255,255,.82)}
285
+
286
+ .rk-discover-empty{position:absolute;inset:0;z-index:1;display:grid;place-content:center;justify-items:center;gap:8px;text-align:center;padding:28px;
287
+ background:#fff;border:1px solid var(--rk-border);border-radius:var(--rk-r-3xl);box-shadow:var(--rk-shadow-card)}
288
+ .rk-discover-empty h3{font-family:var(--rk-font-display);font-size:var(--rk-text-xl);color:var(--rk-ink)}
289
+ .rk-discover-empty p{font-size:13.5px;max-width:230px}
290
+ .rk-discover-empty .rk-btn{margin-top:8px}
291
+
292
+ .rk-discover-actions{display:flex;align-items:center;justify-content:center;gap:20px}
293
+ .rk-discover-act{width:60px;height:60px;flex:none;border-radius:var(--rk-r-full);border:1px solid var(--rk-border);background:#fff;
294
+ display:grid;place-items:center;font-size:24px;font-weight:700;cursor:pointer;box-shadow:var(--rk-shadow-md);
295
+ transition:transform var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease)}
296
+ .rk-discover-act:hover{transform:translateY(-2px);box-shadow:var(--rk-shadow-lg)}
297
+ .rk-discover-act-skip{color:var(--rk-n-500)}
298
+ .rk-discover-act-skip:hover{color:var(--rk-red);border-color:var(--rk-red-tint)}
299
+ .rk-discover-act-fork{background:var(--rk-gold-500);color:var(--rk-ink);border-color:transparent}
300
+ .rk-discover-act-fork:hover{background:var(--rk-gold-700);box-shadow:0 8px 22px -6px rgba(252,185,0,.6)}
301
+ .rk-discover-act-hint{font-size:12.5px;font-weight:600;color:var(--rk-text-faint);text-align:center;letter-spacing:.01em}
302
+
303
+ /* ── categories: browse-by-format cards ── */
304
+ .rk-discover-cats{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:20px}
305
+ @media(max-width:560px){.rk-discover-cats{grid-template-columns:1fr}}
306
+ .rk-discover-cat{display:flex;flex-direction:column;gap:14px;background:var(--rk-surface);border:1px solid var(--rk-border);
307
+ border-radius:var(--rk-r-3xl);padding:22px;box-shadow:var(--rk-shadow-card);color:inherit}
308
+ .rk-discover-cat-head{display:flex;align-items:center;gap:14px}
309
+ .rk-discover-cat-titlewrap{flex:1;min-width:0;display:grid;gap:2px}
310
+ .rk-discover-cat-title{font-family:var(--rk-font-display);font-size:var(--rk-text-lg);font-weight:700;color:var(--rk-ink);letter-spacing:-.01em}
311
+ .rk-discover-cat-count{font-size:12.5px;font-weight:600;color:var(--rk-text-muted)}
312
+ .rk-discover-cat-arrow{flex:none;font-size:18px;color:var(--rk-n-400);transition:transform var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
313
+ .rk-discover-cat:hover .rk-discover-cat-arrow{transform:translateX(3px);color:var(--rk-gold-700)}
314
+ .rk-discover-cat-blurb{font-size:13px;color:var(--rk-text-muted);line-height:1.45}
315
+ .rk-discover-cat-thumbs{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:8px}
316
+ .rk-discover-mini{aspect-ratio:9/16;border-radius:var(--rk-r-lg);display:grid;place-items:center;font-size:20px;box-shadow:var(--rk-shadow-xs)}
317
+ `;
318
+ const script = `
319
+ (function(){
320
+ var root=document;
321
+ // ── swipe: advance the deck ──
322
+ var deck=root.querySelector('[data-deck]');
323
+ if(deck){
324
+ var empty=deck.querySelector('[data-empty]');
325
+ var actions=root.querySelector('[data-actions]');
326
+ var hint=root.querySelector('[data-actions-hint]');
327
+ var all=Array.prototype.slice.call(deck.querySelectorAll('[data-swipe]'));
328
+ var live=all.slice();
329
+ function restack(){
330
+ live.forEach(function(c,i){
331
+ if(i>2){c.style.display='none';return;}
332
+ c.style.display='';
333
+ c.style.zIndex=String(20-i);
334
+ c.style.transform='translateY('+(i*12)+'px) scale('+(1-i*0.045).toFixed(3)+')';
335
+ c.style.opacity=i===0?'1':(i===1?'0.92':'0.82');
336
+ c.classList.toggle('is-front',i===0);
337
+ });
338
+ if(live.length===0){
339
+ if(empty)empty.hidden=false;
340
+ if(actions)actions.style.visibility='hidden';
341
+ if(hint)hint.style.visibility='hidden';
342
+ }
343
+ }
344
+ function advance(dir){
345
+ var front=live.shift();
346
+ if(!front)return;
347
+ front.classList.remove('is-front');
348
+ front.style.transform='translateX('+(dir==='fork'?460:-460)+'px) rotate('+(dir==='fork'?15:-15)+'deg)';
349
+ front.style.opacity='0';
350
+ window.setTimeout(function(){front.style.display='none';},360);
351
+ restack();
352
+ }
353
+ var skip=root.querySelector('[data-swipe-skip]');
354
+ var fork=root.querySelector('[data-swipe-fork]');
355
+ if(skip)skip.addEventListener('click',function(){advance('skip')});
356
+ if(fork)fork.addEventListener('click',function(){advance('fork')});
357
+ var restart=deck.querySelector('[data-restart]');
358
+ if(restart)restart.addEventListener('click',function(){
359
+ live=all.slice();
360
+ all.forEach(function(c){c.style.transition='none';c.style.opacity='';c.style.transform='';});
361
+ // force reflow so the reset transforms take without animating
362
+ void deck.offsetWidth;
363
+ all.forEach(function(c){c.style.transition='';});
364
+ if(empty)empty.hidden=true;
365
+ if(actions)actions.style.visibility='';
366
+ if(hint)hint.style.visibility='';
367
+ restack();
368
+ });
369
+ }
370
+ })();`;
371
+ return reskinDocument({
372
+ title: `vidfarm reskin — Discover (${mode})`,
373
+ description: "Reskinned vidfarm Discover — Feed / Swipe / Categories modes, poster cards, warm sunny SaaS.",
374
+ activeSlug: "discover",
375
+ pageCss,
376
+ body,
377
+ script
378
+ });
379
+ }
380
+ //# sourceMappingURL=discover-page.js.map
@@ -0,0 +1,74 @@
1
+ // ─────────────────────────────────────────────────────────────────────────
2
+ // /reskin/* shared document skeleton + chrome (LEFT SIDEBAR nav)
3
+ // ─────────────────────────────────────────────────────────────────────────
4
+ // Every reskin page module calls `reskinDocument(...)` to wrap its body in a
5
+ // full, standalone HTML document that loads ONLY the shared reskin stylesheet
6
+ // (theme.ts). This is what guarantees CSS isolation from the live app pages.
7
+ //
8
+ // The app chrome is a fixed LEFT sidebar nav (Notion/Linear-style) + a content
9
+ // column. Page modules stay pure: they build a `body` string (their own
10
+ // `<main class="rk-container rk-page">…`) and return `reskinDocument({...})`.
11
+ // They never emit the sidebar and never import from app.ts.
12
+ //
13
+ // NOTE: the sidebar uses `.rk-sidebar*` class names — deliberately NOT `.rk-side*`
14
+ // (that belongs to the settings page's own inner tab rail) so the two never clash.
15
+ import { RESKIN_CSS, RESKIN_FONT_LINKS, RESKIN_PAGES } from "./theme.js";
16
+ /** Minimal HTML escaper so reskin modules don't need to import app helpers. */
17
+ export function rkEscape(value) {
18
+ return String(value ?? "")
19
+ .replace(/&/g, "&amp;")
20
+ .replace(/</g, "&lt;")
21
+ .replace(/>/g, "&gt;")
22
+ .replace(/"/g, "&quot;")
23
+ .replace(/'/g, "&#39;");
24
+ }
25
+ function renderSidebar(activeSlug) {
26
+ const items = RESKIN_PAGES.map((p) => {
27
+ const active = p.slug === activeSlug ? " is-active" : "";
28
+ return `<a class="rk-sidebar-item${active}" href="/reskin/${p.slug}"${p.slug === activeSlug ? ` aria-current="page"` : ""}>${p.label}</a>`;
29
+ }).join("");
30
+ const live = RESKIN_PAGES.find((p) => p.slug === activeSlug)?.live ?? "/";
31
+ const homeActive = activeSlug === null ? " is-active" : "";
32
+ return `<aside class="rk-sidebar">
33
+ <a class="rk-sidebar-brand" href="/reskin"><span class="rk-brand-mark">V</span>vidfarm<span class="rk-brand-tag">reskin</span></a>
34
+ <nav class="rk-sidebar-nav" aria-label="Reskin pages">
35
+ <a class="rk-sidebar-item${homeActive}" href="/reskin">Gallery</a>
36
+ <div class="rk-sidebar-sep"></div>
37
+ ${items}
38
+ </nav>
39
+ <div class="rk-sidebar-foot">
40
+ <a class="rk-sidebar-foot-link" href="${rkEscape(live)}">↗ Compare with live page</a>
41
+ <a class="rk-btn rk-btn-ink rk-btn-sm rk-sidebar-cta" href="/">Live site <span class="rk-arrow">→</span></a>
42
+ </div>
43
+ </aside>`;
44
+ }
45
+ /** Wrap page content in a complete standalone reskin HTML document. */
46
+ export function reskinDocument(input) {
47
+ const chrome = input.chrome ?? "full";
48
+ const active = input.activeSlug ?? null;
49
+ const pageCss = input.pageCss ? `\n/* page-scoped */\n${input.pageCss}\n` : "";
50
+ const script = input.script ? `\n<script>${input.script}</script>` : "";
51
+ const shell = chrome === "full"
52
+ ? `<div class="rk-app">
53
+ ${renderSidebar(active)}
54
+ <div class="rk-content">
55
+ ${input.body}
56
+ </div>
57
+ </div>`
58
+ : input.body;
59
+ return `<!doctype html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="utf-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1">
64
+ <title>${rkEscape(input.title)}</title>
65
+ <meta name="description" content="${rkEscape(input.description ?? input.title)}">
66
+ ${RESKIN_FONT_LINKS}
67
+ <style>${RESKIN_CSS}${pageCss}</style>
68
+ </head>
69
+ <body class="rk-root${chrome === "full" ? " rk-has-sidebar" : ""}">
70
+ ${shell}${script}
71
+ </body>
72
+ </html>`;
73
+ }
74
+ //# sourceMappingURL=document.js.map