@rune-kit/rune 2.3.3 → 2.4.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/README.md +51 -1
- package/compiler/__tests__/scripts-bundling.test.js +284 -0
- package/compiler/__tests__/tier-override.test.js +41 -0
- package/compiler/adapters/antigravity.js +4 -0
- package/compiler/adapters/codex.js +4 -0
- package/compiler/adapters/cursor.js +4 -0
- package/compiler/adapters/generic.js +4 -0
- package/compiler/adapters/openclaw.js +4 -0
- package/compiler/adapters/opencode.js +4 -0
- package/compiler/adapters/windsurf.js +4 -0
- package/compiler/emitter.js +85 -5
- package/compiler/transforms/scripts-path.js +18 -0
- package/extensions/zalo/PACK.md +20 -1
- package/extensions/zalo/references/conversation-management.md +214 -0
- package/extensions/zalo/references/eval-scenarios.md +157 -0
- package/extensions/zalo/references/listen-mode.md +237 -0
- package/extensions/zalo/references/mcp-production.md +274 -0
- package/extensions/zalo/references/multi-account-proxy.md +224 -0
- package/extensions/zalo/references/vietqr-banking.md +160 -0
- package/package.json +2 -3
- package/skills/marketing/SKILL.md +3 -0
- package/skills/sentinel/SKILL.md +4 -1
- package/skills/sentinel/references/auth-crypto-reference.md +192 -0
- package/skills/sentinel/references/desktop-security.md +201 -0
- package/skills/sentinel/references/supply-chain.md +160 -0
- package/skills/slides/SKILL.md +142 -0
- package/skills/slides/scripts/build-deck.js +158 -0
- package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
- package/docs/ARCHITECTURE.md +0 -332
- package/docs/COMMUNITY-PACKS.md +0 -109
- package/docs/CONTRIBUTING-L4.md +0 -215
- package/docs/CROSS-IDE-ANALYSIS.md +0 -164
- package/docs/EXTENSION-TEMPLATE.md +0 -126
- package/docs/MESH-RULES.md +0 -34
- package/docs/MULTI-PLATFORM.md +0 -804
- package/docs/SKILL-DEPTH-AUDIT.md +0 -191
- package/docs/SKILL-TEMPLATE.md +0 -118
- package/docs/TRADE-MATRIX.md +0 -327
- package/docs/VERSIONING.md +0 -91
- package/docs/VISION.md +0 -263
- package/docs/assets/demo-subtitles.srt +0 -215
- package/docs/assets/end-card.html +0 -276
- package/docs/assets/mesh-diagram.html +0 -654
- package/docs/assets/thumbnail.html +0 -295
- package/docs/guides/cli.md +0 -403
- package/docs/guides/index.html +0 -1450
- package/docs/index.html +0 -1005
- package/docs/references/claudekit-analysis.md +0 -414
- package/docs/references/voltagent-analysis.md +0 -189
- package/docs/script.js +0 -495
- package/docs/skills/index.html +0 -832
- package/docs/style.css +0 -958
- 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)">⚠ 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 — <s style="opacity:.6">$49</s> $25';
|
|
417
|
-
if (bizBtn) bizBtn.innerHTML = 'Get Rune Business — <s style="opacity:.6">$149</s> $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> $25';
|
|
422
|
-
if (el.textContent.includes('$149')) el.innerHTML = '<s style="opacity:.5">$149</s> $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">⚡</span>'
|
|
438
|
-
+ '<span><strong>Launch Week</strong> — 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 →</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
|
-
}
|