@mevdragon/vidfarm-devcli 0.17.0 → 0.18.1

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 (55) hide show
  1. package/.agents/skills/vidfarm-media/SKILL.md +43 -6
  2. package/SKILL.director.md +37 -23
  3. package/SKILL.platform.md +6 -6
  4. package/demo/dist/app.js +69 -69
  5. package/demo/dist/favicon.ico +0 -0
  6. package/dist/src/app.js +1832 -213
  7. package/dist/src/cli.js +238 -17
  8. package/dist/src/devcli/clip-store.js +29 -2
  9. package/dist/src/devcli/clips.js +116 -73
  10. package/dist/src/devcli/composition-edit.js +262 -0
  11. package/dist/src/devcli/timeline-edit.js +283 -0
  12. package/dist/src/editor-chat.js +29 -6
  13. package/dist/src/frontend/discover-client.js +130 -0
  14. package/dist/src/frontend/discover-store.js +23 -0
  15. package/dist/src/frontend/file-directory.js +744 -0
  16. package/dist/src/frontend/template-editor-chat.js +22 -19
  17. package/dist/src/landing-page.js +24 -7
  18. package/dist/src/page-shell.js +25 -1
  19. package/dist/src/reskin/agency-page.js +214 -170
  20. package/dist/src/reskin/calendar-page.js +503 -187
  21. package/dist/src/reskin/chat-page.js +739 -299
  22. package/dist/src/reskin/discover-page.js +1450 -279
  23. package/dist/src/reskin/document.js +1392 -16
  24. package/dist/src/reskin/help-page.js +139 -100
  25. package/dist/src/reskin/index-page.js +1 -1
  26. package/dist/src/reskin/inpaint-page.js +547 -0
  27. package/dist/src/reskin/job-runs-page.js +355 -127
  28. package/dist/src/reskin/library-page.js +1188 -317
  29. package/dist/src/reskin/login-page.js +124 -87
  30. package/dist/src/reskin/pricing-page.js +197 -166
  31. package/dist/src/reskin/settings-page.js +566 -187
  32. package/dist/src/reskin/theme.js +434 -17
  33. package/dist/src/services/clip-curation/gemini.js +5 -0
  34. package/dist/src/services/clip-curation/hunt.js +79 -1
  35. package/dist/src/services/clip-curation/index.js +2 -1
  36. package/dist/src/services/clip-curation/local-agent.js +4 -3
  37. package/dist/src/services/clip-curation/media-select.js +85 -0
  38. package/dist/src/services/clip-curation/query.js +5 -1
  39. package/dist/src/services/clip-curation/refine.js +50 -20
  40. package/dist/src/services/clip-curation/scan.js +10 -3
  41. package/dist/src/services/clip-curation/taxonomy.js +3 -1
  42. package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
  43. package/dist/src/services/clip-records.js +14 -1
  44. package/dist/src/services/clip-search.js +43 -13
  45. package/dist/src/services/file-directory.js +114 -0
  46. package/dist/src/services/storage.js +24 -1
  47. package/dist/src/services/upstream.js +5 -5
  48. package/dist/src/template-editor-shell.js +16 -2
  49. package/package.json +1 -1
  50. package/public/assets/discover-client-app.js +1 -0
  51. package/public/assets/file-directory-app.js +2 -0
  52. package/public/assets/homepage-client-app.js +12 -12
  53. package/public/assets/page-runtime-client-app.js +24 -24
  54. package/src/assets/favicon.ico +0 -0
  55. package/src/assets/logo-vidfarm.png +0 -0
@@ -1,127 +1,173 @@
1
- // /reskin/pricing — plans + wallet credit top-up, reskinned into the farmville
2
- // warm-sunny design system using ONLY shared rk-* primitives + a page-scoped
3
- // rk-pricing-* CSS block + a small self-contained module script.
1
+ // /reskin/pricing — MIGRATED to real production billing.
4
2
  //
