@mevdragon/vidfarm-devcli 0.17.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.
@@ -1,141 +1,154 @@
1
- // /reskin/help — the vidfarm Help / Getting-started center, ported into the
2
- // farmville design system using ONLY shared rk-* primitives + a page-scoped
3
- // CSS block + a small self-contained module script.
1
+ // /reskin/help — the vidfarm Help center, MIGRATED to real production content.
4
2
  //
5
- // Renders with representative SAMPLE help content so it can be viewed without
6
- // auth. Faithfully mirrors src/help-page.ts renderHelpPage: a friendly intro,
7
- // support entry points (video tutorials / report a bug / contact / billing),
8
- // and a native <details>/<summary> accordion whose items can be deep-linked
9
- // open via an `?expand=id,id` query param (URL stays in sync on toggle).
3
+ // This mirrors the live renderer src/help-page.ts renderHelpPage: it accepts
4
+ // the SAME input object that route builds (`Parameters<typeof renderHelpPage>[0]`
5
+ // `{ account, expand }`) so wiring the route is just "call this instead of the
6
+ // legacy renderer". The accordion is the REAL support surface: the four live
7
+ // sections (video tutorials, report a bug, contact the team, billing/cancel)
8
+ // with their REAL destinations — the same YouTube embed, the two Tally forms,
9
+ // the support email, and the Stripe billing portal the live page ships. The
10
+ // `?expand=id,id` deep-link is honored both server-side (native <details open>)
11
+ // and client-side (URL stays in sync on toggle). Design stays farmville (shared
12
+ // rk-* primitives + a page-scoped CSS block); only the data + links became real.
10
13
  //
11
- // Content is representative vidfarm help: forking templates, editing on the
12
- // timeline, rendering, publishing, and billing / credits.
14
+ // A default input keeps the existing zero-arg route call compiling; the route
15
+ // can pass real `{ account, expand }` whenever it's wired.
13
16
  import { reskinDocument, rkEscape } from "./document.js";
14
17
  const SEARCH_ICON = `<svg class="rk-help-search-ic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>`;
