// Stoopshake as a Telegram Mini App (opened from @CyberMaxTools_bot: menu button, /play, challenge deep links, the // pinned channel post). Inside Telegram: telegram-web-app.js, full-height stage with safe areas, Telegram theme colours, // haptics, sharing into chats, and purchases with Telegram Stars only (Telegram's rule for digital goods in Mini Apps): // Crew Pass (full game) $2.99 = 235 Stars, Dice & Table Pack $1.99 = 155 Stars (same USD price points as the web, 0.013 USD/Star rule). // Payment: data Worker /tg/game-invoice -> WebApp.openInvoice; ownership comes back from /tg/game-owned as signed codes // that the normal unlock check accepts, so a Telegram purchase also unlocks the web game. (c) 2026 CyberMax. const DATA = 'https://data.cybermaxtools.com'; export const BOT = 'CyberMaxTools_bot'; export const ITEMS = { 'stoopshake-full': { stars: 235, usd: '$2.99', name: 'the Crew Pass' }, 'stoopshake-skins': { stars: 155, usd: '$1.99', name: 'the Dice & Table Pack' } }; const q = new URLSearchParams(location.search); export const inTG = q.has('tg') || /tgWebAppData=/.test(location.hash); let W = null; export const app = () => W; function load() { return new Promise((res) => { if (window.Telegram && window.Telegram.WebApp) { res(window.Telegram.WebApp); return; } const s = document.createElement('script'); s.src = 'https://telegram.org/js/telegram-web-app.js'; s.onload = () => res(window.Telegram && window.Telegram.WebApp); s.onerror = () => res(null); document.head.appendChild(s); }); } function insets() { if (!W) return; const a = W.safeAreaInset || {}; const c = W.contentSafeAreaInset || {}; const r = document.documentElement.style; r.setProperty('--tgt', `${(a.top || 0) + (c.top || 0)}px`); r.setProperty('--tgb', `${(a.bottom || 0) + (c.bottom || 0)}px`); r.setProperty('--tgh', `${W.viewportStableHeight || window.innerHeight}px`); } /** Start the Mini App shell. onResize: re-layout the canvas; onBack: Telegram back button pressed. */ export async function init({ onResize, onBack } = {}) { if (!inTG) return null; document.body.classList.add('tg'); W = await load(); if (!W || !W.initData) { document.body.classList.remove('tg'); W = null; return null; } // opened with ?tg=1 outside Telegram: plain web game try { W.ready(); W.expand(); } catch { /* old client */ } try { if (W.disableVerticalSwipes) W.disableVerticalSwipes(); } catch { /* < 7.7 */ } try { W.setHeaderColor('#0B0912'); W.setBackgroundColor('#07060C'); if (W.setBottomBarColor) W.setBottomBarColor('#07060C'); } catch { /* < 6.1 */ } const tp = W.themeParams || {}; if (tp.button_color) document.documentElement.style.setProperty('--tg-btn', tp.button_color); insets(); for (const ev of ['viewportChanged', 'safeAreaChanged', 'contentSafeAreaChanged']) { try { W.onEvent(ev, () => { insets(); onResize && onResize(); }); } catch { /* */ } } try { W.BackButton.onClick(() => onBack && onBack()); } catch { /* */ } onResize && onResize(); return W; } export function back(show) { try { if (W) (show ? W.BackButton.show() : W.BackButton.hide()); } catch { /* */ } } export function haptic(kind) { if (!W || !W.HapticFeedback) return; try { if (kind === 'select') W.HapticFeedback.selectionChanged(); else if (kind === 'success' || kind === 'error' || kind === 'warning') W.HapticFeedback.notificationOccurred(kind); else W.HapticFeedback.impactOccurred(kind || 'light'); } catch { /* */ } } /** Share into a Telegram chat (the native chat picker). */ export function share(text, url) { const link = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`; try { W.openTelegramLink(link); } catch { window.open(link, '_blank'); } } export const playUrl = () => `https://t.me/${BOT}?start=dice`; async function post(path, body) { const r = await fetch(DATA + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, initData: W.initData }) }); return r.json().catch(() => ({})); } /** What this Telegram user already bought (codes the normal unlock check accepts). */ export async function owned() { if (!W) return {}; try { return (await post('/tg/game-owned', {})).codes || {}; } catch { return {}; } } /** Buy with Stars: opens Telegram's own payment sheet. Resolves 'paid' | 'cancelled' | 'failed' | 'pending'. */ export async function buy(item) { if (!W) return 'failed'; let j; try { j = await post('/tg/game-invoice', { item }); } catch { j = {}; } if (!j.link) return 'failed'; return new Promise((res) => { try { W.openInvoice(j.link, (status) => res(status)); } catch { res('failed'); } }); } /** The Stars purchase sheet shown instead of the web checkout inside Telegram. */ export function sheet(item, { perks = [], onPaid } = {}) { const it = ITEMS[item]; let d = document.getElementById('tgbuy'); if (!d) { d = document.createElement('dialog'); d.id = 'tgbuy'; d.className = 'funpass'; document.body.appendChild(d); d.addEventListener('click', (e) => { if (e.target === d || e.target.closest('[data-close]')) d.close(); }); } d.innerHTML = `
Stoopshake
Paid through Telegram. 14-day promise: if it doesn't work as described, we fix it or refund your Stars (/paysupport in @${BOT}).