5
- // Faithfully mirrors the live vidfarm pricing surface (src/account-pages-legacy.ts):
6
- // Paid-plan tiers Standard $99/mo ("Best value") and Agency $1200/mo, with
7
- // a Free/local tier added to make the ladder read as a real pricing page.
8
- // The credit wallet model top-ups at $0.03 / credit ($0.02 margin,
9
- // $0.01 ecosystem liability), "you're only charged for compute BYO AI
10
- // keys are never billed."
11
- // Renders with representative SAMPLE data so it can be viewed without auth. The
12
- // gold "joy" accent is reserved for the hero plan, the popular credit pack, and
13
- // the primary CTAs; every other action uses the charcoal/ghost workhorses.
3
+ // This mirrors the live vidfarm billing surface (src/account-pages-legacy.ts
4
+ // `renderPricingPage` + the settings wallet flow + the help page). It accepts the
5
+ // SAME input the live route builds (`Parameters<typeof renderPricingPage>[0]` =
6
+ // `{ email }`), which the live app renders post-login as a paywall gate
7
+ // ("Paid plan required {email} does not have an active plan").
8
+ //
9
+ // REAL facts wired here (nothing invented):
10
+ // The one real subscription tier is **$99/month** (the live "Current Offer").
11
+ // On the live page the Buy button is DISABLED ("Checkout is not enabled yet"),
12
+ // so activation happens by contacting operator@zoom-gtm.com — that is the real
13
+ // path today, so the Pro CTA is that mailto (email pre-filled).
14
+ // • Local `vidfarm serve` renders are free → the real free tier.
15
+ // • The credit wallet: every credit is $0.03 of compute; top-ups go to the REAL
16
+ // Stripe checkout https://buy.stripe.com/3cIfZggERgLsfVzfKR53O0H with the
17
+ // email pre-filled (exactly the URL + prefill the live settings modal uses).
18
+ // • Manage/cancel the subscription via the REAL Stripe billing portal
19
+ // https://billing.stripe.com/p/login/fZecQubkxcwOgDuaEE ; 90-day refunds via
20
+ // operator@zoom-gtm.com — both straight from the live help page.
21
+ // The gold "joy" accent is reserved for the hero plan + the primary CTAs; every
22
+ // other action uses the charcoal/ghost workhorses. ✓ feature checkmarks kept.
14
23
  import { reskinDocument, rkEscape } from "./document.js";
