@rune-kit/rune 2.2.4 → 2.2.6

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/docs/script.js CHANGED
@@ -43,9 +43,9 @@
43
43
  const dy = nodes[i].y - nodes[j].y;
44
44
  const dist = Math.sqrt(dx * dx + dy * dy);
45
45
  if (dist < maxDist) {
46
- const alpha = (1 - dist / maxDist) * 0.35;
47
- ctx.strokeStyle = `rgba(16, 185, 129, ${alpha})`;
48
- ctx.lineWidth = 0.6;
46
+ const alpha = (1 - dist / maxDist) * 0.5;
47
+ ctx.strokeStyle = `rgba(60, 85, 50, ${alpha})`;
48
+ ctx.lineWidth = 0.8;
49
49
  ctx.beginPath();
50
50
  ctx.moveTo(nodes[i].x, nodes[i].y);
51
51
  ctx.lineTo(nodes[j].x, nodes[j].y);
@@ -56,7 +56,7 @@
56
56
 
57
57
  // draw nodes
58
58
  for (const n of nodes) {
59
- ctx.fillStyle = 'rgba(16, 185, 129, 0.6)';
59
+ ctx.fillStyle = 'rgba(60, 85, 50, 0.7)';
60
60
  ctx.beginPath();
61
61
  ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
62
62
  ctx.fill();
@@ -98,7 +98,7 @@
98
98
  });
99
99
  }, { threshold: 0.1 });
100
100
 
