@mevdragon/vidfarm-devcli 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/farmville-saas-ux/SKILL.md +156 -0
- package/.agents/skills/farmville-saas-ux/assets/starter.html +294 -0
- package/.agents/skills/farmville-saas-ux/references/components.md +340 -0
- package/.agents/skills/farmville-saas-ux/references/porting-guide.md +121 -0
- package/.agents/skills/farmville-saas-ux/references/tokens.md +271 -0
- package/.agents/skills/vidfarm-media/SKILL.md +4 -3
- package/.agents/skills/vidfarm-media/references/tts.md +20 -1
- package/SKILL.director.md +20 -20
- package/SKILL.platform.md +4 -4
- package/dist/src/account-pages-legacy.js +2 -2
- package/dist/src/app.js +721 -101
- package/dist/src/cli.js +11 -10
- package/dist/src/devcli/clips.js +64 -64
- package/dist/src/editor-chat.js +2 -2
- package/dist/src/frontend/homepage-client.js +162 -2
- package/dist/src/frontend/homepage-store.js +30 -1
- package/dist/src/frontend/homepage-view.js +111 -4
- package/dist/src/homepage.js +184 -1
- package/dist/src/landing-page.js +367 -0
- package/dist/src/primitive-registry.js +278 -2
- package/dist/src/reskin/agency-page.js +299 -0
- package/dist/src/reskin/calendar-page.js +567 -0
- package/dist/src/reskin/chat-page.js +607 -0
- package/dist/src/reskin/discover-page.js +1096 -0
- package/dist/src/reskin/document.js +663 -0
- package/dist/src/reskin/help-page.js +356 -0
- package/dist/src/reskin/index-page.js +62 -0
- package/dist/src/reskin/inpaint-page.js +541 -0
- package/dist/src/reskin/job-runs-page.js +477 -0
- package/dist/src/reskin/library-page.js +688 -0
- package/dist/src/reskin/login-page.js +262 -0
- package/dist/src/reskin/pricing-page.js +388 -0
- package/dist/src/reskin/settings-page.js +687 -0
- package/dist/src/reskin/theme.js +362 -0
- package/dist/src/services/serverless-records.js +54 -0
- package/dist/src/services/swipe-customize.js +434 -0
- package/package.json +1 -1
- package/public/assets/homepage-client-app.js +22 -22
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// /reskin/login — the auth screen ported into the farmville design system,
|
|
2
|
+
// now MIGRATED to the REAL production auth (no more fake password-only form).
|
|
3
|
+
//
|
|
4
|
+
// Bare chrome (no shared top nav / footer): an auth screen owns its whole
|
|
5
|
+
// canvas, so we render just the body over a warm sunny background. A two-column
|
|
6
|
+
// composition — a sunlit brand/marketing panel on the left, a floating white
|
|
7
|
+
// auth card on the right — collapses to a single centered card on small screens.
|
|
8
|
+
//
|
|
9
|
+
// This accepts the SAME input object the live /login route already builds (via
|
|
10
|
+
// `Parameters<typeof renderLoginPage>[0]`), so the route wiring is just "call
|
|
11
|
+
// this instead of the legacy renderer". Every form posts to the REAL login
|
|
12
|
+
// endpoints with the exact field names the live login uses:
|
|
13
|
+
// • password: POST ${routePrefix}/password?mode=password {email,password}
|
|
14
|
+
// • OTP request: POST ${routePrefix}/otp/request?mode=otp {email}
|
|
15
|
+
// • OTP verify: POST ${routePrefix}/otp/verify?mode=otp {email(hidden),code,name?}
|
|
16
|
+
// routePrefix defaults to "/login" but is honored from the input (e.g. the
|
|
17
|
+
// agency-client login uses "/agency-client-login"). On success the real
|
|
18
|
+
// endpoints own the redirect (post-login return path) — we never fake it.
|
|
19
|
+
//
|
|
20
|
+
// The segmented control at the top switches between the two REAL modes
|
|
21
|
+
// (Email code = otp, Password) via honest ?mode= links, so it mirrors the
|
|
22
|
+
// live login's mode handling exactly. Design stays farmville; NO emoji.
|
|
23
|
+
import { reskinDocument, rkEscape } from "./document.js";
|
|
24
|
+
const EYE = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`;
|
|
25
|
+
const MAIL = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="3"/><path d="m3.5 7 8.5 6 8.5-6"/></svg>`;
|
|
26
|
+
const LOCK = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="10" width="16" height="10" rx="2.5"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>`;
|
|
27
|
+
export function renderReskinLogin(input = {}) {
|
|
28
|
+
// Mirror the live renderer's normalization exactly.
|
|
29
|
+
const mode = input.mode === "password" ? "password" : "otp";
|
|
30
|
+
const routePrefix = input.routePrefix || "/login";
|
|
31
|
+
const rp = rkEscape(routePrefix);
|
|
32
|
+
const email = rkEscape(input.email ?? "");
|
|
33
|
+
const otpSent = Boolean(input.otpSent);
|
|
34
|
+
// REAL endpoints (same as src/account-pages-legacy.ts → renderLoginPage).
|
|
35
|
+
const PASSWORD_ACTION = `${rp}/password?mode=password`;
|
|
36
|
+
const OTP_REQUEST_ACTION = `${rp}/otp/request?mode=otp`;
|
|
37
|
+
const OTP_VERIFY_ACTION = `${rp}/otp/verify?mode=otp`;
|
|
38
|
+
const OTP_HREF = `${rp}?mode=otp`;
|
|
39
|
+
const PASSWORD_HREF = `${rp}?mode=password`;
|
|
40
|
+
const message = input.message
|
|
41
|
+
? `<div class="rk-notice rk-notice-ok rk-login-flash">${rkEscape(input.message)}</div>`
|
|
42
|
+
: "";
|
|
43
|
+
const error = input.error
|
|
44
|
+
? `<div class="rk-notice rk-notice-err rk-login-flash">${rkEscape(input.error)}</div>`
|
|
45
|
+
: "";
|
|
46
|
+
// ── segmented mode control (real ?mode= links) ──────────────────────────
|
|
47
|
+
// Hidden while verifying a code (mid-flow) — there we show "Change email".
|
|
48
|
+
const seg = otpSent
|
|
49
|
+
? ""
|
|
50
|
+
: `<div class="rk-login-seg" role="tablist" aria-label="Choose a sign-in method">
|
|
51
|
+
<span class="rk-login-seg-thumb${mode === "password" ? " is-right" : ""}"></span>
|
|
52
|
+
<a class="rk-login-seg-btn${mode === "otp" ? " is-active" : ""}" href="${OTP_HREF}" role="tab" aria-selected="${mode === "otp" ? "true" : "false"}">Email code</a>
|
|
53
|
+
<a class="rk-login-seg-btn${mode === "password" ? " is-active" : ""}" href="${PASSWORD_HREF}" role="tab" aria-selected="${mode === "password" ? "true" : "false"}">Password</a>
|
|
54
|
+
</div>`;
|
|
55
|
+
// ── header copy per state ───────────────────────────────────────────────
|
|
56
|
+
const head = mode === "password"
|
|
57
|
+
? { title: "Welcome back", sub: "Log in with your email and password." }
|
|
58
|
+
: otpSent
|
|
59
|
+
? { title: "Check your email", sub: `Enter the 6-digit code we sent${input.email ? ` to ${email}` : ""}.` }
|
|
60
|
+
: { title: "Sign in with a code", sub: "We'll email you a 6-digit code — no password to remember." };
|
|
61
|
+
// ── the auth form (three honest states) ─────────────────────────────────
|
|
62
|
+
let authPane;
|
|
63
|
+
if (mode === "password") {
|
|
64
|
+
authPane = `
|
|
65
|
+
<form class="rk-stack rk-login-form" method="post" action="${PASSWORD_ACTION}">
|
|
66
|
+
<div class="rk-field">
|
|
67
|
+
<label class="rk-label" for="rk-login-email">Email</label>
|
|
68
|
+
<input class="rk-input" id="rk-login-email" type="email" name="email" value="${email}" placeholder="you@studio.co" autocomplete="email" required autofocus />
|
|
69
|
+
</div>
|
|
70
|
+
<div class="rk-field">
|
|
71
|
+
<label class="rk-label" for="rk-login-pass">Password</label>
|
|
72
|
+
<div class="rk-input-group">
|
|
73
|
+
<input class="rk-input" id="rk-login-pass" type="password" name="password" placeholder="••••••••" autocomplete="current-password" required />
|
|
74
|
+
<button class="rk-btn rk-btn-ghost rk-btn-sm rk-login-eye" type="button" data-rk-reveal="rk-login-pass" aria-label="Show password">${EYE}</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">Log in <span class="rk-arrow">→</span></button>
|
|
78
|
+
</form>
|
|
79
|
+
|
|
80
|
+
<div class="rk-login-divider"><span>or</span></div>
|
|
81
|
+
<a class="rk-btn rk-btn-ghost rk-btn-lg rk-btn-block rk-login-alt" href="${OTP_HREF}">${MAIL} Email me a login code</a>`;
|
|
82
|
+
}
|
|
83
|
+
else if (otpSent) {
|
|
84
|
+
authPane = `
|
|
85
|
+
<form class="rk-stack rk-login-form" method="post" action="${OTP_VERIFY_ACTION}">
|
|
86
|
+
<input type="hidden" name="email" value="${email}" />
|
|
87
|
+
<div class="rk-field">
|
|
88
|
+
<label class="rk-label" for="rk-login-code">6-digit code</label>
|
|
89
|
+
<input class="rk-input rk-login-code" id="rk-login-code" name="code" inputmode="numeric" autocomplete="one-time-code" maxlength="6" pattern="[0-9]*" placeholder="000000" autofocus required />
|
|
90
|
+
</div>
|
|
91
|
+
<div class="rk-field">
|
|
92
|
+
<label class="rk-label" for="rk-login-name">Your name <span class="rk-login-optional">(optional — for new accounts)</span></label>
|
|
93
|
+
<input class="rk-input" id="rk-login-name" name="name" type="text" placeholder="Dylan Vane" autocomplete="name" />
|
|
94
|
+
</div>
|
|
95
|
+
<button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">Verify & continue <span class="rk-arrow">→</span></button>
|
|
96
|
+
</form>
|
|
97
|
+
|
|
98
|
+
<p class="rk-login-foot"><a href="${OTP_HREF}">Use a different email</a></p>`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
authPane = `
|
|
102
|
+
<form class="rk-stack rk-login-form" method="post" action="${OTP_REQUEST_ACTION}">
|
|
103
|
+
<div class="rk-field">
|
|
104
|
+
<label class="rk-label" for="rk-login-email">Email</label>
|
|
105
|
+
<input class="rk-input" id="rk-login-email" type="email" name="email" value="${email}" placeholder="you@studio.co" autocomplete="email" required autofocus />
|
|
106
|
+
</div>
|
|
107
|
+
<button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">${MAIL} Email me a login code</button>
|
|
108
|
+
</form>
|
|
109
|
+
|
|
110
|
+
<div class="rk-login-divider"><span>or</span></div>
|
|
111
|
+
<a class="rk-btn rk-btn-ghost rk-btn-lg rk-btn-block rk-login-alt" href="${PASSWORD_HREF}">${LOCK} Use a password instead</a>`;
|
|
112
|
+
}
|
|
113
|
+
const body = `
|
|
114
|
+
<main class="rk-login">
|
|
115
|
+
<div class="rk-login-art">
|
|
116
|
+
<div class="rk-login-art-sky"></div>
|
|
117
|
+
<span class="rk-login-blob rk-login-blob-a"></span>
|
|
118
|
+
<span class="rk-login-blob rk-login-blob-b"></span>
|
|
119
|
+
<div class="rk-login-art-inner">
|
|
120
|
+
<a class="rk-brand rk-login-brand" href="/">
|
|
121
|
+
<span class="rk-brand-mark">V</span>vidfarm
|
|
122
|
+
</a>
|
|
123
|
+
<div class="rk-login-art-copy">
|
|
124
|
+
<h1 class="rk-login-hed">A week of content, made in an afternoon.</h1>
|
|
125
|
+
<p class="rk-login-lede">Sign in to spin up short-form ads from your own clips — punchy hooks, UGC energy, captions that pop.</p>
|
|
126
|
+
<ul class="rk-login-feats">
|
|
127
|
+
<li><span class="rk-login-feat-mark" aria-hidden="true"></span> Mine & tag clips automatically</li>
|
|
128
|
+
<li><span class="rk-login-feat-mark" aria-hidden="true"></span> Swipe templates into finished ads</li>
|
|
129
|
+
<li><span class="rk-login-feat-mark" aria-hidden="true"></span> Publish straight to your channels</li>
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
<figure class="rk-login-quote rk-glass">
|
|
133
|
+
<div class="rk-login-quote-avatar">DV</div>
|
|
134
|
+
<blockquote>
|
|
135
|
+
<p>"We ship a full week of ads before lunch now. It feels like cheating."</p>
|
|
136
|
+
<figcaption>Dylan Vane · Founder, Studio Co</figcaption>
|
|
137
|
+
</blockquote>
|
|
138
|
+
</figure>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="rk-login-pane">
|
|
143
|
+
<div class="rk-login-card">
|
|
144
|
+
<a class="rk-brand rk-login-card-brand" href="/">
|
|
145
|
+
<span class="rk-brand-mark">V</span>vidfarm
|
|
146
|
+
</a>
|
|
147
|
+
|
|
148
|
+
<div class="rk-login-head">
|
|
149
|
+
<h2 class="rk-login-title">${rkEscape(head.title)}</h2>
|
|
150
|
+
<p class="rk-muted">${head.sub}</p>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
${seg}
|
|
154
|
+
${message}
|
|
155
|
+
${error}
|
|
156
|
+
${authPane}
|
|
157
|
+
</div>
|
|
158
|
+
<p class="rk-login-legal">By continuing you agree to our <a href="#">Terms</a> & <a href="#">Privacy Policy</a>.</p>
|
|
159
|
+
</div>
|
|
160
|
+
</main>`;
|
|
161
|
+
const pageCss = `
|
|
162
|
+
.rk-login{min-height:100vh;display:grid;grid-template-columns:1.05fr .95fr;
|
|
163
|
+
background:radial-gradient(120% 90% at 100% 0%,var(--rk-gold-tint) 0%,transparent 55%),var(--rk-bg)}
|
|
164
|
+
@media(max-width:920px){.rk-login{grid-template-columns:1fr}}
|
|
165
|
+
|
|
166
|
+
/* ── left: sunlit brand / marketing panel ── */
|
|
167
|
+
.rk-login-art{position:relative;overflow:hidden;display:flex;padding:56px clamp(2rem,4vw,4rem)}
|
|
168
|
+
@media(max-width:920px){.rk-login-art{display:none}}
|
|
169
|
+
.rk-login-art-sky{position:absolute;inset:0;z-index:0;background:
|
|
170
|
+
radial-gradient(72% 55% at 22% 12%,rgba(255,244,214,.96),transparent 62%),
|
|
171
|
+
radial-gradient(60% 55% at 92% 96%,rgba(255,199,56,.42),transparent 60%),
|
|
172
|
+
linear-gradient(158deg,#89ccf3 0%,#b7e0fa 42%,#ffe7c9 100%)}
|
|
173
|
+
.rk-login-blob{position:absolute;z-index:0;border-radius:var(--rk-r-full);filter:blur(2px);
|
|
174
|
+
background:rgba(255,255,255,.55)}
|
|
175
|
+
.rk-login-blob-a{width:220px;height:120px;top:16%;right:12%;box-shadow:60px 20px 0 -18px rgba(255,255,255,.5)}
|
|
176
|
+
.rk-login-blob-b{width:150px;height:84px;bottom:24%;left:-30px;background:rgba(255,255,255,.4)}
|
|
177
|
+
.rk-login-art-inner{position:relative;z-index:1;display:flex;flex-direction:column;gap:32px;
|
|
178
|
+
justify-content:space-between;width:100%;max-width:440px;margin:auto}
|
|
179
|
+
.rk-login-brand{font-size:22px;color:var(--rk-ink)}
|
|
180
|
+
.rk-login-brand .rk-brand-mark{width:30px;height:30px;font-size:17px}
|
|
181
|
+
.rk-login-art-copy{display:grid;gap:18px}
|
|
182
|
+
.rk-login-eyebrow{justify-self:start;background:rgba(255,255,255,.72);border-color:rgba(255,255,255,.9);
|
|
183
|
+
backdrop-filter:blur(6px);color:var(--rk-n-700)}
|
|
184
|
+
.rk-login-hed{font-size:clamp(2rem,3.4vw,2.9rem);line-height:1.05;letter-spacing:-.035em;color:var(--rk-ink);
|
|
185
|
+
max-width:12ch;text-wrap:balance}
|
|
186
|
+
.rk-login-lede{font-size:var(--rk-text-lg);line-height:1.5;color:var(--rk-n-700);max-width:34ch}
|
|
187
|
+
.rk-login-feats{list-style:none;margin:0;padding:0;display:grid;gap:12px}
|
|
188
|
+
.rk-login-feats li{display:flex;align-items:center;gap:11px;font-size:15px;font-weight:600;color:var(--rk-n-800)}
|
|
189
|
+
.rk-login-feat-mark{width:7px;height:7px;border-radius:var(--rk-r-full);background:var(--rk-gold-500);
|
|
190
|
+
box-shadow:0 0 0 4px var(--rk-gold-tint2);flex:none;margin:0 3px}
|
|
191
|
+
.rk-login-quote{border-radius:var(--rk-r-3xl);padding:18px 20px;display:flex;gap:14px;align-items:flex-start;
|
|
192
|
+
box-shadow:var(--rk-shadow-lg)}
|
|
193
|
+
.rk-login-quote-avatar{width:42px;height:42px;border-radius:var(--rk-r-full);flex:none;display:grid;place-items:center;
|
|
194
|
+
font-family:var(--rk-font-display);font-weight:800;font-size:15px;color:var(--rk-ink);
|
|
195
|
+
background:var(--rk-gold-500);box-shadow:var(--rk-shadow-xs)}
|
|
196
|
+
.rk-login-quote blockquote{margin:0;display:grid;gap:6px}
|
|
197
|
+
.rk-login-quote p{font-size:14px;font-weight:600;color:var(--rk-ink);line-height:1.45}
|
|
198
|
+
.rk-login-quote figcaption{font-size:12px;font-weight:600;color:var(--rk-n-600)}
|
|
199
|
+
|
|
200
|
+
/* ── right: floating auth card ── */
|
|
201
|
+
.rk-login-pane{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;
|
|
202
|
+
padding:48px clamp(1.25rem,4vw,3rem)}
|
|
203
|
+
.rk-login-card{width:100%;max-width:412px;background:var(--rk-surface);border:1px solid var(--rk-border);
|
|
204
|
+
border-radius:var(--rk-r-3xl);padding:clamp(28px,3vw,40px);box-shadow:var(--rk-shadow-xl);display:grid;gap:22px}
|
|
205
|
+
.rk-login-card-brand{display:none;font-size:20px}
|
|
206
|
+
@media(max-width:920px){.rk-login-card-brand{display:inline-flex;justify-self:center}}
|
|
207
|
+
.rk-login-head{display:grid;gap:6px;text-align:center}
|
|
208
|
+
.rk-login-title{font-size:var(--rk-text-3xl);letter-spacing:-.03em}
|
|
209
|
+
.rk-login-flash{font-size:13px;padding:11px 14px}
|
|
210
|
+
|
|
211
|
+
/* segmented mode control — real ?mode= links. gold is reserved for submit, so
|
|
212
|
+
the active thumb is a plain white pill with an ink label */
|
|
213
|
+
.rk-login-seg{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:2px;padding:4px;
|
|
214
|
+
background:var(--rk-n-100);border:1px solid var(--rk-border);border-radius:var(--rk-r-full)}
|
|
215
|
+
.rk-login-seg-thumb{position:absolute;z-index:0;top:4px;left:4px;width:calc(50% - 4px);height:calc(100% - 8px);
|
|
216
|
+
background:#fff;border-radius:var(--rk-r-full);box-shadow:var(--rk-shadow-sm);
|
|
217
|
+
transition:transform var(--rk-dur) var(--rk-ease)}
|
|
218
|
+
.rk-login-seg-thumb.is-right{transform:translateX(100%)}
|
|
219
|
+
.rk-login-seg-btn{position:relative;z-index:1;appearance:none;border:0;background:transparent;cursor:pointer;
|
|
220
|
+
padding:9px 12px;border-radius:var(--rk-r-full);font-family:var(--rk-font-body);font-size:14px;font-weight:600;
|
|
221
|
+
text-align:center;color:var(--rk-n-500);transition:color var(--rk-dur) var(--rk-ease)}
|
|
222
|
+
.rk-login-seg-btn.is-active{color:var(--rk-ink)}
|
|
223
|
+
|
|
224
|
+
.rk-login-form{gap:16px}
|
|
225
|
+
.rk-login-eye{padding:0 12px;color:var(--rk-n-500)}
|
|
226
|
+
.rk-login-submit{margin-top:4px}
|
|
227
|
+
.rk-login-code{font-family:var(--rk-font-mono);font-size:20px;letter-spacing:.42em;text-align:center;padding-right:0}
|
|
228
|
+
.rk-login-code::placeholder{letter-spacing:.42em}
|
|
229
|
+
.rk-login-optional{font-weight:500;color:var(--rk-text-faint)}
|
|
230
|
+
|
|
231
|
+
.rk-login-divider{display:flex;align-items:center;gap:14px;color:var(--rk-text-faint);
|
|
232
|
+
font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.08em}
|
|
233
|
+
.rk-login-divider::before,.rk-login-divider::after{content:"";height:1px;flex:1;background:var(--rk-border)}
|
|
234
|
+
.rk-login-alt{color:var(--rk-ink)}
|
|
235
|
+
.rk-login-foot{text-align:center;font-size:14px;color:var(--rk-text-muted)}
|
|
236
|
+
.rk-login-foot a{font-weight:700;color:var(--rk-ink)}
|
|
237
|
+
.rk-login-foot a:hover{color:var(--rk-gold-700)}
|
|
238
|
+
.rk-login-legal{font-size:12px;color:var(--rk-text-faint);text-align:center;max-width:412px}
|
|
239
|
+
.rk-login-legal a{color:var(--rk-text-muted);text-decoration:underline}
|
|
240
|
+
`;
|
|
241
|
+
const script = `
|
|
242
|
+
(function(){
|
|
243
|
+
var root=document;
|
|
244
|
+
// password reveal eye (only present in password mode)
|
|
245
|
+
root.querySelectorAll('[data-rk-reveal]').forEach(function(btn){
|
|
246
|
+
btn.addEventListener('click',function(){
|
|
247
|
+
var el=root.getElementById(btn.getAttribute('data-rk-reveal'));
|
|
248
|
+
if(!el)return; el.type=el.type==='password'?'text':'password';
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
})();`;
|
|
252
|
+
return reskinDocument({
|
|
253
|
+
title: "vidfarm reskin — Login",
|
|
254
|
+
description: "Reskinned vidfarm login / sign up screen.",
|
|
255
|
+
activeSlug: "login",
|
|
256
|
+
chrome: "bare",
|
|
257
|
+
pageCss,
|
|
258
|
+
body,
|
|
259
|
+
script
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=login-page.js.map
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
// /reskin/pricing — MIGRATED to real production billing.
|
|
2
|
+
//
|
|
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.
|
|
23
|
+
import { reskinDocument, rkEscape } from "./document.js";
|
|
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 }
|
|
118
|
+
];
|
|
119
|
+
const FAQS = [
|
|
120
|
+
{
|
|
121
|
+
q: "What exactly is a credit?",
|
|
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."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
q: "Do credits expire?",
|
|
126
|
+
a: "No. Wallet credits roll over forever and the balance auto-scales to zero — nothing is charged while you're idle."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
q: "Can I set spend limits?",
|
|
130
|
+
a: "Yes. Cap usage with per-hour and per-day credit rate limits so long-lived render jobs never surprise you."
|
|
131
|
+
},
|
|
132
|
+
{
|
|
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.`
|
|
143
|
+
}
|
|
144
|
+
];
|
|
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>`;
|
|
160
|
+
}
|
|
161
|
+
function planCard(p) {
|
|
162
|
+
const featured = p.featured ? " rk-pricing-plan-featured" : "";
|
|
163
|
+
const badge = p.featured ? `<span class="rk-pricing-most">Most popular</span>` : "";
|
|
164
|
+
const checkTint = p.featured ? "rk-tile-gold" : "rk-tile-green";
|
|
165
|
+
const features = p.features.map((f) => `
|
|
166
|
+
<li class="rk-pricing-feat">
|
|
167
|
+
<span class="rk-tile rk-tile-sm ${checkTint} rk-pricing-check">${CHECK}</span>
|
|
168
|
+
<span>${rkEscape(f)}</span>
|
|
169
|
+
</li>`).join("");
|
|
170
|
+
const ext = p.ctaExternal ? ` target="_blank" rel="noreferrer"` : "";
|
|
171
|
+
return `
|
|
172
|
+
<article class="rk-card rk-card-hover rk-pricing-plan${featured}">
|
|
173
|
+
${badge}
|
|
174
|
+
<div class="rk-pricing-plan-head">
|
|
175
|
+
<span class="rk-tile ${p.featured ? "rk-tile-gold" : "rk-tile-ink"} rk-pricing-mono" aria-hidden="true">${rkEscape(p.name.charAt(0))}</span>
|
|
176
|
+
<div>
|
|
177
|
+
<span class="rk-eyebrow ${p.featured ? "" : "rk-pricing-kicker-muted"}">${rkEscape(p.kicker)}</span>
|
|
178
|
+
<h3>${rkEscape(p.name)}</h3>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
${priceBlock(p)}
|
|
182
|
+
<p class="rk-pricing-pitch">${rkEscape(p.pitch)}</p>
|
|
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>
|
|
184
|
+
<ul class="rk-pricing-feats">${features}</ul>
|
|
185
|
+
</article>`;
|
|
186
|
+
}
|
|
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);
|
|
192
|
+
return `
|
|
193
|
+
<article class="rk-pricing-pack${popular}">
|
|
194
|
+
${flag}
|
|
195
|
+
<div class="rk-pricing-pack-top">
|
|
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>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="rk-pricing-pack-credits">${credits(total)}<span>credits</span></div>
|
|
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>
|
|
203
|
+
</article>`;
|
|
204
|
+
}
|
|
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);
|
|
212
|
+
const faqCards = FAQS.map((f) => `
|
|
213
|
+
<div class="rk-card rk-card-pad-sm rk-pricing-faq">
|
|
214
|
+
<h3>${rkEscape(f.q)}</h3>
|
|
215
|
+
<p class="rk-muted">${rkEscape(f.a)}</p>
|
|
216
|
+
</div>`).join("");
|
|
217
|
+
const trustPills = [
|
|
218
|
+
"Cancel anytime",
|
|
219
|
+
"Transparent per-render billing",
|
|
220
|
+
"BYO AI keys — never billed",
|
|
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.");
|
|
224
|
+
const body = `
|
|
225
|
+
<main class="rk-container rk-page rk-pricing">
|
|
226
|
+
|
|
227
|
+
<!-- ── intro ── -->
|
|
228
|
+
<section class="rk-pricing-intro">
|
|
229
|
+
<h1 class="rk-pricing-title">Ship a week of content<br><span class="rk-pricing-title-accent">in an afternoon.</span></h1>
|
|
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
|
+
: ""}
|
|
234
|
+
</section>
|
|
235
|
+
|
|
236
|
+
<!-- ── plan cards ── -->
|
|
237
|
+
<section class="rk-pricing-plans">${planCards}</section>
|
|
238
|
+
|
|
239
|
+
<p class="rk-pricing-manage">Already on a plan? <a href="${rkEscape(BILLING_PORTAL_URL)}" target="_blank" rel="noreferrer">Manage billing, invoices & cancellation in the Stripe portal <span class="rk-arrow">↗</span></a></p>
|
|
240
|
+
|
|
241
|
+
<!-- ── credit top-up strip ── -->
|
|
242
|
+
<section class="rk-card rk-pricing-credits">
|
|
243
|
+
<div class="rk-pricing-credits-head">
|
|
244
|
+
<span class="rk-eyebrow">Credit wallet</span>
|
|
245
|
+
<h2>Top up credits, pay only for compute</h2>
|
|
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>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="rk-pricing-packs">${packCards}</div>
|
|
253
|
+
<div class="rk-pricing-credits-foot">
|
|
254
|
+
<span class="rk-pill rk-pill-sky">Set per-hour & per-day spend caps</span>
|
|
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>
|
|
257
|
+
</div>
|
|
258
|
+
</section>
|
|
259
|
+
|
|
260
|
+
<!-- ── FAQ + trust ── -->
|
|
261
|
+
<section class="rk-pricing-faqs-wrap">
|
|
262
|
+
<div class="rk-pricing-faqs-head">
|
|
263
|
+
<span class="rk-eyebrow">Good to know</span>
|
|
264
|
+
<h2>Questions, answered</h2>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="rk-grid rk-grid-2 rk-pricing-faqs">${faqCards}</div>
|
|
267
|
+
<div class="rk-row rk-wrap rk-pricing-trustrow">${trustPills}</div>
|
|
268
|
+
</section>
|
|
269
|
+
|
|
270
|
+
<!-- ── final gold CTA band ── -->
|
|
271
|
+
<section class="rk-pricing-cta">
|
|
272
|
+
<div class="rk-pricing-cta-art" aria-hidden="true"></div>
|
|
273
|
+
<div class="rk-pricing-cta-inner">
|
|
274
|
+
<span class="rk-badge rk-pricing-cta-badge">Ready when you are</span>
|
|
275
|
+
<h2 class="rk-pricing-cta-title">Make your first ad today.</h2>
|
|
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>
|
|
277
|
+
<div class="rk-row rk-wrap rk-pricing-cta-btns">
|
|
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>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</section>
|
|
283
|
+
|
|
284
|
+
</main>`;
|
|
285
|
+
const pageCss = `
|
|
286
|
+
.rk-pricing{padding-top:32px}
|
|
287
|
+
|
|
288
|
+
/* intro */
|
|
289
|
+
.rk-pricing-intro{display:grid;justify-items:center;text-align:center;gap:18px;max-width:760px;margin:8px auto 52px}
|
|
290
|
+
.rk-pricing-title{font-size:clamp(2.4rem,6vw,3.6rem);letter-spacing:-.035em;line-height:1.04}
|
|
291
|
+
.rk-pricing-title-accent{position:relative;display:inline-block;color:var(--rk-ink)}
|
|
292
|
+
.rk-pricing-title-accent::after{content:"";position:absolute;left:-2%;right:-2%;bottom:.08em;height:.36em;z-index:-1;
|
|
293
|
+
background:var(--rk-gold-500);border-radius:var(--rk-r-full);opacity:.55}
|
|
294
|
+
.rk-pricing-lede{font-size:var(--rk-text-lg);color:var(--rk-text-muted);max-width:620px;line-height:1.55}
|
|
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}
|
|
298
|
+
|
|
299
|
+
/* plan cards */
|
|
300
|
+
.rk-pricing-plans{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;align-items:start;margin-bottom:20px}
|
|
301
|
+
@media(max-width:960px){.rk-pricing-plans{grid-template-columns:1fr;max-width:460px;margin-left:auto;margin-right:auto}}
|
|
302
|
+
.rk-pricing-plan{position:relative;display:flex;flex-direction:column;gap:18px;overflow:visible}
|
|
303
|
+
.rk-pricing-plan-featured{border-color:transparent;box-shadow:var(--rk-ring-gold),var(--rk-shadow-xl);
|
|
304
|
+
background:linear-gradient(180deg,var(--rk-gold-tint) 0%,#fff 26%);transform:translateY(-10px);z-index:2}
|
|
305
|
+
@media(max-width:960px){.rk-pricing-plan-featured{transform:none}}
|
|
306
|
+
.rk-pricing-most{position:absolute;top:-14px;left:50%;transform:translateX(-50%);white-space:nowrap;
|
|
307
|
+
background:var(--rk-gold-500);color:var(--rk-ink);font-family:var(--rk-font-body);font-weight:700;
|
|
308
|
+
font-size:12px;letter-spacing:.02em;padding:6px 16px;border-radius:var(--rk-r-full);box-shadow:var(--rk-shadow-md)}
|
|
309
|
+
.rk-pricing-mono{font-family:var(--rk-font-display);font-weight:800;line-height:1}
|
|
310
|
+
.rk-pricing-plan-head{display:flex;align-items:center;gap:12px}
|
|
311
|
+
.rk-pricing-plan-head h3{font-size:var(--rk-text-2xl);margin-top:2px}
|
|
312
|
+
.rk-pricing-kicker-muted{color:var(--rk-n-500)}
|
|
313
|
+
.rk-pricing-price{display:flex;align-items:baseline;gap:2px;color:var(--rk-ink);margin-top:2px}
|
|
314
|
+
.rk-pricing-cur{font-family:var(--rk-font-display);font-weight:800;font-size:1.5rem;align-self:flex-start;margin-top:.5rem}
|
|
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}
|
|
317
|
+
.rk-pricing-suffix{font-size:var(--rk-text-base);font-weight:600;color:var(--rk-text-muted);margin-left:4px}
|
|
318
|
+
.rk-pricing-price-sub{font-size:13px;color:var(--rk-text-muted);min-height:18px}
|
|
319
|
+
.rk-pricing-pitch{font-size:14px;color:var(--rk-text);line-height:1.55;min-height:66px}
|
|
320
|
+
.rk-pricing-feats{list-style:none;margin:0;padding:16px 0 0;display:grid;gap:12px;border-top:1px solid var(--rk-border)}
|
|
321
|
+
.rk-pricing-feat{display:flex;align-items:flex-start;gap:11px;font-size:14px;color:var(--rk-n-700)}
|
|
322
|
+
.rk-pricing-check{padding:6px}
|
|
323
|
+
.rk-pricing-check svg{width:100%;height:100%}
|
|
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
|
+
|
|
330
|
+
/* credit strip */
|
|
331
|
+
.rk-pricing-credits{padding:34px;background:linear-gradient(180deg,#fff 0%,var(--rk-n-50) 100%)}
|
|
332
|
+
.rk-pricing-credits-head{max-width:680px;margin-bottom:26px;display:grid;gap:8px}
|
|
333
|
+
.rk-pricing-credits-head h2{font-size:var(--rk-text-3xl);letter-spacing:-.02em}
|
|
334
|
+
.rk-pricing-credits-head p{font-size:15px;line-height:1.55}
|
|
335
|
+
.rk-pricing-credits-cta{margin-top:8px}
|
|
336
|
+
.rk-pricing-packs{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
|
|
337
|
+
@media(max-width:900px){.rk-pricing-packs{grid-template-columns:repeat(2,1fr)}}
|
|
338
|
+
@media(max-width:520px){.rk-pricing-packs{grid-template-columns:1fr}}
|
|
339
|
+
.rk-pricing-pack{position:relative;display:flex;flex-direction:column;gap:10px;background:#fff;
|
|
340
|
+
border:1px solid var(--rk-border);border-radius:var(--rk-r-2xl);padding:20px 18px;box-shadow:var(--rk-shadow-xs);
|
|
341
|
+
transition:transform var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease)}
|
|
342
|
+
.rk-pricing-pack:hover{transform:translateY(-3px);box-shadow:var(--rk-shadow-md);border-color:var(--rk-border-strong)}
|
|
343
|
+
.rk-pricing-pack-popular{border-color:var(--rk-gold-500);box-shadow:0 0 0 2px var(--rk-gold-tint2),var(--rk-shadow-md)}
|
|
344
|
+
.rk-pricing-pack-flag{position:absolute;top:-11px;right:14px;font-size:11px;padding:3px 10px}
|
|
345
|
+
.rk-pricing-pack-top{display:flex;align-items:center;gap:9px}
|
|
346
|
+
.rk-pricing-pack-label{font-family:var(--rk-font-display);font-weight:700;font-size:14px;color:var(--rk-ink)}
|
|
347
|
+
.rk-pricing-pack-credits{font-family:var(--rk-font-display);font-weight:800;font-size:1.9rem;letter-spacing:-.02em;
|
|
348
|
+
color:var(--rk-ink);line-height:1;display:flex;align-items:baseline;gap:6px}
|
|
349
|
+
.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}
|
|
350
|
+
.rk-pricing-pack-bonus{align-self:flex-start}
|
|
351
|
+
.rk-pricing-pack-note{font-size:12px;color:var(--rk-text-muted);min-height:16px}
|
|
352
|
+
.rk-pricing-pack a{margin-top:4px}
|
|
353
|
+
.rk-pricing-credits-foot{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px;padding-top:20px;border-top:1px solid var(--rk-border)}
|
|
354
|
+
|
|
355
|
+
/* faq + trust */
|
|
356
|
+
.rk-pricing-faqs-wrap{margin-top:64px}
|
|
357
|
+
.rk-pricing-faqs-head{display:grid;gap:8px;text-align:center;justify-items:center;margin-bottom:28px}
|
|
358
|
+
.rk-pricing-faqs-head h2{font-size:var(--rk-text-3xl);letter-spacing:-.02em}
|
|
359
|
+
.rk-pricing-faq{display:grid;gap:7px}
|
|
360
|
+
.rk-pricing-faq h3{font-size:var(--rk-text-base);font-weight:700}
|
|
361
|
+
.rk-pricing-faq p{font-size:14px;line-height:1.55}
|
|
362
|
+
.rk-pricing-trustrow{justify-content:center;margin-top:26px}
|
|
363
|
+
.rk-pricing-trust{background:#fff;border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-xs);color:var(--rk-n-600);padding:7px 14px}
|
|
364
|
+
|
|
365
|
+
/* final gold CTA band */
|
|
366
|
+
.rk-pricing-cta{position:relative;overflow:hidden;margin-top:72px;border-radius:var(--rk-r-3xl);
|
|
367
|
+
padding:clamp(40px,6vw,64px) var(--rk-gutter);text-align:center;box-shadow:var(--rk-shadow-lg)}
|
|
368
|
+
.rk-pricing-cta-art{position:absolute;inset:0;z-index:0;background:
|
|
369
|
+
radial-gradient(70% 120% at 15% 10%,rgba(255,255,255,.7),transparent 55%),
|
|
370
|
+
radial-gradient(60% 120% at 90% 90%,rgba(252,185,0,.55),transparent 55%),
|
|
371
|
+
linear-gradient(135deg,var(--rk-gold-500) 0%,var(--rk-gold-600) 55%,#ffd873 100%)}
|
|
372
|
+
.rk-pricing-cta-inner{position:relative;z-index:1;display:grid;justify-items:center;gap:16px;max-width:620px;margin:0 auto}
|
|
373
|
+
.rk-pricing-cta-badge{background:rgba(255,255,255,.9);border-color:transparent}
|
|
374
|
+
.rk-pricing-cta-title{font-size:clamp(2rem,5vw,3rem);letter-spacing:-.03em;color:var(--rk-ink)}
|
|
375
|
+
.rk-pricing-cta-sub{font-size:var(--rk-text-lg);color:#5a4a12;line-height:1.5;font-weight:500}
|
|
376
|
+
.rk-pricing-cta-btns{justify-content:center;margin-top:8px}
|
|
377
|
+
.rk-pricing-cta-ghost{background:rgba(255,255,255,.85);border-color:transparent}
|
|
378
|
+
.rk-pricing-cta-ghost:hover{background:#fff}
|
|
379
|
+
`;
|
|
380
|
+
return reskinDocument({
|
|
381
|
+
title: "vidfarm reskin — Pricing",
|
|
382
|
+
description: "Reskinned vidfarm pricing — the $99/mo Pro plan, the $0.03/credit wallet, and real Stripe checkout.",
|
|
383
|
+
activeSlug: "pricing",
|
|
384
|
+
pageCss,
|
|
385
|
+
body
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=pricing-page.js.map
|