15
- const PLANS = [
16
- {
17
- id: "free",
18
- kicker: "Get started",
19
- name: "Starter",
20
- monthlyUsd: 0,
21
- yearlyUsd: 0,
22
- priceSuffix: "forever",
23
- pitch: "Fork templates, recut with your own footage, and render locally on a vidfarm serve box no card required.",
24
- features: [
25
- "1 publishing channel",
26
- "Local in-process renders (free)",
27
- "Community template feed",
28
- "Bring your own AI keys",
29
- "Clip library & My Files"
30
- ],
31
- cta: "Start free",
32
- ctaStyle: "ghost"
33
- },
34
- {
35
- id: "standard",
36
- kicker: "Best value",
37
- name: "Standard",
38
- monthlyUsd: 99,
39
- yearlyUsd: 79,
40
- priceSuffix: "/mo",
41
- pitch: "For operators publishing consistently — more channels, unlimited cloud video output, and Swipe-mode auto-recut.",
42
- features: [
43
- "10 publishing channels",
44
- "Unlimited cloud renders",
45
- "Swipe mode & AI recaption",
46
- "Bring your own AI keys",
47
- "Wallet top-ups at $0.03 / credit",
48
- "Email support"
49
- ],
50
- cta: "Go Standard",
51
- ctaStyle: "gold",
52
- featured: true
53
- },
54
- {
55
- id: "agency",
56
- kicker: "For teams",
57
- name: "Agency",
58
- monthlyUsd: 1200,
59
- yearlyUsd: 960,
60
- priceSuffix: "/mo",
61
- pitch: "For studios running many clients — unlimited capacity, per-client key setups, and hands-on support.",
62
- features: [
63
- "Unlimited channels",
64
- "Unlimited videos",
65
- "Per-client API-key workspaces",
66
- "Priority Lambda render lane",
67
- "White-label client library",
68
- "Live call support"
69
- ],
70
- cta: "Talk to sales",
71
- ctaStyle: "ink"
72
- }
73
- ];
74
- const PACKS = [
75
- { label: "Starter", usd: 20, credits: 666, bonus: 0, note: "~20 cloud renders" },
76
- { label: "Studio", usd: 50, credits: 1666, bonus: 84, note: "+5% bonus credits", popular: true },
77
- { label: "Scale", usd: 100, credits: 3333, bonus: 334, note: "+10% bonus credits" },
78
- { label: "Agency", usd: 250, credits: 8333, bonus: 1167, note: "+14% bonus credits" }
24
+ // ── real production constants (copied from the live app) ──
25
+ const SUPPORT_EMAIL = "operator@zoom-gtm.com";
26
+ const WALLET_CHECKOUT_BASE = "https://buy.stripe.com/3cIfZggERgLsfVzfKR53O0H";
27
+ const BILLING_PORTAL_URL = "https://billing.stripe.com/p/login/fZecQubkxcwOgDuaEE";
28
+ const PLAN_PRICE_USD = 99;
29
+ const CREDIT_USD = 0.03;
30
+ /** Real wallet top-up: the live Stripe checkout with the email pre-filled (the
31
+ * live settings modal also registers a server-side client_reference_id; a static
32
+ * pricing page can't, so it links to the same checkout with the email prefill).
33
+ * Email may be empty (the route currently passes none) — then no prefill param. */
34
+ function walletCheckoutUrl(email) {
35
+ return email
36
+ ? `${WALLET_CHECKOUT_BASE}?prefilled_email=${encodeURIComponent(email)}`
37
+ : WALLET_CHECKOUT_BASE;
38
+ }
39
+ function mailto(email, subject, bodyLead) {
40
+ const body = email ? `${bodyLead} My account email: ${email}` : bodyLead;
41
+ return `mailto:${SUPPORT_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
42
+ }
43
+ const CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12.5l5 5L20 6"/></svg>`;
44
+ function usd(n) {
45
+ return `$${n.toLocaleString("en-US")}`;
46
+ }
47
+ function credits(n) {
48
+ return n.toLocaleString("en-US");
49
+ }
50
+ function creditsForUsd(n) {
51
+ return Math.floor(n / CREDIT_USD);
52
+ }
53
+ function buildPlans(email) {
54
+ return [
55
+ {
56
+ id: "local",
57
+ kicker: "Get started",
58
+ name: "Local",
59
+ price: { kind: "free", sub: "forever · vidfarm serve" },
60
+ pitch: "Fork templates, recut with your own footage, and render in-process on your own machine — no card required.",
61
+ features: [
62
+ "Local in-process renders (free)",
63
+ "Fork & recut the community feed",
64
+ "Bring your own AI keys",
65
+ "Clip library & My Files",
66
+ "1 publishing channel"
67
+ ],
68
+ ctaLabel: "Start free",
69
+ ctaHref: "/login",
70
+ ctaStyle: "ghost"
71
+ },
72
+ {
73
+ id: "pro",
74
+ kicker: "Full access",
75
+ name: "Pro",
76
+ price: { kind: "monthly", usd: PLAN_PRICE_USD, sub: "Billed monthly · cancel anytime · 90-day money-back" },
77
+ pitch: "For operators publishing consistently — unlimited cloud output, Swipe-mode auto-recut, and every publishing channel.",
78
+ features: [
79
+ "Everything in Local",
80
+ "Unlimited cloud renders",
81
+ "Swipe mode & AI recaption",
82
+ "FlockPoster + email channels",
83
+ "Wallet top-ups at $0.03 / credit",
84
+ "Email support"
85
+ ],
86
+ ctaLabel: "Get access",
87
+ ctaHref: mailto(email, "Activate my Vidfarm Pro plan", "I'd like to activate the $99/mo Vidfarm Pro plan."),
88
+ ctaStyle: "gold",
89
+ featured: true
90
+ },
91
+ {
92
+ id: "agency",
93
+ kicker: "For teams",
94
+ name: "Agency",
95
+ price: { kind: "custom", label: "Custom", sub: "Priced per workspace" },
96
+ pitch: "For studios running many clients — separate client workspaces, per-client keys and wallets, and hands-on onboarding.",
97
+ features: [
98
+ "Multiple client workspaces",
99
+ "Per-client AI keys & wallets",
100
+ "Team seats",
101
+ "Shared credit billing",
102
+ "Hands-on onboarding"
103
+ ],
104
+ ctaLabel: "Talk to sales",
105
+ ctaHref: mailto(email, "Vidfarm for agencies", "I'd like to talk about Vidfarm agency workspaces."),
106
+ ctaStyle: "ink"
107
+ }
108
+ ];
109
+ }
110
+ // Example top-up amounts. Credit counts are the HONEST $0.03/credit math — no
111
+ // invented bonus tiers. Each links to the same real Stripe checkout (the amount
112
+ // is confirmed on Stripe's hosted page).
113
+ const CREDIT_EXAMPLES = [
114
+ { usd: 20 },
115
+ { usd: 50, recommended: true },
116
+ { usd: 100 },
117
+ { usd: 250 }
79
118
  ];
80
119
  const FAQS = [
81
120
  {
82
121
  q: "What exactly is a credit?",
83
- a: "A credit is $0.03 of compute. Renders, clip scans, and decompose passes draw from your wallet. Bring-your-own AI keys are never billed by vidfarm."
122
+ a: "A credit is $0.03 of compute. Cloud renders, clip scans, and decompose passes draw from your wallet. Bring-your-own AI keys are never billed by vidfarm — you only pay for compute you actually run."
84
123
  },
85
124
  {
86
125
  q: "Do credits expire?",
87
- a: "No. Wallet credits roll over forever and the balance auto-scales to zero — you only ever pay for compute you actually run."
126
+ a: "No. Wallet credits roll over forever and the balance auto-scales to zero — nothing is charged while you're idle."
88
127
  },
89
128
  {
90
129
  q: "Can I set spend limits?",
91
130
  a: "Yes. Cap usage with per-hour and per-day credit rate limits so long-lived render jobs never surprise you."
92
131
  },
93
132
  {
94
- q: "Local renders are really free?",
95
- a: "Yep. A vidfarm serve box renders in-process on your machine with no charge — perfect for reusing already-decomposed templates."
133
+ q: "Are local renders really free?",
134
+ a: "Yep. A vidfarm serve box renders in-process on your own machine at no charge — perfect for reusing already-decomposed templates."
135
+ },
136
+ {
137
+ q: "How do I start the $99 / mo plan?",
138
+ a: `Self-serve subscription checkout is rolling out. To activate the Pro plan today, email ${SUPPORT_EMAIL} from your account address and we'll switch it on.`
139
+ },
140
+ {
141
+ q: "Can I cancel or get a refund?",
142
+ a: `Cancel anytime in the Stripe billing portal. We also offer 90-day refunds — email ${SUPPORT_EMAIL} and mention the address you used at Stripe checkout so we can find your subscription quickly.`
96
143
  }
97
144
  ];
98
- const CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12.5l5 5L20 6"/></svg>`;
99
- function usd(n) {
100
- return `$${n.toLocaleString("en-US")}`;
101
- }
102
- function credits(n) {
103
- return n.toLocaleString("en-US");
145
+ function priceBlock(p) {
146
+ if (p.price.kind === "free") {
147
+ return `<div class="rk-pricing-price"><span class="rk-pricing-amount">Free</span></div>
148
+ <p class="rk-pricing-price-sub">${rkEscape(p.price.sub)}</p>`;
149
+ }
150
+ if (p.price.kind === "custom") {
151
+ return `<div class="rk-pricing-price"><span class="rk-pricing-amount rk-pricing-amount-sm">${rkEscape(p.price.label ?? "Custom")}</span></div>
152
+ <p class="rk-pricing-price-sub">${rkEscape(p.price.sub)}</p>`;
153
+ }
154
+ return `<div class="rk-pricing-price">
155
+ <span class="rk-pricing-cur">$</span>
156
+ <span class="rk-pricing-amount">${p.price.usd}</span>
157
+ <span class="rk-pricing-suffix">/mo</span>
158
+ </div>
159
+ <p class="rk-pricing-price-sub">${rkEscape(p.price.sub)}</p>`;
104
160
  }
105
161
  function planCard(p) {
106
162
  const featured = p.featured ? " rk-pricing-plan-featured" : "";
107
- const badge = p.featured
108
- ? `<span class="rk-pricing-most">Most popular</span>`
109
- : "";
163
+ const badge = p.featured ? `<span class="rk-pricing-most">Most popular</span>` : "";
110
164
  const checkTint = p.featured ? "rk-tile-gold" : "rk-tile-green";
111
165
  const features = p.features.map((f) => `
112
166
  <li class="rk-pricing-feat">
113
167
  <span class="rk-tile rk-tile-sm ${checkTint} rk-pricing-check">${CHECK}</span>
114
168
  <span>${rkEscape(f)}</span>
115
169
  </li>`).join("");
116
- const priceBlock = p.monthlyUsd === 0
117
- ? `<div class="rk-pricing-price"><span class="rk-pricing-amount" data-rk-price-free>Free</span></div>
118
- <p class="rk-pricing-price-sub">${rkEscape(p.priceSuffix)}</p>`
119
- : `<div class="rk-pricing-price">
120
- <span class="rk-pricing-cur">$</span>
121
- <span class="rk-pricing-amount" data-rk-price data-rk-monthly="${p.monthlyUsd}" data-rk-yearly="${p.yearlyUsd}">${p.monthlyUsd}</span>
122
- <span class="rk-pricing-suffix">${rkEscape(p.priceSuffix)}</span>
123
- </div>
124
- <p class="rk-pricing-price-sub" data-rk-price-note><span class="rk-strike">${usd(p.monthlyUsd)}</span> billed yearly · save 20%</p>`;
170
+ const ext = p.ctaExternal ? ` target="_blank" rel="noreferrer"` : "";
125
171
  return `
126
172
  <article class="rk-card rk-card-hover rk-pricing-plan${featured}">
127
173
  ${badge}
@@ -132,36 +178,37 @@ function planCard(p) {
132
178
  <h3>${rkEscape(p.name)}</h3>
133
179
  </div>
134
180
  </div>
135
- ${priceBlock}
181
+ ${priceBlock(p)}
136
182
  <p class="rk-pricing-pitch">${rkEscape(p.pitch)}</p>
137
- <a class="rk-btn rk-btn-${p.ctaStyle} rk-btn-block rk-btn-lg" href="#">${rkEscape(p.cta)}<span class="rk-arrow">→</span></a>
183
+ <a class="rk-btn rk-btn-${p.ctaStyle} rk-btn-block rk-btn-lg" href="${rkEscape(p.ctaHref)}"${ext}>${rkEscape(p.ctaLabel)}<span class="rk-arrow">→</span></a>
138
184
  <ul class="rk-pricing-feats">${features}</ul>
139
185
  </article>`;
140
186
  }
141
- function packCard(p) {
142
- const total = p.credits + p.bonus;
143
- const popular = p.popular ? " rk-pricing-pack-popular" : "";
144
- const flag = p.popular ? `<span class="rk-pill rk-pill-gold rk-pricing-pack-flag">Most popular</span>` : "";
145
- const bonusPill = p.bonus > 0
146
- ? `<span class="rk-pill rk-pill-green rk-pricing-pack-bonus">+${credits(p.bonus)} bonus</span>`
147
- : `<span class="rk-pill rk-pricing-pack-bonus">base rate</span>`;
187
+ function packCard(email, pack) {
188
+ const total = creditsForUsd(pack.usd);
189
+ const popular = pack.recommended ? " rk-pricing-pack-popular" : "";
190
+ const flag = pack.recommended ? `<span class="rk-pill rk-pill-gold rk-pricing-pack-flag">Recommended</span>` : "";
191
+ const checkoutUrl = walletCheckoutUrl(email);
148
192
  return `
149
193
  <article class="rk-pricing-pack${popular}">
150
194
  ${flag}
151
195
  <div class="rk-pricing-pack-top">
152
- <span class="rk-tile rk-tile-sm ${p.popular ? "rk-tile-gold" : "rk-tile-coral"} rk-pricing-mono" aria-hidden="true">${rkEscape(p.label.charAt(0))}</span>
153
- <span class="rk-pricing-pack-label">${rkEscape(p.label)}</span>
196
+ <span class="rk-tile rk-tile-sm ${pack.recommended ? "rk-tile-gold" : "rk-tile-coral"} rk-pricing-mono" aria-hidden="true">${rkEscape(usd(pack.usd).charAt(0))}</span>
197
+ <span class="rk-pricing-pack-label">${rkEscape(usd(pack.usd))} top-up</span>
154
198
  </div>
155
199
  <div class="rk-pricing-pack-credits">${credits(total)}<span>credits</span></div>
156
- ${bonusPill}
157
- <div class="rk-pricing-pack-price">${usd(p.usd)}<span>one-time</span></div>
158
- <p class="rk-pricing-pack-note">${rkEscape(p.note)}</p>
159
- <button type="button" class="rk-btn ${p.popular ? "rk-btn-gold" : "rk-btn-ghost"} rk-btn-block rk-btn-sm">Top up ${usd(p.usd)}</button>
200
+ <span class="rk-pill rk-pricing-pack-bonus">at $0.03 / credit</span>
201
+ <p class="rk-pricing-pack-note">Credits never expire · roll over forever</p>
202
+ <a class="rk-btn ${pack.recommended ? "rk-btn-gold" : "rk-btn-ghost"} rk-btn-block rk-btn-sm" href="${rkEscape(checkoutUrl)}" target="_blank" rel="noreferrer">Top up ${rkEscape(usd(pack.usd))}</a>
160
203
  </article>`;
161
204
  }
162
- export function renderReskinPricing() {
163
- const planCards = PLANS.map(planCard).join("");
164
- const packCards = PACKS.map(packCard).join("");
205
+ export function renderReskinPricing(input) {
206
+ // Runtime-safe: the live route renders this post-login with the customer email;
207
+ // if none is supplied, email-specific bits degrade gracefully (no prefill).
208
+ const email = (input?.email ?? "").trim();
209
+ const planCards = buildPlans(email).map(planCard).join("");
210
+ const packCards = CREDIT_EXAMPLES.map((p) => packCard(email, p)).join("");
211
+ const checkoutUrl = walletCheckoutUrl(email);
165
212
  const faqCards = FAQS.map((f) => `
166
213
  <div class="rk-card rk-card-pad-sm rk-pricing-faq">
167
214
  <h3>${rkEscape(f.q)}</h3>
@@ -171,36 +218,42 @@ export function renderReskinPricing() {
171
218
  "Cancel anytime",
172
219
  "Transparent per-render billing",
173
220
  "BYO AI keys — never billed",
174
- "Auto-scales to zero"
175
- ].map((t) => `<span class="rk-pill rk-pricing-trust">${t}</span>`).join("");
221
+ "90-day money-back"
222
+ ].map((t) => `<span class="rk-pill rk-pricing-trust">${rkEscape(t)}</span>`).join("");
223
+ const demoHref = mailto(email, "Vidfarm demo", "I'd like a Vidfarm demo.");
176
224
  const body = `
177
225
  <main class="rk-container rk-page rk-pricing">
178
226
 
179
227
  <!-- ── intro ── -->
180
228
  <section class="rk-pricing-intro">
181
- <span class="rk-badge">Simple, sunny pricing</span>
182
229
  <h1 class="rk-pricing-title">Ship a week of content<br><span class="rk-pricing-title-accent">in an afternoon.</span></h1>
183
- <p class="rk-pricing-lede">Start free and render locally. Upgrade when you're publishing daily. Top up a credit wallet for cloud compute — only ever pay for what you render.</p>
184
- <div class="rk-pricing-toggle" role="group" aria-label="Billing period">
185
- <button type="button" class="rk-pricing-toggle-btn is-active" data-rk-period="monthly">Monthly</button>
186
- <button type="button" class="rk-pricing-toggle-btn" data-rk-period="yearly">Yearly <span class="rk-pill rk-pill-gold rk-pricing-save">Save 20%</span></button>
187
- </div>
230
+ <p class="rk-pricing-lede">Start free and render locally. Go Pro for unlimited cloud output. Top up a credit wallet for compute — only ever pay for what you render.</p>
231
+ ${email
232
+ ? `<p class="rk-pricing-signed">Signed in as <strong>${rkEscape(email)}</strong> — no active plan yet. Pick one below to unlock cloud rendering.</p>`
233
+ : ""}
188
234
  </section>
189
235
 
190
236
  <!-- ── plan cards ── -->
191
237
  <section class="rk-pricing-plans">${planCards}</section>
192
238
 
239
+ <p class="rk-pricing-manage">Already on a plan? <a href="${rkEscape(BILLING_PORTAL_URL)}" target="_blank" rel="noreferrer">Manage billing, invoices &amp; cancellation in the Stripe portal <span class="rk-arrow">↗</span></a></p>
240
+
193
241
  <!-- ── credit top-up strip ── -->
194
242
  <section class="rk-card rk-pricing-credits">
195
243
  <div class="rk-pricing-credits-head">
196
244
  <span class="rk-eyebrow">Credit wallet</span>
197
245
  <h2>Top up credits, pay only for compute</h2>
198
246
  <p class="rk-muted">Every credit is <strong>$0.03</strong> of render compute. Cloud renders, clip scans and decompose passes draw from your wallet — bring-your-own AI keys are never billed. Balance rolls over forever and auto-scales to zero.</p>
247
+ <div class="rk-row rk-wrap rk-pricing-credits-cta">
248
+ <a class="rk-btn rk-btn-gold rk-btn-lg" href="${rkEscape(checkoutUrl)}" target="_blank" rel="noreferrer">Top up wallet<span class="rk-arrow">→</span></a>
249
+ <span class="rk-hint">Secure Stripe checkout${email ? ` · pre-filled for ${rkEscape(email)}` : ""}</span>
250
+ </div>
199
251
  </div>
200
252
  <div class="rk-pricing-packs">${packCards}</div>
201
253
  <div class="rk-pricing-credits-foot">
202
- <span class="rk-pill rk-pill-sky">Set per-hour & per-day spend caps</span>
254
+ <span class="rk-pill rk-pill-sky">Set per-hour &amp; per-day spend caps</span>
203
255
  <span class="rk-pill">Local <code>vidfarm serve</code> renders are free</span>
256
+ <span class="rk-pill">Choose any amount at checkout</span>
204
257
  </div>
205
258
  </section>
206
259
 
@@ -220,10 +273,10 @@ export function renderReskinPricing() {
220
273
  <div class="rk-pricing-cta-inner">
221
274
  <span class="rk-badge rk-pricing-cta-badge">Ready when you are</span>
222
275
  <h2 class="rk-pricing-cta-title">Make your first ad today.</h2>
223
- <p class="rk-pricing-cta-sub">Fork a template, recut it with your footage, and render — free on your own machine. Upgrade only when you're ready to scale.</p>
276
+ <p class="rk-pricing-cta-sub">Fork a template, recut it with your footage, and render — free on your own machine. Go Pro only when you're ready to scale.</p>
224
277
  <div class="rk-row rk-wrap rk-pricing-cta-btns">
225
- <a class="rk-btn rk-btn-ink rk-btn-lg" href="#">Start free<span class="rk-arrow">→</span></a>
226
- <a class="rk-btn rk-btn-ghost rk-btn-lg rk-pricing-cta-ghost" href="#">Book a demo</a>
278
+ <a class="rk-btn rk-btn-ink rk-btn-lg" href="/login">Start free<span class="rk-arrow">→</span></a>
279
+ <a class="rk-btn rk-btn-ghost rk-btn-lg rk-pricing-cta-ghost" href="${rkEscape(demoHref)}">Book a demo</a>
227
280
  </div>
228
281
  </div>
229
282
  </section>
@@ -239,17 +292,12 @@ export function renderReskinPricing() {
239
292
  .rk-pricing-title-accent::after{content:"";position:absolute;left:-2%;right:-2%;bottom:.08em;height:.36em;z-index:-1;
240
293
  background:var(--rk-gold-500);border-radius:var(--rk-r-full);opacity:.55}
241
294
  .rk-pricing-lede{font-size:var(--rk-text-lg);color:var(--rk-text-muted);max-width:620px;line-height:1.55}
242
- .rk-pricing-toggle{display:inline-flex;gap:4px;padding:5px;background:#fff;border:1px solid var(--rk-border);
243
- border-radius:var(--rk-r-full);box-shadow:var(--rk-shadow-sm)}
244
- .rk-pricing-toggle-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 20px;border:0;cursor:pointer;
245
- background:transparent;border-radius:var(--rk-r-full);font-family:var(--rk-font-body);font-size:14px;
246
- font-weight:600;color:var(--rk-n-600);transition:background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
247
- .rk-pricing-toggle-btn.is-active{background:var(--rk-ink);color:#fff}
248
- .rk-pricing-save{font-size:11px;padding:2px 8px}
249
- .rk-pricing-toggle-btn.is-active .rk-pricing-save{background:var(--rk-gold-500);color:var(--rk-ink)}
295
+ .rk-pricing-signed{font-size:13px;color:var(--rk-n-600);background:var(--rk-gold-tint);border:1px solid var(--rk-gold-tint2);
296
+ padding:8px 16px;border-radius:var(--rk-r-full)}
297
+ .rk-pricing-signed strong{color:var(--rk-ink);font-weight:700}
250
298
 
251
299
  /* plan cards */
252
- .rk-pricing-plans{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;align-items:start;margin-bottom:64px}
300
+ .rk-pricing-plans{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;align-items:start;margin-bottom:20px}
253
301
  @media(max-width:960px){.rk-pricing-plans{grid-template-columns:1fr;max-width:460px;margin-left:auto;margin-right:auto}}
254
302
  .rk-pricing-plan{position:relative;display:flex;flex-direction:column;gap:18px;overflow:visible}
255
303
  .rk-pricing-plan-featured{border-color:transparent;box-shadow:var(--rk-ring-gold),var(--rk-shadow-xl);
@@ -265,20 +313,27 @@ export function renderReskinPricing() {
265
313
  .rk-pricing-price{display:flex;align-items:baseline;gap:2px;color:var(--rk-ink);margin-top:2px}
266
314
  .rk-pricing-cur{font-family:var(--rk-font-display);font-weight:800;font-size:1.5rem;align-self:flex-start;margin-top:.5rem}
267
315
  .rk-pricing-amount{font-family:var(--rk-font-display);font-weight:800;font-size:3.4rem;letter-spacing:-.03em;line-height:1}
316
+ .rk-pricing-amount-sm{font-size:2.5rem}
268
317
  .rk-pricing-suffix{font-size:var(--rk-text-base);font-weight:600;color:var(--rk-text-muted);margin-left:4px}
269
318
  .rk-pricing-price-sub{font-size:13px;color:var(--rk-text-muted);min-height:18px}
270
- .rk-strike{text-decoration:line-through;color:var(--rk-n-400)}
271
319
  .rk-pricing-pitch{font-size:14px;color:var(--rk-text);line-height:1.55;min-height:66px}
272
320
  .rk-pricing-feats{list-style:none;margin:0;padding:16px 0 0;display:grid;gap:12px;border-top:1px solid var(--rk-border)}
273
321
  .rk-pricing-feat{display:flex;align-items:flex-start;gap:11px;font-size:14px;color:var(--rk-n-700)}
274
322
  .rk-pricing-check{padding:6px}
275
323
  .rk-pricing-check svg{width:100%;height:100%}
276
324
 
325
+ /* manage-billing line */
326
+ .rk-pricing-manage{text-align:center;font-size:13px;color:var(--rk-text-muted);margin:0 auto 60px}
327
+ .rk-pricing-manage a{color:var(--rk-n-700);font-weight:600;border-bottom:1px solid var(--rk-border-strong)}
328
+ .rk-pricing-manage a:hover{color:var(--rk-ink)}
329
+
277
330
  /* credit strip */
278
331
  .rk-pricing-credits{padding:34px;background:linear-gradient(180deg,#fff 0%,var(--rk-n-50) 100%)}
332
+ @media(max-width:560px){.rk-pricing-credits{padding:22px 16px}}
279
333
  .rk-pricing-credits-head{max-width:680px;margin-bottom:26px;display:grid;gap:8px}
280
334
  .rk-pricing-credits-head h2{font-size:var(--rk-text-3xl);letter-spacing:-.02em}
281
335
  .rk-pricing-credits-head p{font-size:15px;line-height:1.55}
336
+ .rk-pricing-credits-cta{margin-top:8px}
282
337
  .rk-pricing-packs{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
283
338
  @media(max-width:900px){.rk-pricing-packs{grid-template-columns:repeat(2,1fr)}}
284
339
  @media(max-width:520px){.rk-pricing-packs{grid-template-columns:1fr}}
@@ -294,11 +349,8 @@ export function renderReskinPricing() {
294
349
  color:var(--rk-ink);line-height:1;display:flex;align-items:baseline;gap:6px}
295
350
  .rk-pricing-pack-credits span{font-family:var(--rk-font-body);font-size:12px;font-weight:600;color:var(--rk-text-muted);letter-spacing:0}
296
351
  .rk-pricing-pack-bonus{align-self:flex-start}
297
- .rk-pricing-pack-price{font-family:var(--rk-font-display);font-weight:700;font-size:1.05rem;color:var(--rk-n-700);
298
- display:flex;align-items:baseline;gap:6px;margin-top:2px}
299
- .rk-pricing-pack-price span{font-family:var(--rk-font-body);font-size:12px;font-weight:500;color:var(--rk-text-faint)}
300
352
  .rk-pricing-pack-note{font-size:12px;color:var(--rk-text-muted);min-height:16px}
301
- .rk-pricing-pack button{margin-top:4px}
353
+ .rk-pricing-pack a{margin-top:4px}
302
354
  .rk-pricing-credits-foot{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px;padding-top:20px;border-top:1px solid var(--rk-border)}
303
355
 
304
356
  /* faq + trust */
@@ -326,34 +378,13 @@ export function renderReskinPricing() {
326
378
  .rk-pricing-cta-ghost{background:rgba(255,255,255,.85);border-color:transparent}
327
379
  .rk-pricing-cta-ghost:hover{background:#fff}
328
380
  `;
329
- const script = `
330
- (function(){
331
- var root=document;
332
- var btns=root.querySelectorAll('[data-rk-period]');
333
- btns.forEach(function(btn){
334
- btn.addEventListener('click',function(){
335
- var period=btn.getAttribute('data-rk-period');
336
- btns.forEach(function(b){b.classList.toggle('is-active',b===btn)});
337
- var yearly=period==='yearly';
338
- root.querySelectorAll('[data-rk-price]').forEach(function(el){
339
- var v=el.getAttribute(yearly?'data-rk-yearly':'data-rk-monthly');
340
- el.textContent=v;
341
- });
342
- root.querySelectorAll('[data-rk-price-note]').forEach(function(el){
343
- el.style.visibility=yearly?'visible':'hidden';
344
- });
345
- });
346
- });
347
- // start on monthly: hide the "billed yearly" note until yearly is picked
348
- root.querySelectorAll('[data-rk-price-note]').forEach(function(el){el.style.visibility='hidden';});
349
- })();`;
350
381
  return reskinDocument({
351
382
  title: "vidfarm reskin — Pricing",
352
- description: "Reskinned vidfarm pricing page plans, credit wallet top-ups, and the $0.03/credit model.",
383
+ description: "Reskinned vidfarm pricing — the $99/mo Pro plan, the $0.03/credit wallet, and real Stripe checkout.",
353
384
  activeSlug: "pricing",
385
+ account: { email: input.email },
354
386
  pageCss,
355
- body,
356
- script
387
+ body
357
388
  });
358
389
  }
359
390
  //# sourceMappingURL=pricing-page.js.map