@rune-kit/rune 2.3.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +86 -17
  2. package/compiler/__tests__/pack-split.test.js +141 -1
  3. package/compiler/__tests__/parser.test.js +147 -55
  4. package/compiler/__tests__/scripts-bundling.test.js +283 -0
  5. package/compiler/__tests__/skill-index.test.js +218 -0
  6. package/compiler/__tests__/tier-override.test.js +41 -0
  7. package/compiler/adapters/antigravity.js +71 -53
  8. package/compiler/adapters/codex.js +4 -0
  9. package/compiler/adapters/cursor.js +4 -0
  10. package/compiler/adapters/generic.js +4 -0
  11. package/compiler/adapters/openclaw.js +4 -0
  12. package/compiler/adapters/opencode.js +4 -0
  13. package/compiler/adapters/windsurf.js +4 -0
  14. package/compiler/bin/rune.js +355 -355
  15. package/compiler/doctor.js +11 -1
  16. package/compiler/emitter.js +678 -386
  17. package/compiler/parser.js +267 -247
  18. package/compiler/transforms/scripts-path.js +18 -0
  19. package/extensions/zalo/PACK.md +20 -1
  20. package/extensions/zalo/references/conversation-management.md +214 -0
  21. package/extensions/zalo/references/eval-scenarios.md +157 -0
  22. package/extensions/zalo/references/listen-mode.md +237 -0
  23. package/extensions/zalo/references/mcp-production.md +274 -0
  24. package/extensions/zalo/references/multi-account-proxy.md +224 -0
  25. package/extensions/zalo/references/vietqr-banking.md +160 -0
  26. package/hooks/hooks.json +12 -0
  27. package/hooks/intent-router/index.cjs +108 -0
  28. package/hooks/pre-tool-guard/index.cjs +177 -68
  29. package/package.json +63 -64
  30. package/skills/brainstorm/SKILL.md +2 -0
  31. package/skills/cook/SKILL.md +661 -648
  32. package/skills/debug/SKILL.md +394 -392
  33. package/skills/deploy/SKILL.md +2 -0
  34. package/skills/fix/SKILL.md +283 -281
  35. package/skills/marketing/SKILL.md +3 -0
  36. package/skills/onboard/SKILL.md +7 -0
  37. package/skills/plan/SKILL.md +344 -342
  38. package/skills/preflight/SKILL.md +362 -360
  39. package/skills/review/SKILL.md +491 -489
  40. package/skills/scout/SKILL.md +1 -0
  41. package/skills/sentinel/SKILL.md +319 -296
  42. package/skills/sentinel/references/auth-crypto-reference.md +192 -0
  43. package/skills/sentinel/references/desktop-security.md +201 -0
  44. package/skills/sentinel/references/supply-chain.md +160 -0
  45. package/skills/session-bridge/SKILL.md +1 -0
  46. package/skills/slides/SKILL.md +142 -0
  47. package/skills/slides/scripts/build-deck.js +158 -0
  48. package/skills/team/SKILL.md +1 -0
  49. package/skills/test/SKILL.md +587 -585
  50. package/skills/verification/SKILL.md +1 -0
  51. package/skills/watchdog/SKILL.md +2 -0
  52. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
  53. package/docs/ARCHITECTURE.md +0 -332
  54. package/docs/COMMUNITY-PACKS.md +0 -109
  55. package/docs/CONTRIBUTING-L4.md +0 -215
  56. package/docs/CROSS-IDE-ANALYSIS.md +0 -164
  57. package/docs/EXTENSION-TEMPLATE.md +0 -126
  58. package/docs/MESH-RULES.md +0 -34
  59. package/docs/MULTI-PLATFORM.md +0 -804
  60. package/docs/SKILL-DEPTH-AUDIT.md +0 -191
  61. package/docs/SKILL-TEMPLATE.md +0 -118
  62. package/docs/TRADE-MATRIX.md +0 -327
  63. package/docs/VERSIONING.md +0 -91
  64. package/docs/VISION.md +0 -263
  65. package/docs/assets/demo-subtitles.srt +0 -215
  66. package/docs/assets/end-card.html +0 -276
  67. package/docs/assets/mesh-diagram.html +0 -654
  68. package/docs/assets/thumbnail.html +0 -295
  69. package/docs/guides/cli.md +0 -403
  70. package/docs/guides/index.html +0 -1450
  71. package/docs/index.html +0 -1005
  72. package/docs/references/claudekit-analysis.md +0 -414
  73. package/docs/references/voltagent-analysis.md +0 -189
  74. package/docs/script.js +0 -495
  75. package/docs/skills/index.html +0 -832
  76. package/docs/style.css +0 -958
  77. package/docs/video-demo-plan.md +0 -172
