brimbloom / skins.js
CyberMax-tools's picture
Deploy brimbloom
41a71f1 verified
Raw History Blame Contribute Delete
5.5 kB
// CyberMax game Skin 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 = /^(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 */ } });
async function check(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} Skin Pack is on. Enjoy the new looks!`, 3500);
emit(); render();
} else if (ok === false) toast('That code is not a Skin 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-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} Skin Pack</p><h2>All ${cfg.skins} skins are yours. Thank you!</h2>
<p>Pick one from the colour dots under the game.</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} Skin Pack</p>
<h2>${cfg.skins} extra looks, ${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 course, same scores 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 Skin 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}`);
}