Spaces:
Running
Running
File size: 8,349 Bytes
9759528 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | // CyberMax game Skin Pack (Stoopshake: the Dice & Table Pack): a one-time cosmetic unlock for ONE game (separate from the Fun Pass, which covers every
// game's archive + themes). Buy: counted redirect data.cybermaxtools.com/buy/st-<game>-skins-pack?s=game-<game>
// -> Stripe Payment Link; or the Gumroad listing (licence key). After checkout data.cybermaxtools.com/unlock/<game>-skins
// checks the order (or the Gumroad licence key) and returns to the game with ?unlock=skins#cmx-pro=<code>.
// capture() must run BEFORE funpass.init() so the Fun Pass never mistakes a skin-pack code for a pass code.
// Cosmetic only: skins never change the game, scores or the daily course. (c) 2026 CyberMax.
import { count, store, toast } from './cmx.js';
const DATA = 'https://data.cybermaxtools.com';
const CODE = /^(cmxk1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|cs_(live|test)_[A-Za-z0-9]{10,200}|[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8})$/i;
let cfg = { game: '', name: '', price: '$1.99', gumroad: '', skins: 0 };
let state = null;
const subs = [];
const key = () => `cmx-skins:${cfg.game}`;
export const owned = () => !!(state && state.code);
export function onChange(fn) { subs.push(fn); }
const emit = () => subs.forEach((f) => { try { f(owned()); } catch { /* game hook */ } });
// Offline Pro keys (17 Oct 2026): "cmxk1.<payload>.<Ed25519 signature>" from data.cybermaxtools.com/unlock is verified here
// with WebCrypto and this public key, so an unlock needs no Worker call. Expired, foreign-signed or unverifiable keys fall
// back to the online check of the order code inside them, as before.
const CMX_PUB = 'u0FDxbu0d9B9Sz5-yLT6AuL5IagyTlFcbBgEDqZxWtk';
const b64d = (s) => { s = s.replace(/-/g, '+').replace(/_/g, '/'); while (s.length % 4) s += '='; return Uint8Array.from(atob(s), (c) => c.charCodeAt(0)); };
async function cmxKeyOk(k, app, online) {
const [tag, body, sig] = k.split('.');
let p;
try { p = JSON.parse(new TextDecoder().decode(b64d(body))); } catch { return false; }
if (!p || p.a !== app) return false;
try {
const key = await crypto.subtle.importKey('raw', b64d(CMX_PUB), { name: 'Ed25519' }, false, ['verify']);
if (await crypto.subtle.verify({ name: 'Ed25519' }, key, b64d(sig), new TextEncoder().encode(tag + '.' + body)) && (!p.x || p.x * 1000 > Date.now())) return true;
} catch { /* no Ed25519 here: online check below */ }
return p.c ? online(p.c) : false;
}
async function check(code) {
if (/^cmxk1\./.test(code)) return cmxKeyOk(code, `${cfg.game}-skins`, checkOnline);
return checkOnline(code);
}
async function checkOnline(code) {
try {
const r = await fetch(`${DATA}/unlock/${cfg.game}-skins?json=1&session_id=${encodeURIComponent(code)}`);
const j = await r.json();
return j && typeof j.pro === 'boolean' ? j.pro : null;
} catch { return null; }
}
export async function unlock(code) {
const ok = await check(code);
if (ok) {
state = store(key(), { code, at: new Date().toISOString() });
count(cfg.game, 'pro', 'skins-unlocked');
toast(`${cfg.name} Dice & Table Pack is on. New dice, tables and win emotes are yours!`, 3500);
emit(); render();
} else if (ok === false) toast('That code is not a Dice & Table Pack order for this game.', 3500);
else toast('Could not reach the unlock check. Try again in a minute.', 3500);
return ok;
}
/** Call first: game id, display name, skin count, Gumroad URL. Reads ?unlock=skins#cmx-pro=<code> after checkout. */
export function capture(options) {
cfg = { ...cfg, ...options };
state = store(key());
const q = new URLSearchParams(location.search);
const m = /[#&]cmx-key=([^&]+)/.exec(location.hash || '') || /[#&]cmx-pro=([^&]+)/.exec(location.hash || '');
if (q.get('unlock') === 'skins' && m) {
const code = decodeURIComponent(m[1]);
q.delete('unlock');
history.replaceState(null, '', location.pathname + (q.toString() ? `?${q}` : ''));
if (CODE.test(code)) unlock(code);
}
}
const buyUrl = (from) => `${DATA}/buy/st-${cfg.game}-skins-pack?s=game-${cfg.game}${from ? `-${from}` : ''}`;
function dialog() {
let d = document.getElementById('skinpack');
if (!d) {
d = document.createElement('dialog'); d.id = 'skinpack'; d.className = 'funpass';
document.body.appendChild(d);
d.addEventListener('click', (e) => { if (e.target === d || e.target.closest('[data-close]')) d.close(); });
}
return d;
}
function render() {
const d = dialog();
const prev = (cfg.preview || []).map((s) => `<span class="sp-dot" style="background:${s}"></span>`).join('');
if (owned()) {
d.innerHTML = `<div class="fp"><button class="fp-x" data-close aria-label="Close">×</button><p class="fp-kick">${cfg.name} Dice & Table Pack</p><h2>Your Dice & Table Pack is on. Thank you!</h2>
<p>Pick dice, tables and emotes under STYLE on the home screen.</p><p class="fp-small">Your code (paste it on another device):</p><code class="fp-code">${state.code}</code></div>`;
return;
}
d.innerHTML = `<div class="fp"><button class="fp-x" data-close aria-label="Close">×</button><p class="fp-kick">${cfg.name} Dice & Table Pack</p>
<h2>${cfg.title || 'Dice & Table Pack'}, ${cfg.price} once</h2><p class="sp-row">${prev}</p>
<ul class="fp-perks">${(cfg.perks || []).map((p) => `<li>${p}</li>`).join('')}<li>Cosmetic only: same dice odds and chips for everyone</li><li>Pay once, keep forever, on any device with your code</li></ul>
<p class="fp-row"><a class="btn pass" href="${buyUrl('')}" rel="nofollow" data-buy>Get the Dice & Table Pack: ${cfg.price}</a>${cfg.gumroad ? ` <a class="btn ghost sm" href="${cfg.gumroad}" rel="nofollow noopener" target="_blank" data-gum>or on Gumroad</a>` : ''}</p>
<p class="fp-small">Secure Stripe checkout · the skins turn on as soon as you pay · 14-day promise: if it doesn't work as described, we fix it or refund you (<a href="https://cybermaxtools.com/terms#payments" target="_blank" rel="noopener">terms</a>). The <b>Fun Pass</b> is a separate pass for every game's archive and themes.</p>
<details><summary>Already bought it?</summary><form class="fp-form"><input aria-label="Order code" autocomplete="off" placeholder="Receipt code (cs_live_…) or Gumroad licence key"><button class="btn sm" type="submit">Turn on</button></form></details></div>`;
d.querySelector('.fp-form').addEventListener('submit', (e) => {
e.preventDefault();
const c = e.target.querySelector('input').value.trim();
if (CODE.test(c)) unlock(c); else toast('Paste the code from your receipt page (cs_live_…) or your Gumroad licence key.', 3500);
});
d.querySelector('[data-buy]').addEventListener('click', () => count(cfg.game, 'cta', 'skins-buy'));
d.querySelector('[data-gum]')?.addEventListener('click', () => count(cfg.game, 'cta', 'skins-gumroad'));
}
export function open(from = 'button') {
render();
const d = dialog();
if (d.showModal) d.showModal(); else d.setAttribute('open', '');
count(cfg.game, 'cta', `skins-open-${from}`);
}
// Direct checkout (17 Oct 2026, Workers cap fix): a click on a data.cybermaxtools.com/buy/<offer> link goes straight to the
// Stripe/Polar checkout (static map https://cybermaxtools.com/store/buy-direct.json); the click is counted by a beacon.
(() => {
if (typeof document === 'undefined' || window.__cmxBd) return;
window.__cmxBd = 1;
let MAP = null;
try { MAP = JSON.parse(sessionStorage.getItem('cmx-bd') || 'null'); } catch { /* none */ }
if (!MAP) fetch('https://cybermaxtools.com/store/buy-direct.json').then((r) => r.json()).then((j) => { MAP = j; try { sessionStorage.setItem('cmx-bd', JSON.stringify(j)); } catch { /* full */ } }).catch(() => {});
document.addEventListener('click', (e) => {
const a = e.target && e.target.closest && e.target.closest('a[href*="data.cybermaxtools.com/buy/"]');
if (!a || !MAP || !MAP.offers) return;
try {
const u = new URL(a.href);
const k = /^\/buy\/([a-z0-9-]+)\/?$/.exec(u.pathname);
const t = k && MAP.offers[k[1]];
if (!t) return;
const s = String(u.searchParams.get('s') || 'unknown').toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 80) || 'unknown';
a.href = t.split(MAP.ph).join(s);
if (!navigator.webdriver) navigator.sendBeacon(u.href + (u.search ? '&' : '?') + 'b=1');
} catch { /* keep the counted link */ }
}, true);
})();
|