15
- const TOPICS = [
16
- { expand: "getting_started", title: "Getting started", blurb: "Fork a template on Discover and make it yours in a click.", count: "6 articles" },
17
- { expand: "editing", title: "Editing", blurb: "Retime clips, swap media, and edit by chatting on the timeline.", count: "9 articles" },
18
- { expand: "rendering", title: "Rendering", blurb: "Turn your composition into a finished MP4 on HyperFrames.", count: "4 articles" },
19
- { expand: "publishing", title: "Publishing", blurb: "Schedule and post approved cuts straight to your channels.", count: "5 articles" },
20
- { expand: "billing", title: "Billing & credits", blurb: "Understand your wallet, compute charges, and rate limits.", count: "7 articles" }
21
- ];
22
- const FAQ = [
18
+ // The ONLY deep-linkable accordion ids — mirrors HELP_SECTION_IDS in
19
+ // src/help-page.ts. `?expand=` values are filtered against this allowlist.
20
+ const HELP_SECTION_IDS = ["video_tutorials", "report_bug", "contact_team", "cancel_subscription"];
21
+ /** Parse `?expand=id,id` exactly like the live renderer: split, trim, keep only
22
+ * known section ids. */
23
+ function parseExpanded(expand) {
24
+ const allowed = new Set(HELP_SECTION_IDS);
25
+ if (!expand)
26
+ return new Set();
27
+ return new Set(expand
28
+ .split(",")
29
+ .map((value) => value.trim())
30
+ .filter((value) => allowed.has(value)));
31
+ }
32
+ // The four REAL support sections, with the SAME destinations the live help page
33
+ // ships: the YouTube tutorial embed, the two Tally forms, the support email, and
34
+ // the Stripe billing/cancellation portal.
35
+ const SECTIONS = [
23
36
  {
24
- id: "getting_started",
25
- cat: "Getting started",
37
+ id: "video_tutorials",
38
+ cat: "Tutorials",
26
39
  catPill: "rk-pill-green",
27
- question: "How do I start from a template?",
28
- answer: `<p>Head to <strong>Discover</strong> and tap any template to preview it. When one clicks, hit <strong>Fork</strong> — vidfarm copies it into your <strong>Library</strong> as an editable project with its own timeline.</p>
29
- <p>Forking never touches the original, so you're free to remix hooks, captions, and clips however you like. Your forks always show a <em>“Your project”</em> pill so they're easy to spot.</p>`
30
- },
31
- {
32
- id: "editing",
33
- cat: "Editing",
34
- catPill: "rk-pill-sky",
35
- question: "How does the timeline editor work?",
36
- answer: `<p>Open any project in <strong>/editor</strong> to get a track-based timeline. Drag a clip to retime it, click a layer to edit its text or swap the media, and use the handles on the canvas frame to move or resize anything.</p>
37
- <p>Prefer words to knobs? The AI chat dock edits for you in plain language — try <code>make the hook bigger</code> or <code>swap scene 2 for a beach clip</code>. Every change is saved to your project and you'll see a confirmation banner.</p>`
40
+ title: "Video tutorials",
41
+ body: `<div class="rk-help-video">
42
+ <iframe
43
+ src="https://www.youtube.com/embed/BG2vuCDzehc"
44
+ title="Video tutorials"
45
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
46
+ allowfullscreen
47
+ loading="lazy"
48
+ referrerpolicy="strict-origin-when-cross-origin"></iframe>
49
+ </div>`
38
50
  },
39
51
  {
40
- id: "rendering",
41
- cat: "Rendering",
42
- catPill: "rk-pill-violet",
43
- question: "How do I render my video, and what does it cost?",
44
- answer: `<p>Hit <strong>Render</strong> in the editor. Vidfarm renders your composition to a full-quality MP4 on HyperFrames Lambda, and the finished file lands back in your <strong>Library</strong> when it's done.</p>
45
- <p>You're only charged for the <strong>compute time</strong> the render actually uses — your own AI keys are never billed by vidfarm. Long renders keep you posted from the <strong>Job runs</strong> page.</p>`
46
- },
47
- {
48
- id: "publishing",
49
- cat: "Publishing",
52
+ id: "report_bug",
53
+ cat: "Support",
50
54
  catPill: "rk-pill-red",
51
- question: "Can I publish straight to social?",
52
- answer: `<p>Yes. First connect a channel in <strong>Settings → Channels</strong> (via FlockPoster or an email channel). Then, from any finished render, choose <strong>Publish</strong> to post or schedule to TikTok, Instagram, and more.</p>
53
- <p>Scheduled posts show up on your <strong>Calendar</strong>, so you can plan a week of content and let vidfarm ship it for you.</p>`
54
- },
55
- {
56
- id: "billing",
57
- cat: "Billing",
58
- catPill: "rk-pill-gold",
59
- question: "How do credits and billing work?",
60
- answer: `<p>Your <strong>wallet</strong> holds a prepaid balance. Renders and clip scans draw from it based on the compute they use — nothing else. Every charge is itemized in <strong>Settings → Wallet</strong>.</p>
61
- <p>Top up any time, and set <strong>rate limits</strong> like “max spend per hour / per day” so a runaway job can never surprise you.</p>`
55
+ title: "Report a bug",
56
+ body: `<iframe
57
+ class="rk-help-formframe"
58
+ src="https://tally.so/embed/NpdkNj?alignLeft=1&amp;hideTitle=1&amp;transparentBackground=1"
59
+ title="Report a bug"
60
+ loading="lazy"></iframe>`
62
61
  },
63
62
  {
64
- id: "clips",
65
- cat: "Editing",
63
+ id: "contact_team",
64
+ cat: "Support",
66
65
  catPill: "rk-pill-sky",
67
- question: "What are Clips, and how do I use them?",
68
- answer: `<p>Clips are short, tagged moments mined from your footage. Point vidfarm at a source video and it cuts the video into scenes, tags each one, and drops them into your <strong>Clips</strong> library.</p>
69
- <p>From there you can search by what's on screen (“founder on camera”, “product close-up”) and drop any clip straight into a project on the timeline.</p>`
70
- },
71
- {
72
- id: "ai_keys",
73
- cat: "Billing",
74
- catPill: "rk-pill-gold",
75
- question: "Do I need my own AI keys?",
76
- answer: `<p>Bring-your-own keys — OpenAI, Gemini, OpenRouter, or Anthropic — power clip tagging, recaption, and Swipe mode. Add them in <strong>Settings → Developer</strong>; they're stored per workspace.</p>
77
- <p>These keys are <strong>never billed by vidfarm</strong>. You only ever pay us for render and clip-scan compute.</p>`
66
+ title: "Ask a question / contact the team",
67
+ body: `<iframe
68
+ class="rk-help-formframe"
69
+ src="https://tally.so/embed/javNVa?alignLeft=1&amp;hideTitle=1&amp;transparentBackground=1"
70
+ title="Ask a question / contact the team"
71
+ loading="lazy"></iframe>`
78
72
  },
79
73
  {
80
- id: "cancel",
74
+ id: "cancel_subscription",
81
75
  cat: "Billing",
82
76
  catPill: "rk-pill-gold",
83
- question: "How do I cancel or get a refund?",
84
- answer: `<p>You can cancel any time from the self-serve billing portal — no email required. We also offer <strong>90-day refunds</strong>.</p>
85
- <p>For a refund, email <a href="mailto:operator@zoom-gtm.com">operator@zoom-gtm.com</a> and mention the email address you used at checkout so we can find your subscription quickly.</p>
86
- <p><a class="rk-help-inline-cta" href="https://billing.stripe.com/p/login/fZecQubkxcwOgDuaEE" target="_blank" rel="noopener noreferrer">Open the billing portal <span class="rk-arrow">↗</span></a></p>`
77
+ title: "Cancel subscription & refunds",
78
+ body: `<div class="rk-help-copy">
79
+ <p>You can cancel your subscription at any time with a simple self-serve billing portal.</p>
80
+ <p>For refunds, we offer <strong>90-day refunds</strong>. Email <a href="mailto:operator@zoom-gtm.com">operator@zoom-gtm.com</a> and mention the original email address you used in Stripe so we can find your subscription quickly.</p>
81
+ <p><a class="rk-help-inline-cta" href="https://billing.stripe.com/p/login/fZecQubkxcwOgDuaEE" target="_blank" rel="noopener noreferrer">Open the cancellation portal <span class="rk-arrow">↗</span></a></p>
82
+ </div>`
87
83
  }
88
84
  ];
85
+ // Browse-by-topic quick nav — every card deep-links straight to a REAL section.
86
+ const TOPICS = [
87
+ { expand: "video_tutorials", title: "Video tutorials", blurb: "A quick tour from your first fork to your first published post.", action: "Watch" },
88
+ { expand: "report_bug", title: "Report a bug", blurb: "Something broken? Send us the details in about 30 seconds.", action: "Report" },
89
+ { expand: "contact_team", title: "Ask the team", blurb: "Questions about your workflow? Reach a real human, fast.", action: "Contact" },
90
+ { expand: "cancel_subscription", title: "Billing & cancellation", blurb: "Refunds, invoices, or cancel any time — no email required.", action: "Manage" }
91
+ ];
92
+ // Real quick actions. Two deep-link into the accordion; the billing portal is
93
+ // the live external Stripe portal.
89
94
  const SUPPORT_LINKS = [
90
- { title: "Watch the tutorials", blurb: "A 4-minute tour from fork to published post.", cta: "Play video", href: "#" },
91
- { title: "Report a bug", blurb: "Something broken? Send us the details in 30 seconds.", cta: "Report it", href: "#" },
92
- { title: "Billing portal", blurb: "Update payment, download invoices, or cancel.", cta: "Manage billing", href: "#" }
95
+ { title: "Watch the tutorials", blurb: "A short tour from fork to published post.", cta: "Play video", href: "?expand=video_tutorials#rk-help-faq" },
96
+ { title: "Report a bug", blurb: "Something broken? Send us the details in 30 seconds.", cta: "Report it", href: "?expand=report_bug#rk-help-faq" },
97
+ { title: "Billing portal", blurb: "Update payment, download invoices, or cancel.", cta: "Manage billing", href: "https://billing.stripe.com/p/login/fZecQubkxcwOgDuaEE", external: true }
98
+ ];
99
+ // Popular chips — every one deep-links a REAL section.
100
+ const POPULAR = [
101
+ { label: "Video tutorials", expand: "video_tutorials" },
102
+ { label: "Report a bug", expand: "report_bug" },
103
+ { label: "Contact the team", expand: "contact_team" },
104
+ { label: "Cancel or refund", expand: "cancel_subscription" }
93
105
  ];
94
106
  function renderTopicCard(t) {
95
107
  return `<a class="rk-card rk-card-hover rk-help-topic" href="?expand=${rkEscape(t.expand)}#rk-help-faq">
96
108
  <h3>${rkEscape(t.title)}</h3>
97
109
  <p class="rk-muted rk-help-topic-blurb">${rkEscape(t.blurb)}</p>
98
- <span class="rk-help-topic-foot">${rkEscape(t.count)} <span class="rk-arrow">→</span></span>
110
+ <span class="rk-help-topic-foot">${rkEscape(t.action)} <span class="rk-arrow">→</span></span>
99
111
  </a>`;
100
112
  }
101
- function renderFaqItem(f) {
102
- return `<details class="rk-help-faq-item" data-expand-id="${rkEscape(f.id)}">
113
+ function renderSection(s, open) {
114
+ const search = `${s.title} ${s.cat}`.toLowerCase();
115
+ return `<details class="rk-help-faq-item" data-expand-id="${rkEscape(s.id)}" data-search="${rkEscape(search)}"${open ? " open" : ""}>
103
116
  <summary class="rk-help-faq-summary">
104
117
  <span class="rk-help-faq-q">
105
- <span class="rk-pill ${f.catPill} rk-help-faq-cat">${rkEscape(f.cat)}</span>
106
- <span class="rk-help-faq-qt">${rkEscape(f.question)}</span>
118
+ <span class="rk-pill ${s.catPill} rk-help-faq-cat">${rkEscape(s.cat)}</span>
119
+ <span class="rk-help-faq-qt">${rkEscape(s.title)}</span>
107
120
  </span>
108
121
  <span class="rk-help-faq-toggle" aria-hidden="true"></span>
109
122
  </summary>
110
- <div class="rk-help-faq-body">${f.answer}</div>
123
+ <div class="rk-help-faq-body">${s.body}</div>
111
124
  </details>`;
112
125
  }
113
126
  function renderSupportLink(s) {
114
- return `<a class="rk-item rk-help-support" href="${rkEscape(s.href)}">
127
+ const attrs = s.external ? ` target="_blank" rel="noopener noreferrer"` : "";
128
+ return `<a class="rk-item rk-help-support" href="${rkEscape(s.href)}"${attrs}>
115
129
  <div class="rk-item-main">
116
130
  <div class="rk-item-title">${rkEscape(s.title)}</div>
117
131
  <div class="rk-help-support-sub rk-muted">${rkEscape(s.blurb)}</div>
118
132
  </div>
119
- <span class="rk-btn rk-btn-ghost rk-btn-sm">${rkEscape(s.cta)} <span class="rk-arrow">→</span></span>
133
+ <span class="rk-btn rk-btn-ghost rk-btn-sm">${rkEscape(s.cta)} <span class="rk-arrow">${s.external ? "↗" : "→"}</span></span>
120
134
  </a>`;
121
135
  }
122
- export function renderReskinHelp() {
123
- const popular = ["Fork a template", "Render an MP4", "Connect TikTok", "Wallet top-up", "What are Clips?"];
136
+ export function renderReskinHelp(input = { account: { isLoggedIn: false }, expand: null }) {
137
+ const expanded = parseExpanded(input.expand);
124
138
  const hero = `
125
139
  <div class="rk-hero-band rk-help-band">
126
140
  <div class="rk-hero-art rk-help-art"></div>
127
141
  <div class="rk-hero rk-help-hero">
128
- <span class="rk-badge rk-help-badge">Help center</span>
129
142
  <h1 class="rk-help-title">How can we help?</h1>
130
143
  <p class="rk-help-lede">Guides and answers for making short-form video with vidfarm — from your first fork to your first published post.</p>
131
144
  <form class="rk-help-search" role="search" onsubmit="return false">
132
145
  ${SEARCH_ICON}
133
- <input class="rk-help-search-input" type="search" placeholder="Search help articles — “how do I render?”" aria-label="Search help articles" />
146
+ <input class="rk-help-search-input" type="search" placeholder="Search help — “render”, “bug”, “cancel”…" aria-label="Search help topics" data-help-search />
134
147
  <button class="rk-btn rk-btn-gold rk-help-search-btn" type="submit">Search</button>
135
148
  </form>
136
149
  <div class="rk-help-popular">
137
150
  <span class="rk-help-popular-label">Popular:</span>
138
- ${popular.map((p) => `<a class="rk-help-chip" href="#rk-help-faq">${rkEscape(p)}</a>`).join("")}
151
+ ${POPULAR.map((p) => `<a class="rk-help-chip" href="?expand=${rkEscape(p.expand)}#rk-help-faq">${rkEscape(p.label)}</a>`).join("")}
139
152
  </div>
140
153
  </div>
141
154
  </div>`;
@@ -148,13 +161,14 @@ export function renderReskinHelp() {
148
161
  const faq = `
149
162
  <section class="rk-help-faq" id="rk-help-faq" aria-labelledby="rk-help-faq-title">
150
163
  <div class="rk-page-head rk-help-faq-head">
151
- <span class="rk-eyebrow">Frequently asked</span>
152
- <h2 class="rk-h1 rk-help-faq-h" id="rk-help-faq-title">Questions, answered</h2>
153
- <p class="rk-sub">The quick answers most folks are looking for. Still stuck? We're one message away.</p>
164
+ <span class="rk-eyebrow">Help topics</span>
165
+ <h2 class="rk-h1 rk-help-faq-h" id="rk-help-faq-title">Guides & support</h2>
166
+ <p class="rk-sub">Watch a tutorial, report a bug, reach the team, or manage billing. Still stuck? We're one message away.</p>
154
167
  </div>
155
- <div class="rk-help-faq-list">
156
- ${FAQ.map(renderFaqItem).join("")}
168
+ <div class="rk-help-faq-list" data-help-list>
169
+ ${SECTIONS.map((s) => renderSection(s, expanded.has(s.id))).join("")}
157
170
  </div>
171
+ <p class="rk-muted rk-help-empty" data-help-empty hidden>No help topics match your search.</p>
158
172
  </section>`;
159
173
  const contact = `
160
174
  <section class="rk-help-contact" aria-label="Contact support">
@@ -191,7 +205,7 @@ ${hero}
191
205
  .rk-help-badge{background:rgba(255,255,255,.9);backdrop-filter:blur(6px)}
192
206
  .rk-help-title{font-size:clamp(2.4rem,6vw,3.6rem);letter-spacing:-.035em;color:#fff;text-shadow:0 2px 18px rgba(20,54,90,.28)}
193
207
  .rk-help-lede{font-size:var(--rk-text-lg);color:#f3fbff;max-width:560px;line-height:1.55;text-shadow:0 1px 10px rgba(20,54,90,.25)}
194
- /* decorative search */
208
+ /* search */
195
209
  .rk-help-search{position:relative;display:flex;align-items:center;width:min(560px,100%);
196
210
  background:#fff;border-radius:var(--rk-r-full);box-shadow:var(--rk-shadow-lg);padding:7px 7px 7px 18px;margin-top:6px}
197
211
  .rk-help-search-ic{color:var(--rk-n-400);flex:none}
@@ -211,20 +225,18 @@ ${hero}
211
225
 
212
226
  /* ── topic cards (pulled up onto the hero seam) ── */
213
227
  .rk-help-topics{margin-top:-72px}
214
- .rk-help-topic-grid{grid-template-columns:repeat(5,1fr)}
215
- @media(max-width:1080px){.rk-help-topic-grid{grid-template-columns:repeat(3,1fr)}}
216
- @media(max-width:720px){.rk-help-topic-grid{grid-template-columns:repeat(2,1fr)}}
228
+ .rk-help-topic-grid{grid-template-columns:repeat(4,1fr)}
229
+ @media(max-width:1080px){.rk-help-topic-grid{grid-template-columns:repeat(2,1fr)}}
217
230
  @media(max-width:460px){.rk-help-topic-grid{grid-template-columns:1fr}}
218
231
  .rk-help-topic{display:grid;gap:10px;align-content:start;padding:24px;text-align:left}
219
232
  .rk-help-topic h3{font-size:var(--rk-text-lg);font-weight:800;font-family:var(--rk-font-display);color:var(--rk-ink)}
220
- .rk-help-topic-tile{margin-bottom:4px}
221
233
  .rk-help-topic-blurb{font-size:13.5px;line-height:1.5}
222
234
  .rk-help-topic-foot{display:inline-flex;align-items:center;gap:6px;margin-top:6px;
223
235
  font-size:13px;font-weight:700;color:var(--rk-gold-700)}
224
236
  .rk-help-topic:hover .rk-help-topic-foot .rk-arrow{transform:translateX(3px)}
225
237
  .rk-help-topic-foot .rk-arrow{transition:transform var(--rk-dur) var(--rk-ease)}
226
238
 
227
- /* ── FAQ accordion ── */
239
+ /* ── accordion (real support sections) ── */
228
240
  .rk-help-faq{margin-top:72px}
229
241
  .rk-help-faq-head{justify-items:center;text-align:center;margin-bottom:28px;gap:10px}
230
242
  .rk-help-faq-h{font-size:var(--rk-text-3xl)}
@@ -232,6 +244,7 @@ ${hero}
232
244
  .rk-help-faq-item{background:var(--rk-surface);border:1px solid var(--rk-border);border-radius:var(--rk-r-2xl);
233
245
  box-shadow:var(--rk-shadow-card);overflow:hidden;transition:box-shadow var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease)}
234
246
  .rk-help-faq-item[open]{border-color:var(--rk-gold-tint2);box-shadow:var(--rk-shadow-lg)}
247
+ .rk-help-faq-item[hidden]{display:none}
235
248
  .rk-help-faq-summary{display:flex;align-items:center;justify-content:space-between;gap:16px;
236
249
  padding:20px 22px;cursor:pointer;list-style:none;user-select:none}
237
250
  .rk-help-faq-summary::-webkit-details-marker{display:none}
@@ -247,13 +260,20 @@ ${hero}
247
260
  .rk-help-faq-item[open] .rk-help-faq-toggle::after{opacity:0}
248
261
  .rk-help-faq-item[open] .rk-help-faq-toggle::before{background:var(--rk-gold-700)}
249
262
  .rk-help-faq-summary:hover .rk-help-faq-toggle{background:var(--rk-n-200)}
250
- .rk-help-faq-body{display:grid;gap:12px;padding:0 22px 22px;max-width:680px;color:var(--rk-text);line-height:1.65;font-size:14.5px}
263
+ .rk-help-faq-body{display:grid;gap:12px;padding:0 22px 22px;color:var(--rk-text);line-height:1.65;font-size:14.5px}
251
264
  .rk-help-faq-body p{margin:0}
252
265
  .rk-help-faq-body strong{color:var(--rk-n-800);font-weight:600}
253
266
  .rk-help-faq-body a{color:var(--rk-gold-700);font-weight:600;text-decoration:underline;text-underline-offset:2px}
254
267
  .rk-help-inline-cta{display:inline-flex;align-items:center;gap:6px;text-decoration:none!important}
255
268
  .rk-help-inline-cta .rk-arrow{transition:transform var(--rk-dur) var(--rk-ease)}
256
269
  .rk-help-inline-cta:hover .rk-arrow{transform:translateX(3px)}
270
+ .rk-help-copy{display:grid;gap:12px;max-width:680px}
271
+ /* embedded iframes (real tutorial + Tally forms) */
272
+ .rk-help-video{position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;border-radius:var(--rk-r-xl);background:#151a23}
273
+ .rk-help-video iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
274
+ .rk-help-formframe{display:block;width:100%;min-height:680px;border:0;border-radius:var(--rk-r-xl);background:rgba(255,255,255,.72)}
275
+ @media(max-width:560px){.rk-help-formframe{min-height:75svh}}
276
+ .rk-help-empty{text-align:center;max-width:820px;margin:18px auto 0}
257
277
 
258
278
  /* ── contact ── */
259
279
  .rk-help-contact{margin-top:72px;display:grid;gap:20px}
@@ -293,9 +313,11 @@ ${hero}
293
313
  }
294
314
  }
295
315
 
316
+ // Union of server-rendered [open] and any ?expand= in the URL, so both the
317
+ // static server render and client-side deep links open the right sections.
296
318
  var initial=readExpandedFromUrl();
297
319
  items.forEach(function(d){
298
- d.open=Boolean(d.dataset.expandId&&initial[d.dataset.expandId]);
320
+ d.open=d.open||Boolean(d.dataset.expandId&&initial[d.dataset.expandId]);
299
321
  d.addEventListener('toggle',writeExpandedToUrl);
300
322
  });
301
323
 
@@ -305,10 +327,26 @@ ${hero}
305
327
  firstOpen.scrollIntoView({block:'center',behavior:'smooth'});
306
328
  });
307
329
  }
330
+
331
+ // Search filters the visible support sections.
332
+ var search=document.querySelector('[data-help-search]');
333
+ var empty=document.querySelector('[data-help-empty]');
334
+ if(search){
335
+ search.addEventListener('input',function(){
336
+ var q=(search.value||'').trim().toLowerCase();
337
+ var shown=0;
338
+ items.forEach(function(d){
339
+ var hit=!q||(d.dataset.search||'').indexOf(q)!==-1;
340
+ d.hidden=!hit;
341
+ if(hit)shown++;
342
+ });
343
+ if(empty)empty.hidden=shown!==0;
344
+ });
345
+ }
308
346
  })();`;
309
347
  return reskinDocument({
310
348
  title: "vidfarm reskin — Help",
311
- description: "Reskinned vidfarm help center — getting started, topics, FAQ, and support.",
349
+ description: "Reskinned vidfarm help center — tutorials, bug reports, contact, and billing.",
312
350
  activeSlug: "help",
313
351
  pageCss,
314
352
  body,
@@ -7,7 +7,7 @@ export function renderReskinIndex() {
7
7
  const status = p.done
8
8
  ? `<span class="rk-pill rk-pill-green">✓ Ported</span>`
9
9
  : `<span class="rk-pill">In progress</span>`;
10
- const href = p.done ? `/reskin/${p.slug}` : `/reskin/${p.slug}`;
10
+ const href = `/${p.slug}`;
11
11
  return `<a class="rk-card rk-card-hover rk-idx-card" href="${href}">
12
12
  <div class="rk-spread">
13
13
  <span class="rk-tile rk-tile-lg rk-tile-gold rk-idx-mark">${p.label.charAt(0)}</span>