// CyberMax Fun Pass: ONE optional supporter pass for every CyberMax game (copied into each game's web/ folder). // Free games stay free. The pass removes the supporter nudges and unlocks extra packs, the full archive and themes. // Monthly $2.99 · Yearly $19 · Unlock all $4.99 once (Stripe Payment Links, pack revenue/packs/stripe/fun-pass). // Buy: counted redirect https://data.cybermax-tools.workers.dev/buy/st-fun-pass-?s=game- -> Stripe checkout. // After checkout Stripe sends the buyer to data.cybermax-tools.workers.dev/unlock/fun-pass?session_id=..., which checks // the Checkout Session (and, for a subscription, that it is still active) and returns to /games/ with #cmx-pro=. // Every game on the same site reads the same browser key, so one pass covers them all. The code on the receipt page // unlocks any other device or browser. No account, no cookies, nothing personal stored. (c) 2026 CyberMax. import { count, store, toast } from './cmx.js'; const DATA = 'https://data.cybermax-tools.workers.dev'; const KEY = 'cmx-pro:fun-pass'; const SID = /^cs_(live|test)_[A-Za-z0-9]{10,200}$/; const RECHECK = 3 * 864e5; // subscriptions are re-checked every 3 days; a cancelled pass turns off by itself export const PLANS = [ { id: 'monthly', label: 'Monthly', price: '$2.99', per: '/month', note: 'Cancel any time' }, { id: 'yearly', label: 'Yearly', price: '$19', per: '/year', note: 'Save 47%', best: true }, { id: 'unlock', label: 'Unlock all', price: '$4.99', per: ' once', note: 'Pay once, keep forever' }, ]; export const COMMON_PERKS = ['No supporter nudges, ever', 'Extra puzzle packs in every game', 'The full archive of past dailies', 'Custom colour themes']; let state = store(KEY); const listeners = []; let cfg = { game: 'games', name: 'CyberMax games', perks: [] }; export const isPro = () => !!(state && state.code); export function onChange(fn) { listeners.push(fn); fn(isPro()); } function emit() { document.documentElement.classList.toggle('cmx-pro', isPro()); listeners.forEach((fn) => { try { fn(isPro()); } catch { /* game hook */ } }); } async function check(code) { try { const r = await fetch(`${DATA}/unlock/fun-pass?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(), checked: Date.now() }); count(cfg.game, 'pro', 'unlocked'); toast('Fun Pass is on. Thank you for supporting CyberMax games!', 3500); emit(); render(); } else if (ok === false) toast('That code is not an active Fun Pass.', 3500); else toast('Could not reach the pass check. Try again in a minute.', 3500); return ok; } async function recheck() { if (!isPro() || Date.now() - (state.checked || 0) < RECHECK) return; const ok = await check(state.code); if (ok === false) { state = null; try { localStorage.removeItem(KEY); } catch { /* */ } emit(); render(); toast('Your Fun Pass has ended. Everything free is still here.', 4000); } else if (ok) state = store(KEY, { ...state, checked: Date.now() }); } const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); const buyUrl = (plan) => `${DATA}/buy/st-fun-pass-${plan}?s=game-${cfg.game}`; function dialog() { let d = document.getElementById('funpass'); if (!d) { d = document.createElement('dialog'); d.id = 'funpass'; 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 perks = [...(cfg.perks || []), ...COMMON_PERKS.filter((p) => !(cfg.perks || []).some((q) => q.split(' ')[0] === p.split(' ')[0]))].slice(0, 6); if (isPro()) { d.innerHTML = `

CyberMax Fun Pass

You're a supporter. Thank you!

The pass is on in this browser for every CyberMax game: ${perks.map(esc).join(' · ')}.

Your code (paste it on another device to turn the pass on there):

${esc(state.code)}

Manage or cancel a subscription any time from the link in your Stripe receipt email.

`; return; } d.innerHTML = `

CyberMax Fun Pass

One pass. Every game. More to play.

    ${perks.map((p) => `
  • ${esc(p)}
  • `).join('')}

Secure Stripe checkout. Works in every CyberMax game. The free daily games stay free for everyone.

Already have a pass?
`; d.querySelector('.fp-form').addEventListener('submit', (e) => { e.preventDefault(); const c = e.target.querySelector('input').value.trim(); if (SID.test(c)) unlock(c); else toast('Paste the code from your receipt page. It starts with cs_live_.', 3500); }); d.querySelectorAll('[data-plan]').forEach((a) => a.addEventListener('click', () => count(cfg.game, 'cta', `pass-${a.dataset.plan}`))); } /** Open the pass sheet; `from` names the button that opened it (counted as cta). */ export function open(from = 'button') { render(); const d = dialog(); if (d.showModal) d.showModal(); else d.setAttribute('open', ''); count(cfg.game, 'cta', `pass-open-${from}`); } /** Call once per page: game id, display name and the game's own Pro perks (listed first). */ export function init(options) { cfg = { ...cfg, ...options }; const m = /[#&]cmx-pro=([^&]+)/.exec(location.hash || ''); if (m && SID.test(decodeURIComponent(m[1]))) { history.replaceState(null, '', location.pathname + location.search); unlock(decodeURIComponent(m[1])); } else recheck(); emit(); }