101
- document.querySelectorAll('.layer, .feature-card, .workflow-card, .compare-card, .ext-card, .install-card, .pricing-card, .stat').forEach(el => {
101
+ document.querySelectorAll('.layer, .feature-card, .workflow-card, .compare-card, .ext-card, .install-card, .pricing-card, .stat, .mesh-tier, .mesh-point').forEach(el => {
102
102
  el.style.animationPlayState = 'paused';
103
103
  observer.observe(el);
104
104
  });
@@ -134,29 +134,57 @@
134
134
 
135
135
  // ─── Payment Flow ───
136
136
  const PAY_API = 'https://pay.theio.vn';
137
- const BANK_ID = 'vpbank';
137
+ const BANK_ID = 'tpbank';
138
138
  const BANK_ACCOUNT = '04162263666';
139
- const ACCOUNT_NAME = 'NGUYEN CONG';
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
+ }
140
154
 
141
155
  const PRODUCT_INFO = {
142
156
  'rune-pro': {
143
157
  title: 'Get Rune Pro', titleShort: 'Rune Pro',
144
- priceVN: '1,190,000 VND', priceIntl: '$49 USD',
145
- amountLabel: '1,190,000 VND (~$49 USD)',
158
+ basePrice: 1190000, baseIntl: 49,
146
159
  btnClass: 'btn-pro',
147
160
  },
148
161
  'rune-biz': {
149
162
  title: 'Get Rune Business', titleShort: 'Rune Business',
150
- priceVN: '3,590,000 VND', priceIntl: '$149 USD',
151
- amountLabel: '3,590,000 VND (~$149 USD)',
163
+ basePrice: 3590000, baseIntl: 149,
152
164
  btnClass: 'btn-biz',
153
165
  },
154
166
  };
155
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
+
156
184
  let payState = { product: null, orderCode: null, pollTimer: null };
157
185
 
158
186
  function openPayment(product) {
159
- const info = PRODUCT_INFO[product];
187
+ const info = getProductPricing(product);
160
188
  if (!info) return;
161
189
 
162
190
  payState = { product, orderCode: null, pollTimer: null };
@@ -164,8 +192,17 @@ function openPayment(product) {
164
192
  document.getElementById('pay-title').textContent = info.title;
165
193
  document.getElementById('pay-title-2').textContent = info.title;
166
194
  document.getElementById('pay-amount-2').textContent = info.amountLabel;
167
- document.getElementById('pay-price-vn').textContent = info.priceVN;
168
- document.getElementById('pay-price-intl').textContent = info.priceIntl;
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
+
169
206
  document.getElementById('pay-github').value = '';
170
207
  document.getElementById('pay-email').value = '';
171
208
  document.getElementById('pay-error').hidden = true;
@@ -190,7 +227,7 @@ function closePayment() {
190
227
  }
191
228
 
192
229
  function showPayStep(n) {
193
- for (let i = 1; i <= 4; i++) {
230
+ for (let i = 1; i <= 5; i++) {
194
231
  document.getElementById('pay-step-' + i).hidden = (i !== n);
195
232
  }
196
233
  }
@@ -200,6 +237,70 @@ function showVNPayment() {
200
237
  document.getElementById('pay-github').focus();
201
238
  }
202
239
 
240
+ function showIntlPayment() {
241
+ const info = getProductPricing(payState.product);
242
+ document.getElementById('pay-title-5').textContent = info.title;
243
+ document.getElementById('pay-amount-5').textContent = info.priceIntl;
244
+ document.getElementById('pay-intl-github').value = '';
245
+ document.getElementById('pay-intl-email').value = '';
246
+ document.getElementById('pay-intl-error').hidden = true;
247
+
248
+ const btn = document.getElementById('pay-intl-submit');
249
+ btn.className = 'btn ' + info.btnClass;
250
+ btn.style.width = '100%';
251
+ btn.style.marginTop = '16px';
252
+
253
+ showPayStep(5);
254
+ document.getElementById('pay-intl-github').focus();
255
+ }
256
+
257
+ async function startPolarCheckout() {
258
+ const github = document.getElementById('pay-intl-github').value.trim();
259
+ const email = document.getElementById('pay-intl-email').value.trim();
260
+ const errorEl = document.getElementById('pay-intl-error');
261
+
262
+ if (!github) {
263
+ errorEl.textContent = 'Please enter your GitHub username';
264
+ errorEl.hidden = false;
265
+ return;
266
+ }
267
+
268
+ if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/.test(github)) {
269
+ errorEl.textContent = 'Invalid GitHub username format';
270
+ errorEl.hidden = false;
271
+ return;
272
+ }
273
+
274
+ errorEl.hidden = true;
275
+ const btn = document.getElementById('pay-intl-submit');
276
+ btn.disabled = true;
277
+ btn.textContent = 'Redirecting to checkout...';
278
+
279
+ try {
280
+ const res = await fetch(PAY_API + '/checkout/polar', {
281
+ method: 'POST',
282
+ headers: { 'Content-Type': 'application/json' },
283
+ body: JSON.stringify({
284
+ product: payState.product,
285
+ githubUsername: github,
286
+ email: email || undefined,
287
+ }),
288
+ });
289
+
290
+ const data = await res.json();
291
+ if (!data.success || !data.url) throw new Error(data.error || 'Failed to create checkout');
292
+
293
+ // Redirect to Polar checkout page
294
+ window.location.href = data.url;
295
+ } catch (err) {
296
+ errorEl.textContent = err.message;
297
+ errorEl.hidden = false;
298
+ } finally {
299
+ btn.disabled = false;
300
+ btn.textContent = 'Pay with Card / PayPal';
301
+ }
302
+ }
303
+
203
304
  async function createOrder() {
204
305
  const github = document.getElementById('pay-github').value.trim();
205
306
  const email = document.getElementById('pay-email').value.trim();
@@ -275,3 +376,93 @@ function startPolling(github) {
275
376
  }
276
377
  }, 5000);
277
378
  }
379
+
380
+ // ─── Promo Countdown ───
381
+ (function initPromoCountdown() {
382
+ if (!isPromoActive()) return;
383
+
384
+ // Add "50% OFF" ribbon to Pro and Business cards
385
+ ['.pricing-pro', '.pricing-biz'].forEach(sel => {
386
+ const card = document.querySelector(sel);
387
+ if (!card) return;
388
+ const ribbon = document.createElement('div');
389
+ ribbon.className = 'promo-ribbon';
390
+ ribbon.textContent = '50% OFF';
391
+ card.appendChild(ribbon);
392
+ });
393
+
394
+ // Update pricing cards with promo prices + save badge
395
+ const proCard = document.querySelector('.pricing-pro .pricing-price');
396
+ const bizCard = document.querySelector('.pricing-biz .pricing-price');
397
+ 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>';
398
+ 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>';
399
+
400
+ // Update "What's Included" values in Business card
401
+ document.querySelectorAll('.includes-value').forEach(el => {
402
+ if (el.textContent.includes('$49')) el.innerHTML = '<s>$49</s> $25 value';
403
+ if (el.textContent.includes('$100')) el.innerHTML = '<s>$100</s> $50 value';
404
+ });
405
+
406
+ // Update buttons
407
+ const proBtn = document.querySelector('.pricing-pro .btn-pro');
408
+ const bizBtn = document.querySelector('.pricing-biz .btn-biz');
409
+ if (proBtn) proBtn.innerHTML = 'Get Rune Pro &mdash; <s style="opacity:.6">$49</s>&nbsp;$25';
410
+ if (bizBtn) bizBtn.innerHTML = 'Get Rune Business &mdash; <s style="opacity:.6">$149</s>&nbsp;$75';
411
+
412
+ // Update comparison table
413
+ document.querySelectorAll('.table-price').forEach(el => {
414
+ if (el.textContent.includes('$49')) el.innerHTML = '<s style="opacity:.5">$49</s>&nbsp;$25';
415
+ if (el.textContent.includes('$149')) el.innerHTML = '<s style="opacity:.5">$149</s>&nbsp;$75';
416
+ });
417
+
418
+ // Create countdown banner (sticky bottom)
419
+ const banner = document.createElement('div');
420
+ banner.id = 'promo-banner';
421
+ banner.style.cssText = `
422
+ position:fixed;bottom:0;left:0;right:0;z-index:9999;
423
+ background:linear-gradient(135deg,#4a6843 0%,#5b7852 50%,#7a9e6e 100%);
424
+ color:#fff;text-align:center;padding:10px 16px;
425
+ font:600 14px/1.4 "Space Grotesk",sans-serif;
426
+ display:flex;align-items:center;justify-content:center;gap:16px;
427
+ box-shadow:0 -4px 20px rgba(91,120,82,.2);
428
+ `.replace(/\n\s+/g, '');
429
+ banner.innerHTML = ''
430
+ + '<span style="font-size:18px">&#9889;</span>'
431
+ + '<span><strong>Launch Week</strong> &mdash; 50% OFF all paid packs</span>'
432
+ + '<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>'
433
+ + '<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>';
434
+ document.body.appendChild(banner);
435
+ document.body.style.paddingBottom = '52px';
436
+
437
+ function tick() {
438
+ const diff = PROMO.endsAt - Date.now();
439
+ if (diff <= 0) { banner.remove(); document.body.style.paddingBottom = ''; return; }
440
+ const d = Math.floor(diff / 86400000);
441
+ const h = Math.floor((diff % 86400000) / 3600000);
442
+ const m = Math.floor((diff % 3600000) / 60000);
443
+ const s = Math.floor((diff % 60000) / 1000);
444
+ const timer = document.getElementById('promo-timer');
445
+ if (timer) timer.textContent = d + 'd ' + h + 'h ' + m + 'm ' + s + 's';
446
+ }
447
+ tick();
448
+ setInterval(tick, 1000);
449
+ })();
450
+
451
+ // ─── Polar Success Return ───
452
+ (function checkPolarReturn() {
453
+ const params = new URLSearchParams(window.location.search);
454
+ if (params.get('polar') === 'success') {
455
+ // Clean URL
456
+ window.history.replaceState({}, '', window.location.pathname);
457
+ // Show success message
458
+ const modal = document.getElementById('pay-modal');
459
+ if (modal) {
460
+ modal.hidden = false;
461
+ document.body.style.overflow = 'hidden';
462
+ document.getElementById('pay-success-user').textContent = 'you';
463
+ for (let i = 1; i <= 5; i++) {
464
+ document.getElementById('pay-step-' + i).hidden = (i !== 4);
465
+ }
466
+ }
467
+ }
468
+ })();