Spaces:
Running
Running
Download funpass.js from CyberMax-tools/pathmoor: direct link, hf CLI and curl.
- Browser
- Download file 9.17 kB
-
https://huggingface.co/spaces/CyberMax-tools/pathmoor/resolve/main/funpass.js
- Command line
-
hf download hf://spaces/CyberMax-tools/pathmoor/funpass.js
-
curl -L -o funpass.js https://huggingface.co/spaces/CyberMax-tools/pathmoor/resolve/main/funpass.js
9.17 kB
| // 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 · Lifetime $29.99 once (Stripe Payment Links, pack revenue/packs/stripe/fun-pass). | |
| // Buy: counted redirect https://data.cybermaxtools.com/buy/st-fun-pass-<plan>?s=game-<id> -> Stripe checkout. | |
| // After checkout Stripe sends the buyer to data.cybermaxtools.com/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=<code>. | |
| // 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.cybermaxtools.com'; | |
| const KEY = 'cmx-pro:fun-pass'; | |
| const SID = /^(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; // Stripe order code or Gumroad licence key | |
| 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: 'Lifetime', price: '$29.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 = `<div class="fp"><button class="fp-x" data-close aria-label="Close">×</button><p class="fp-kick">CyberMax Fun Pass</p><h2>You're a supporter. Thank you!</h2> | |
| <p>The pass is on in this browser for every CyberMax game: ${perks.map(esc).join(' · ')}.</p> | |
| <p class="fp-small">Your code (paste it on another device to turn the pass on there):</p><code class="fp-code">${esc(state.code)}</code> | |
| <p class="fp-small">Manage or cancel a subscription any time from the link in your Stripe receipt email.</p></div>`; | |
| return; | |
| } | |
| d.innerHTML = `<div class="fp"><button class="fp-x" data-close aria-label="Close">×</button><p class="fp-kick">CyberMax Fun Pass</p> | |
| <h2>One pass. Every game. More to play.</h2> | |
| <ul class="fp-perks">${perks.map((p) => `<li>${esc(p)}</li>`).join('')}</ul> | |
| <div class="fp-plans">${PLANS.map((p) => `<a class="fp-plan${p.best ? ' best' : ''}" href="${buyUrl(p.id)}" rel="nofollow" data-plan="${p.id}">${p.best ? '<i>Best value</i>' : ''}<b>${p.label}</b><span class="fp-price">${p.price}<small>${p.per}</small></span><span class="fp-note">${p.note}</span></a>`).join('')}</div> | |
| <p class="fp-small">Secure Stripe checkout. Works in every CyberMax game. The free daily games stay free for everyone.</p> | |
| <details><summary>Already have a pass?</summary><form class="fp-form"><input aria-label="Pass 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 (SID.test(c)) unlock(c); else toast('Paste the code from your receipt page (cs_live_…) or your Gumroad licence key.', 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}`); | |
| } | |
| /** After a finished round (the value moment): one small inline card under the result, never a pop-up. Shows the | |
| * pass price, the honest trust line and an optional "email me" weekly reminder (double opt-in). | |
| * `host` is the result panel; the card is added once and removed for pass holders. */ | |
| export function afterWin(host, from = 'win') { | |
| if (!host) return; | |
| if (isPro()) { host.querySelector('.fp-after')?.remove(); return; } | |
| let el = host.querySelector('.fp-after'); | |
| if (!el) { el = document.createElement('div'); el.className = 'fp-after'; host.appendChild(el); } | |
| const best = PLANS.find((p) => p.best) || PLANS[0]; | |
| el.innerHTML = `<p><b>Want more today?</b> ${esc(cfg.extra || 'The Fun Pass unlocks extra packs, the full archive and themes in every CyberMax game.')}</p> | |
| <p class="fp-row"><a class="btn sm" href="${buyUrl(best.id)}-after" rel="nofollow" data-after>Get the Fun Pass: ${esc(best.price)}${esc(best.per)}</a> <button class="btn sm ghost" type="button" data-plans>See all plans</button></p> | |
| <p class="fp-small">Secure Stripe checkout · the pass turns on as soon as you pay · cancel any time from your receipt email · 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>). Today's free game stays free.</p> | |
| <div class="fp-cap"></div>`; | |
| el.querySelector('[data-after]').addEventListener('click', () => { count(cfg.game, 'cta', `pass-${best.id}-after`); kit((K) => K.event('upgrade-click', cfg.game)); }); | |
| el.querySelector('[data-plans]').addEventListener('click', () => open(`after-${from}`)); | |
| kit((K) => { | |
| K.event('result', cfg.game); K.event('upgrade-view', cfg.game); | |
| K.capture(el.querySelector('.fp-cap'), { list: `weekly-${cfg.game}`, src: `game-${cfg.game}`, label: cfg.remind || 'Email me a weekly reminder with new puzzles (free)', button: 'Remind me' }); | |
| }); | |
| } | |
| // Convert kit (funnel beacons + email capture) from the CyberMax site; optional, the game works without it. | |
| let kitQ = null; | |
| function kit(fn) { | |
| if (window.CMXConvert) return fn(window.CMXConvert); | |
| if (kitQ) return kitQ.push(fn); | |
| kitQ = [fn]; | |
| const sc = document.createElement('script'); | |
| sc.src = 'https://cybermaxtools.com/store/convert.js'; sc.async = true; | |
| sc.onload = () => kitQ.splice(0).forEach((f) => { try { f(window.CMXConvert); } catch { /* optional */ } }); | |
| document.head.appendChild(sc); | |
| } | |
| /** 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(); | |
| } | |