package/docs/script.js DELETED
@@ -1,495 +0,0 @@
1
- // ─── Mesh Canvas Animation ───
2
- (function initMesh() {
3
- const canvas = document.getElementById('mesh-canvas');
4
- if (!canvas) return;
5
- const ctx = canvas.getContext('2d');
6
- let w, h, nodes, animId;
7
-
8
- function resize() {
9
- w = canvas.width = canvas.parentElement.offsetWidth;
10
- h = canvas.height = canvas.parentElement.offsetHeight;
11
- }
12
-
13
- function createNodes(count) {
14
- const arr = [];
15
- for (let i = 0; i < count; i++) {
16
- arr.push({
17
- x: Math.random() * w,
18
- y: Math.random() * h,
19
- vx: (Math.random() - 0.5) * 0.4,
20
- vy: (Math.random() - 0.5) * 0.4,
21
- r: Math.random() * 2 + 1
22
- });
23
- }
24
- return arr;
25
- }
26
-
27
- function draw() {
28
- ctx.clearRect(0, 0, w, h);
29
- const maxDist = 160;
30
-
31
- // move nodes
32
- for (const n of nodes) {
33
- n.x += n.vx;
34
- n.y += n.vy;
35
- if (n.x < 0 || n.x > w) n.vx *= -1;
36
- if (n.y < 0 || n.y > h) n.vy *= -1;
37
- }
38
-
39
- // draw connections
40
- for (let i = 0; i < nodes.length; i++) {
41
- for (let j = i + 1; j < nodes.length; j++) {
42
- const dx = nodes[i].x - nodes[j].x;
43
- const dy = nodes[i].y - nodes[j].y;
44
- const dist = Math.sqrt(dx * dx + dy * dy);
45
- if (dist < maxDist) {
46
- const alpha = (1 - dist / maxDist) * 0.5;
47
- ctx.strokeStyle = `rgba(60, 85, 50, ${alpha})`;
48
- ctx.lineWidth = 0.8;
49
- ctx.beginPath();
50
- ctx.moveTo(nodes[i].x, nodes[i].y);
51
- ctx.lineTo(nodes[j].x, nodes[j].y);
52
- ctx.stroke();
53
- }
54
- }
55
- }
56
-
57
- // draw nodes
58
- for (const n of nodes) {
59
- ctx.fillStyle = 'rgba(60, 85, 50, 0.7)';
60
- ctx.beginPath();
61
- ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
62
- ctx.fill();
63
- }
64
-
65
- animId = requestAnimationFrame(draw);
66
- }
67
-
68
- function start() {
69
- resize();
70
- const density = Math.min(60, Math.floor((w * h) / 15000));
71
- nodes = createNodes(density);
72
- if (animId) cancelAnimationFrame(animId);
73
- draw();
74
- }
75
-
76
- // respect reduced motion
77
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
78
- resize();
79
- nodes = createNodes(30);
80
- // draw one static frame
81
- draw();
82
- cancelAnimationFrame(animId);
83
- return;
84
- }
85
-
86
- start();
87
- window.addEventListener('resize', () => { start(); });
88
- })();
89
-
90
- // ─── Scroll-triggered animations ───
91
- (function initScrollAnimations() {
92
- const observer = new IntersectionObserver((entries) => {
93
- entries.forEach(entry => {
94
- if (entry.isIntersecting) {
95
- entry.target.style.animationPlayState = 'running';
96
- observer.unobserve(entry.target);
97
- }
98
- });
99
- }, { threshold: 0.1 });
100
-
101
- document.querySelectorAll('.layer, .feature-card, .workflow-card, .compare-card, .ext-card, .install-card, .pricing-card, .stat, .mesh-tier, .mesh-point').forEach(el => {
102
- el.style.animationPlayState = 'paused';
103
- observer.observe(el);
104
- });
105
- })();
106
-
107
- // ─── Counter animation ───
108
- (function initCounters() {
109
- const observer = new IntersectionObserver((entries) => {
110
- entries.forEach(entry => {
111
- if (entry.isIntersecting) {
112
- const el = entry.target;
113
- const target = parseInt(el.dataset.target, 10);
114
- const duration = 1200;
115
- const start = performance.now();
116
-
117
- function tick(now) {
118
- const elapsed = now - start;
119
- const progress = Math.min(elapsed / duration, 1);
120
- // ease out cubic
121
- const eased = 1 - Math.pow(1 - progress, 3);
122
- el.textContent = Math.round(target * eased);
123
- if (progress < 1) requestAnimationFrame(tick);
124
- }
125
-
126
- requestAnimationFrame(tick);
127
- observer.unobserve(el);
128
- }
129
- });
130
- }, { threshold: 0.5 });
131
-
132
- document.querySelectorAll('.stat-num[data-target]').forEach(el => observer.observe(el));
133
- })();
134
-
135
- // ─── Payment Flow ───
136
- const PAY_API = 'https://pay.theio.vn';
137
- const BANK_ID = 'tpbank';
138
- const BANK_ACCOUNT = '04162263666';
139
- const ACCOUNT_NAME = 'NGUYEN VIET NAM';
140
-
141
- // Launch promo: 50% off for 7 days
142
- const PROMO = {
143
- discount: 0.5,
144
- endsAt: new Date('2026-03-25T23:59:59+07:00').getTime(),
145
- };
146
-
147
- function isPromoActive() {
148
- return Date.now() < PROMO.endsAt;
149
- }
150
-
151
- function fmtVND(n) {
152
- return n.toLocaleString('vi-VN') + ' VND';
153
- }
154
-
155
- const PRODUCT_INFO = {
156
- 'rune-pro': {
157
- title: 'Get Rune Pro', titleShort: 'Rune Pro',
158
- basePrice: 1190000, baseIntl: 49,
159
- btnClass: 'btn-pro',
160
- },
161
- 'rune-biz': {
162
- title: 'Get Rune Business', titleShort: 'Rune Business',
163
- basePrice: 3590000, baseIntl: 149,
164
- btnClass: 'btn-biz',
165
- },
166
- };
167
-
168
- function getProductPricing(key) {
169
- const info = PRODUCT_INFO[key];
170
- const promo = isPromoActive();
171
- const price = promo ? Math.round(info.basePrice * (1 - PROMO.discount)) : info.basePrice;
172
- const intl = promo ? Math.round(info.baseIntl * (1 - PROMO.discount)) : info.baseIntl;
173
- return {
174
- ...info,
175
- priceVN: fmtVND(price),
176
- priceIntl: '$' + intl + ' USD',
177
- amountLabel: fmtVND(price) + ' (~$' + intl + ' USD)',
178
- promo,
179
- originalVN: fmtVND(info.basePrice),
180
- originalIntl: '$' + info.baseIntl + ' USD',
181
- };
182
- }
183
-
184
- let payState = { product: null, orderCode: null, pollTimer: null };
185
-
186
- function openPayment(product) {
187
- const info = getProductPricing(product);
188
- if (!info) return;
189
-
190
- payState = { product, orderCode: null, pollTimer: null };
191
-
192
- document.getElementById('pay-title').textContent = info.title;
193
- document.getElementById('pay-title-2').textContent = info.title;
194
- document.getElementById('pay-amount-2').textContent = info.amountLabel;
195
-
196
- const vnPriceEl = document.getElementById('pay-price-vn');
197
- const intlPriceEl = document.getElementById('pay-price-intl');
198
- if (info.promo) {
199
- vnPriceEl.innerHTML = '<s style="opacity:.5">' + info.originalVN + '</s> ' + info.priceVN;
200
- intlPriceEl.innerHTML = '<s style="opacity:.5">' + info.originalIntl + '</s> ' + info.priceIntl;
201
- } else {
202
- vnPriceEl.textContent = info.priceVN;
203
- intlPriceEl.textContent = info.priceIntl;
204
- }
205
-
206
- document.getElementById('pay-github').value = '';
207
- document.getElementById('pay-email').value = '';
208
- document.getElementById('pay-error').hidden = true;
209
-
210
- const submitBtn = document.getElementById('pay-submit');
211
- submitBtn.className = 'btn ' + info.btnClass;
212
- submitBtn.style.width = '100%';
213
- submitBtn.style.marginTop = '16px';
214
-
215
- showPayStep(1);
216
- document.getElementById('pay-modal').hidden = false;
217
- document.body.style.overflow = 'hidden';
218
- }
219
-
220
- function closePayment() {
221
- document.getElementById('pay-modal').hidden = true;
222
- document.body.style.overflow = '';
223
- if (payState.pollTimer) {
224
- clearInterval(payState.pollTimer);
225
- payState.pollTimer = null;
226
- }
227
- }
228
-
229
- function showPayStep(n) {
230
- for (let i = 1; i <= 6; i++) {
231
- const el = document.getElementById('pay-step-' + i);
232
- if (el) el.hidden = (i !== n);
233
- }
234
- }
235
-
236
- function showVNPayment() {
237
- showPayStep(2);
238
- document.getElementById('pay-github').focus();
239
- }
240
-
241
- function showIntlPayment() {
242
- const info = getProductPricing(payState.product);
243
- document.getElementById('pay-title-5').textContent = info.title;
244
- document.getElementById('pay-amount-5').textContent = info.priceIntl;
245
- document.getElementById('pay-intl-github').value = '';
246
- document.getElementById('pay-intl-email').value = '';
247
- document.getElementById('pay-intl-error').hidden = true;
248
-
249
- const btn = document.getElementById('pay-intl-submit');
250
- btn.className = 'btn ' + info.btnClass;
251
- btn.style.width = '100%';
252
- btn.style.marginTop = '16px';
253
-
254
- showPayStep(5);
255
- document.getElementById('pay-intl-github').focus();
256
- }
257
-
258
- async function startPolarCheckout() {
259
- const github = document.getElementById('pay-intl-github').value.trim();
260
- const email = document.getElementById('pay-intl-email').value.trim();
261
- const errorEl = document.getElementById('pay-intl-error');
262
-
263
- if (!github) {
264
- errorEl.textContent = 'Please enter your GitHub username';
265
- errorEl.hidden = false;
266
- return;
267
- }
268
-
269
- if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/.test(github)) {
270
- errorEl.textContent = 'Invalid GitHub username format';
271
- errorEl.hidden = false;
272
- return;
273
- }
274
-
275
- errorEl.hidden = true;
276
- const btn = document.getElementById('pay-intl-submit');
277
- btn.disabled = true;
278
- btn.textContent = 'Redirecting to checkout...';
279
-
280
- try {
281
- const res = await fetch(PAY_API + '/checkout/polar', {
282
- method: 'POST',
283
- headers: { 'Content-Type': 'application/json' },
284
- body: JSON.stringify({
285
- product: payState.product,
286
- githubUsername: github,
287
- email: email || undefined,
288
- }),
289
- });
290
-
291
- const data = await res.json();
292
- if (!data.success || !data.url) throw new Error(data.error || 'Failed to create checkout');
293
-
294
- // Redirect to Polar checkout page (context embedded in success_url by worker)
295
- window.location.href = data.url;
296
- } catch (err) {
297
- errorEl.textContent = err.message;
298
- errorEl.hidden = false;
299
- } finally {
300
- btn.disabled = false;
301
- btn.textContent = 'Pay with Card / PayPal';
302
- }
303
- }
304
-
305
- async function createOrder() {
306
- const github = document.getElementById('pay-github').value.trim();
307
- const email = document.getElementById('pay-email').value.trim();
308
- const errorEl = document.getElementById('pay-error');
309
-
310
- if (!github) {
311
- errorEl.textContent = 'Please enter your GitHub username';
312
- errorEl.hidden = false;
313
- return;
314
- }
315
-
316
- if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/.test(github)) {
317
- errorEl.textContent = 'Invalid GitHub username format';
318
- errorEl.hidden = false;
319
- return;
320
- }
321
-
322
- errorEl.hidden = true;
323
- const submitBtn = document.getElementById('pay-submit');
324
- submitBtn.disabled = true;
325
- submitBtn.textContent = 'Creating order...';
326
-
327
- try {
328
- const res = await fetch(PAY_API + '/order/create', {
329
- method: 'POST',
330
- headers: { 'Content-Type': 'application/json' },
331
- body: JSON.stringify({ product: payState.product, githubUsername: github, email: email || undefined }),
332
- });
333
-
334
- const data = await res.json();
335
- if (!data.success) throw new Error(data.error || 'Failed to create order');
336
-
337
- payState.orderCode = data.orderCode;
338
-
339
- const qrUrl = `https://img.vietqr.io/image/${BANK_ID}-${BANK_ACCOUNT}-compact.png`
340
- + `?amount=${data.amount}&addInfo=${encodeURIComponent(data.orderCode)}`
341
- + `&accountName=${encodeURIComponent(ACCOUNT_NAME)}`;
342
-
343
- document.getElementById('pay-qr-img').src = qrUrl;
344
- document.getElementById('pay-detail-amount').textContent = data.amount.toLocaleString('vi-VN') + ' VND';
345
- document.getElementById('pay-detail-code').textContent = data.orderCode;
346
-
347
- showPayStep(3);
348
- startPolling(github);
349
- } catch (err) {
350
- errorEl.textContent = err.message;
351
- errorEl.hidden = false;
352
- } finally {
353
- submitBtn.disabled = false;
354
- submitBtn.textContent = 'Continue to Payment';
355
- }
356
- }
357
-
358
- function startPolling(github) {
359
- if (payState.pollTimer) clearInterval(payState.pollTimer);
360
-
361
- payState.pollTimer = setInterval(async () => {
362
- try {
363
- const res = await fetch(PAY_API + '/order/' + payState.orderCode);
364
- const data = await res.json();
365
-
366
- if (data.status === 'delivered') {
367
- clearInterval(payState.pollTimer);
368
- payState.pollTimer = null;
369
- // VN users → Vietnamese success (step 6)
370
- const vnUser = document.getElementById('pay-success-user-vn');
371
- if (vnUser) vnUser.textContent = github;
372
- const vnInstall = document.getElementById('pay-success-install-vn');
373
- if (vnInstall && (payState.product === 'rune-biz' || payState.product === 'RUNE-BIZ')) {
374
- vnInstall.textContent = 'Bạn nhận cả Pro + Business packs — xem README trong repo';
375
- }
376
- showPayStep(6);
377
- } else if (data.status === 'underpaid') {
378
- document.getElementById('pay-status').innerHTML =
379
- '<span style="color:var(--loss)">&#9888; Amount too low. Please transfer the exact amount.</span>';
380
- }
381
- } catch (_) {
382
- // silent retry
383
- }
384
- }, 5000);
385
- }
386
-
387
- // ─── Promo Countdown ───
388
- (function initPromoCountdown() {
389
- if (!isPromoActive()) return;
390
-
391
- // Add "50% OFF" ribbon to Pro and Business cards
392
- ['.pricing-pro', '.pricing-biz'].forEach(sel => {
393
- const card = document.querySelector(sel);
394
- if (!card) return;
395
- const ribbon = document.createElement('div');
396
- ribbon.className = 'promo-ribbon';
397
- ribbon.textContent = '50% OFF';
398
- card.appendChild(ribbon);
399
- });
400
-
401
- // Update pricing cards with promo prices + save badge
402
- const proCard = document.querySelector('.pricing-pro .pricing-price');
403
- const bizCard = document.querySelector('.pricing-biz .pricing-price');
404
- if (proCard) proCard.innerHTML = '<s style="opacity:.35;font-size:.55em">$49</s> $25<span class="pricing-period"> lifetime</span><br><span class="promo-save">Save $24</span>';
405
- if (bizCard) bizCard.innerHTML = '<s style="opacity:.35;font-size:.55em">$149</s> $75<span class="pricing-period"> lifetime</span><br><span class="promo-save">Save $74</span>';
406
-
407
- // Update "What's Included" values in Business card
408
- document.querySelectorAll('.includes-value').forEach(el => {
409
- if (el.textContent.includes('$49')) el.innerHTML = '<s>$49</s> $25 value';
410
- if (el.textContent.includes('$100')) el.innerHTML = '<s>$100</s> $50 value';
411
- });
412
-
413
- // Update buttons
414
- const proBtn = document.querySelector('.pricing-pro .btn-pro');
415
- const bizBtn = document.querySelector('.pricing-biz .btn-biz');
416
- if (proBtn) proBtn.innerHTML = 'Get Rune Pro &mdash; <s style="opacity:.6">$49</s>&nbsp;$25';
417
- if (bizBtn) bizBtn.innerHTML = 'Get Rune Business &mdash; <s style="opacity:.6">$149</s>&nbsp;$75';
418
-
419
- // Update comparison table
420
- document.querySelectorAll('.table-price').forEach(el => {
421
- if (el.textContent.includes('$49')) el.innerHTML = '<s style="opacity:.5">$49</s>&nbsp;$25';
422
- if (el.textContent.includes('$149')) el.innerHTML = '<s style="opacity:.5">$149</s>&nbsp;$75';
423
- });
424
-
425
- // Create countdown banner (sticky bottom)
426
- const banner = document.createElement('div');
427
- banner.id = 'promo-banner';
428
- banner.style.cssText = `
429
- position:fixed;bottom:0;left:0;right:0;z-index:9999;
430
- background:linear-gradient(135deg,#4a6843 0%,#5b7852 50%,#7a9e6e 100%);
431
- color:#fff;text-align:center;padding:10px 16px;
432
- font:600 14px/1.4 "Space Grotesk",sans-serif;
433
- display:flex;align-items:center;justify-content:center;gap:16px;
434
- box-shadow:0 -4px 20px rgba(91,120,82,.2);
435
- `.replace(/\n\s+/g, '');
436
- banner.innerHTML = ''
437
- + '<span style="font-size:18px">&#9889;</span>'
438
- + '<span><strong>Launch Week</strong> &mdash; 50% OFF all paid packs</span>'
439
- + '<span id="promo-timer" style="font-family:JetBrains Mono,monospace;font-weight:700;background:rgba(0,0,0,.2);padding:5px 12px;border-radius:6px;letter-spacing:.5px;min-width:150px"></span>'
440
- + '<a href="#pricing" style="background:#f5f0e8;color:#4a6843;text-decoration:none;font-weight:700;padding:6px 16px;border-radius:6px;font-size:13px;white-space:nowrap">Grab the deal &rarr;</a>';
441
- document.body.appendChild(banner);
442
- document.body.style.paddingBottom = '52px';
443
-
444
- function tick() {
445
- const diff = PROMO.endsAt - Date.now();
446
- if (diff <= 0) { banner.remove(); document.body.style.paddingBottom = ''; return; }
447
- const d = Math.floor(diff / 86400000);
448
- const h = Math.floor((diff % 86400000) / 3600000);
449
- const m = Math.floor((diff % 3600000) / 60000);
450
- const s = Math.floor((diff % 60000) / 1000);
451
- const timer = document.getElementById('promo-timer');
452
- if (timer) timer.textContent = d + 'd ' + h + 'h ' + m + 'm ' + s + 's';
453
- }
454
- tick();
455
- setInterval(tick, 1000);
456
- })();
457
-
458
- // ─── Polar Success Return ───
459
- (function checkPolarReturn() {
460
- const params = new URLSearchParams(window.location.search);
461
- if (params.get('polar') === 'success') {
462
- // Read context from URL (embedded by worker in success_url)
463
- const github = params.get('gh') || 'your GitHub account';
464
- const product = params.get('pkg') || '';
465
-
466
- // Clean URL
467
- window.history.replaceState({}, '', window.location.pathname);
468
-
469
- // Show success message
470
- const modal = document.getElementById('pay-modal');
471
- if (modal) {
472
- modal.hidden = false;
473
- document.body.style.overflow = 'hidden';
474
- showSuccessForProduct(product, github);
475
- for (let i = 1; i <= 5; i++) {
476
- document.getElementById('pay-step-' + i).hidden = (i !== 4);
477
- }
478
- }
479
- }
480
- })();
481
-
482
- // Update success page based on product context
483
- function showSuccessForProduct(product, github) {
484
- const el = document.getElementById('pay-success-user');
485
- if (el) el.textContent = github || 'your GitHub account';
486
-
487
- const installEl = document.getElementById('pay-success-install');
488
- if (!installEl) return;
489
-
490
- if (product === 'rune-biz' || product === 'RUNE-BIZ') {
491
- installEl.textContent = 'You get Pro + Business packs — check the repo README for setup';
492
- } else {
493
- installEl.textContent = 'Follow the setup guide in the repo README';
494
- }
495
- }