1(function(){ 2 var MP = window.MP_TRACK = window.MP_TRACK || {}; 3 var BASE = (function(){ try { var src = document.currentScript && document.currentScript.src; return src ? src.replace(/\/t\.js(\?.*)?$/, '') : ''; } catch(e){ return ''; } })(); 4 MP.base = BASE; 5 MP.turnstile_sitekey = '0x4AAAAAAE14Pa2a_IkZMnW2'; 6 var q = {}; try { new URLSearchParams(location.search).forEach(function(v,k){ q[k]=v; }); } catch(e){} 7 var KEY='mp_attr'; var attr={}; 8 try { attr = JSON.parse(sessionStorage.getItem(KEY)||'{}'); } catch(e){} 9 ['gclid','fbclid','rdt_cid'].forEach(function(k){ if(q[k]) attr[k]=q[k]; }); 10 attr.utm = attr.utm || {}; 11 Object.keys(q).forEach(function(k){ if(k.indexOf('utm_')===0) attr.utm[k]=q[k]; }); 12 if(!attr.session_id){ attr.session_id = (crypto.randomUUID ? crypto.randomUUID() : String(Date.now())+Math.random()); } 13 try { sessionStorage.setItem(KEY, JSON.stringify(attr)); } catch(e){} 14 MP.attribution = attr; 15 MP.formStartedAt = Date.now(); 16 17 // ---------- vendors, loaded only after consent (Cookiebot owns the banner) ---------- 18 // IDs are substituted by index.ts from wrangler vars; an empty id disables that vendor. 19 var IDS = { meta: '587309557746415', reddit: 'a2_gnasx3cqpmau', ga4: 'G-1KC26FHPT4', gads: 'AW-16914478893' }; 20 Object.keys(IDS).forEach(function(k){ if (IDS[k].indexOf('__') === 0) IDS[k] = ''; }); 21 var COOKIEBOT_WAIT = 8000, vendorsDone = { meta:false, reddit:false, ga4:false, gads:false }; 22 function loadScript(src, cb){ var s = document.createElement('script'); s.async = true; s.src = src; if (cb) s.onload = cb; (document.head || document.documentElement).appendChild(s); } 23 function gtag(){ window.dataLayer = window.dataLayer || []; window.dataLayer.push(arguments); } 24 function consentFromBanner(){ 25 // Test override on the staging address only: ?mp_consent=all|none 26 var m = /[?&]mp_consent=(all|none)/.exec(location.search); 27 if (m && /workers\.dev$/.test(location.hostname)) return { statistics: m[1] === 'all', marketing: m[1] === 'all', decided: true }; 28 var cb = window.Cookiebot; 29 if (!cb) return { statistics: false, marketing: false, decided: false }; 30 var c = cb.consent || {}; 31 return { statistics: !!c.statistics, marketing: !!c.marketing, decided: !!(cb.consented || cb.declined || cb.hasResponse) }; 32 } 33 function applyConsent(){ 34 var c = consentFromBanner(); 35 gtag('consent', 'update', { 36 analytics_storage: c.statistics ? 'granted' : 'denied', 37 ad_storage: c.marketing ? 'granted' : 'denied', 38 ad_user_data: c.marketing ? 'granted' : 'denied', 39 ad_personalization: c.marketing ? 'granted' : 'denied' 40 }); 41 if (c.statistics && IDS.ga4 && !vendorsDone.ga4) { 42 vendorsDone.ga4 = true; loadScript('https://www.googletagmanager.com/gtag/js?id=' + IDS.ga4); gtag('js', new Date()); 43 // GA4 User ID: the player number (MP-â¦, never an email) once this browser has signed up, so sessions stitch across days and devices. 44 var pn = playerNo(); gtag('config', IDS.ga4, pn ? { user_id: pn } : {}); 45 } 46 if (c.marketing && IDS.gads && !vendorsDone.gads) { vendorsDone.gads = true; if (!vendorsDone.ga4) loadScript('https://www.googletagmanager.com/gtag/js?id=' + IDS.gads); gtag('js', new Date()); gtag('config', IDS.gads); } 47 if (c.marketing && IDS.meta && !vendorsDone.meta) { 48 vendorsDone.meta = true; 49 if (!window.fbq) { var n = window.fbq = function(){ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments); }; n.push = n; n.loaded = true; n.version = '2.0'; n.queue = []; loadScript('https://connect.facebook.net/en_US/fbevents.js'); } 50 window.fbq('init', IDS.meta); window.fbq('track', 'PageView'); 51 } 52 if (c.marketing && IDS.reddit && !vendorsDone.reddit) { 53 vendorsDone.reddit = true; 54 if (!window.rdt) { var r = window.rdt = function(){ r.sendEvent ? r.sendEvent.apply(r, arguments) : r.callQueue.push(arguments); }; r.callQueue = []; loadScript('https://www.redditstatic.com/ads/pixel.js'); } 55 window.rdt('init', IDS.reddit); window.rdt('track', 'PageVisit'); 56 } 57 } 58 function playerNo(){ try { return localStorage.getItem('mpPlayerNo') || ''; } catch(e){ return ''; } } 59 // Called by the popup after a successful signup: remember the player number and tell GA4 (if it is running).
60 MP.identify = function(pn, justSignedUp){ 61 try { localStorage.setItem('mpPlayerNo', pn); } catch(e){} 62 if (vendorsDone.ga4) { gtag('config', IDS.ga4, { user_id: pn }); if (justSignedUp) gtag('event', 'sign_up', { method: 'popup' }); } 63 if (vendorsDone.reddit && justSignedUp) { try { window.rdt('track', 'SignUp'); } catch(e){} } 64 }; 65 // ---------- page view beacon (first party, no cookie, nothing identifying) ---------- 66 // Counts visitors, where they came from and how the popup went. The server derives UTM, referrer 67 // host, country, device and bot from what is sent here and stores nothing else: no session id, no 68 // IP, no user agent. That is why it needs no consent â and why the mp_attr session id above, which 69 // exists for click-id stitching at signup, must never be put in this payload. 70 var BEACON_PATH = '/api/beacon', BEACON_MAX = 10; 71 // The staging test pages are not traffic. ?beacon=1 turns the beacon on there for a check. 72 var beaconOn = !(/^\/test(-publishing)?\/?$/.test(location.pathname) && !/[?&]beacon=1(&|$)/.test(location.search)); 73 var beaconQueue = []; 74 function beaconPlayerNo(){ 75 // Only when this browser already knows its own MP-<n> AND the banner's marketing category is 76 // granted. Otherwise the field is omitted entirely â never sent empty, never sent unconsented. 77 try { return consentFromBanner().marketing ? (playerNo() || '') : ''; } catch(e){ return ''; } 78 } 79 function beaconFlush(){ 80 if (!beaconQueue.length) return; 81 var payload = JSON.stringify({ events: beaconQueue }); beaconQueue = []; 82 try { if (navigator.sendBeacon && navigator.sendBeacon(BASE + BEACON_PATH, payload)) return; } catch(e){} 83 try { fetch(BASE + BEACON_PATH, { method:'POST', body: payload, keepalive: true, credentials:'omit', headers:{'content-type':'text/plain'} }); } catch(e){} 84 } 85 MP.track = function(event, extra){ 86 if (!beaconOn) return; 87 var ev = { event: event, path: location.pathname, search: location.search, referrer: document.referrer || '', ts: Date.now() }; 88 if (extra) { for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k) && extra[k] != null) ev[k] = extra[k]; } 89 var pn = beaconPlayerNo(); if (pn) ev.player_no = pn; 90 beaconQueue.push(ev); 91 // The page view goes at once; a Steam click may be the last thing this page does. The rest ride 92 // along to the next flush, at visibilitychange or pagehide. 93 if (event === 'view' || event === 'steam_click' || beaconQueue.length >= BEACON_MAX) beaconFlush(); 94 }; 95 document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'hidden') beaconFlush(); }, false); 96 window.addEventListener('pagehide', beaconFlush, false); 97 // Steam store clicks, captured on the way down so a handler that stops propagation cannot hide them. 98 // Nothing is prevented: the navigation happens exactly as it would have. 99 document.addEventListener('click', function(e){ 100 var el = e.target, a = null; 101 while (el && el.nodeType === 1) { if (el.tagName === 'A' && el.href) { a = el; break; } el = el.parentNode; } 102 if (!a || a.hostname !== 'store.steampowered.com') return; 103 var m = /\/app\/(\d+)/.exec(String(a.pathname || '')); 104 MP.track('steam_click', { steam_appid: m ? Number(m[1]) : null }); 105 }, true); 106 MP.track('view', { webdriver: !!navigator.webdriver }); 107 108 // ---------- the cookie banner itself (Cookiebot), loaded by this script ---------- 109 // Arthur, 2026-09-18: one script owns the banner and everything behind it, so the site template carries 110 // only /play/t.js. Skipped while Google Tag Manager is on the page (its Cookiebot tag still does this) 111 // or if the page already has the loader. Manual blocking mode: this script gates the vendors itself. 112 var CB = { cbid: '74d97016-5e9c-4bda-a9a3-abbe99567d10', georegions: "{'region':'AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, IS, LI, NO, GB','cbid':'aa208b68-3918-479d-b01b-bcb627d16b31'},{'region':'US','cbid':'74d97016-5e9c-4bda-a9a3-abbe99567d10'}" }; // georegions arrives as a JSON string literal
113 Object.keys(CB).forEach(function(k){ if (typeof CB[k] !== 'string' || CB[k].indexOf('__') === 0) CB[k] = ''; }); 114 function injectCookiebot(){ 115 if (!CB.cbid || window.Cookiebot || window.google_tag_manager || document.getElementById('Cookiebot')) return; 116 var s = document.createElement('script'); s.id = 'Cookiebot'; s.src = 'https://consent.cookiebot.com/uc.js'; 117 s.setAttribute('data-cbid', CB.cbid); 118 if (CB.georegions) s.setAttribute('data-georegions', CB.georegions); 119 s.setAttribute('data-consentmode-dataredaction', 'dynamic'); 120 s.setAttribute('data-blockingmode', 'manual'); 121 var head = document.head || document.documentElement; head.insertBefore(s, head.firstChild); 122 } 123 function startVendors(){ 124 // While Google Tag Manager is still on the page it owns the pixels; stay quiet so nothing double counts. 125 // Once Sergio removes the GTM snippet this script takes over on its own, no deploy needed. 126 if (window.google_tag_manager) return; 127 gtag('consent', 'default', { analytics_storage: 'denied', ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', wait_for_update: 500 }); 128 var since = Date.now(); 129 // The banner's outcome goes to the beacon once per page view (Arthur, 2026-09-24): shown, then accepted or declined; 130 // or "not asked" when no dialog appears within the wait (a region Cookiebot does not ask, or the banner never loaded). 131 // The beacon is consent-free and sees everyone, so "accepted + not asked" over all views is the share the pixels can measure. 132 var consentSent = false, dialogSeen = false; 133 function recordOutcome(){ if (consentSent) return; var c = consentFromBanner(); if (!c.decided) return; consentSent = true; MP.track(c.statistics || c.marketing ? 'consent_accepted' : 'consent_declined'); } 134 window.addEventListener('CookiebotOnDialogDisplay', function(){ if (!dialogSeen) { dialogSeen = true; MP.track('consent_shown'); } }, false); 135 setTimeout(function(){ if (!consentSent && !dialogSeen && !consentFromBanner().decided) { consentSent = true; MP.track('consent_not_asked'); } }, COOKIEBOT_WAIT); 136 function check(){ var c = consentFromBanner(); if (c.decided) { applyConsent(); recordOutcome(); return; } if (!window.Cookiebot && Date.now() - since >= COOKIEBOT_WAIT) return; setTimeout(check, 250); } 137 window.addEventListener('CookiebotOnAccept', function(){ applyConsent(); recordOutcome(); }, false); 138 window.addEventListener('CookiebotOnDecline', function(){ applyConsent(); recordOutcome(); }, false); 139 // Returning visitors: Cookiebot restores the stored choice and fires these on load, possibly after our 5 s wait. 140 window.addEventListener('CookiebotOnConsentReady', function(){ if (consentFromBanner().decided) applyConsent(); }, false); 141 window.addEventListener('CookiebotOnLoad', function(){ if (consentFromBanner().decided) applyConsent(); }, false); 142 check(); 143 } 144 function bootVendors(){ injectCookiebot(); startVendors(); } 145 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootVendors); else bootVendors(); 146 147 function consentState(){ 148 // Consent Mode v2 state, or the banner's marketing category. Absent = unknown = no browser-side ad fire. 149 var b = consentFromBanner(); 150 if (b.decided) return { ad_user_data: b.marketing, ad_personalization: b.marketing }; 151 var g = window.google_tag_data && window.google_tag_data.ics && window.google_tag_data.ics.entries; 152 if(!g) return { ad_user_data: null, ad_personalization: null }; 153 var f = function(k){ var e=g[k]; return e ? (e.update!==undefined ? e.update : e['default']) === true : null; }; 154 return { ad_user_data: f('ad_user_data'), ad_personalization: f('ad_personalization') }; 155 } 156 157 MP.signup = function(fields){ 158 var cmp = consentState(); 159 var body = { 160 email: fields.email, age_band: fields.age_band, 161 consents: { email_marketing: !!fields.email_marketing, ad_sharing: !!fields.ad_sharing }, 162 turnstile_token: fields.turnstile_token, website: fields.website || '', 163 source: fields.source, utm: attr.utm, gclid: attr.gclid, fbclid: attr.fbclid, rdt_cid: attr.rdt_cid, 164 session_id: attr.session_id, cmp: cmp, form_started_at: MP.formStartedAt, page_url: location.href 165 }; 166 return fetch(BASE + '/api/signup', { method:'POST', credentials:'same-origin', 167 headers:{'content-type':'application/json'}, body: JSON.stringify(body) }) 168 .then(function(r){ return r.json(); }) 169 .then(function(res){ 170 if(res && res.ok && res.event_id && res.fire && res.fire.meta && cmp.ad_user_data === true && typeof window.fbq === 'function'){ 171 window.fbq('track', 'CompleteRegistration', {}, { eventID: res.event_id }); 172 } 173 if(res && res.ok && res.event_id && window.dataLayer){ 174 window.dataLayer.push({ event:'mp_signup', event_id: res.event_id, player_no: res.player_no }); 175 } 176 return res; 177 }); 178 }; 179})(); 180(function(){ 181 if (window.__mpSignupPopup) return; window.__mpSignupPopup = true; 182 var CSS = ".mp-signup{--mp-red:#ed192d;--mp-red-text:#ff5a66;--mp-red-text-hover:#ff8a93;--mp-bg:#171615;--mp-field:#111010;--mp-border:#3a3835;--mp-rule:#f2f0eb;--mp-text:#f2f0eb;--mp-text-list:#d8d5ce;--mp-text-body:#c9c6bf;--mp-text-faint:#6f6c63;--mp-disabled-bg:#2a2826;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:16px;padding-bottom:max(16px,env(safe-area-inset-bottom));font-family:'IBM Plex Sans',system-ui,sans-serif;color:var(--mp-text);-webkit-font-smoothing:antialiased;line-height:normal;text-align:left}\n.mp-signup[hidden]{display:none}\n.mp-signup *,.mp-signup *::before,.mp-signup *::after{box-sizing:border-box}\n.mp-signup__overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.78)}\n.mp-signup__dialog{position:relative;width:min(430px,100%);max-height:100%;overflow:auto;background:var(--mp-bg);border:1px solid var(--mp-border);margin:0}\n.mp-signup__close{position:absolute;top:14px;right:14px;z-index:2;width:28px;height:28px;padding:0;margin:0;border:0;border-radius:0;cursor:pointer;background:rgba(17,16,16,.75);color:var(--mp-text);display:flex;align-items:center;justify-content:center;box-shadow:none}\n.mp-signup__close::before{content:'';position:absolute;top:-8px;right:-8px;bottom:-8px;left:-8px}\n.mp-signup__close svg{display:block}\n.mp-signup__close:hover,.mp-signup__close:focus-visible{background:var(--mp-red);color:#fff;outline:none}\n.mp-signup__hero{display:block;width:100%;height:140px;object-fit:cover;margin:0;border:0}\n.mp-signup__body{padding:26px 36px 32px}\n.mp-signup__logo{display:block;height:18px;width:auto;margin:0 0 18px;border:0}\n.mp-signup__title{margin:0;padding:0;font-family:inherit;font-size:30px;line-height:1.05;font-weight:600;letter-spacing:-0.03em;color:var(--mp-text);text-transform:none}\n.mp-signup__list{list-style:none;margin:20px 0 0;padding:20px 0 0;border-top:2px solid var(--mp-rule);display:flex;flex-direction:column;gap:12px}\n.mp-signup__list li{display:flex;gap:12px;align-items:baseline;margin:0;padding:0;font-size:14.5px;line-height:1.5;color:var(--mp-text-list)}\n.mp-signup__list span{flex:none;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:500;letter-spacing:0.08em;color:var(--mp-red-text)}\n.mp-signup__form{margin:22px 0 0;padding:0}\n.mp-signup__input{display:block;width:100%;height:48px;margin:0;padding:0 14px;border:1px solid var(--mp-border);border-radius:0;background:var(--mp-field);color:var(--mp-text);font:inherit;font-size:15px;box-shadow:none;-webkit-appearance:none;appearance:none}\n.mp-signup__input::placeholder{color:var(--mp-text-faint);opacity:1}\n.mp-signup__input:focus{outline:none;border-color:var(--mp-red)}\n.mp-signup__consent{display:flex;gap:11px;align-items:flex-start;margin:14px 0 0;cursor:pointer;font-weight:400}\n.mp-signup__consent input{margin:3px 0 0;padding:0;width:16px;height:16px;flex:none;accent-color:var(--mp-red)}\n.mp-signup__consent span{font-size:12.5px;line-height:1.5;color:var(--mp-text-body)}\n.mp-signup a{color:var(--mp-red-text);text-decoration:none}\n.mp-signup a:hover{color:var(--mp-red-text-hover);text-decoration:underline}\n.mp-signup__submit{display:block;width:100%;height:52px;margin:16px 0 0;padding:0;border:0;border-radius:0;cursor:pointer;background:var(--mp-red);color:#fff;font:inherit;font-size:15px;font-weight:500;letter-spacing:0.01em;text-transform:none;box-shadow:none}\n.mp-signup__submit:hover{background:#b3121f}\n.mp-signup__submit:disabled{background:var(--mp-disabled-bg);color:var(--mp-text-faint);cursor:not-allowed}\n.mp-signup__submit[aria-busy=\"true\"]{opacity:.7;cursor:progress}\n.mp-signup__error{margin:10px 0 0;font-size:12.5px;line-height:1.5;color:var(--mp-red-text)}\n.mp-signup__error[hidden]{display:none}\n.mp-signup__hp{position:absolute;
182left:-9999px;top:0;width:1px;height:1px;opacity:0;pointer-events:none}\n.mp-signup__turnstile{margin:0}\n.mp-signup__turnstile:not(:empty){margin-top:12px}\n.mp-signup__foot{margin:12px 0 0;text-align:center;font-size:12px;line-height:1.5;color:var(--mp-text-faint)}\n.mp-signup__success{padding:26px 36px 32px;text-align:center;display:flex;flex-direction:column}\n.mp-signup__success-inner{flex:1;display:flex;flex-direction:column;justify-content:center;padding:16px 0 24px}\n.mp-signup__continue{margin-top:0}\n.mp-signup__success[hidden]{display:none}\n.mp-signup__body[hidden]{display:none}\n.mp-signup__check{width:56px;height:56px;margin:0 auto 18px;background:var(--mp-red);color:#fff;font-size:25px;display:flex;align-items:center;justify-content:center}\n.mp-signup__title--success{font-size:27px;line-height:1.08;letter-spacing:-0.025em}\n.mp-signup__success-text{margin:10px 0 0;font-size:15px;line-height:1.55;color:var(--mp-text-body)}\n@media (max-width:480px){.mp-signup__success{padding:18px 20px 22px}}\n/* mobile fit + our own tick box (prototyped with Arthur in the Popup Lab, 2026-09-18) */\n.mp-signup{color-scheme:dark;align-items:safe center}\n.mp-signup__dialog{max-height:calc(100dvh - 32px);overscroll-behavior:contain;scrollbar-width:none}\n.mp-signup__dialog::-webkit-scrollbar{display:none}\n.mp-signup__input:-webkit-autofill,.mp-signup__input:-webkit-autofill:hover,.mp-signup__input:-webkit-autofill:focus{-webkit-box-shadow:0 0 0 1000px var(--mp-field) inset;-webkit-text-fill-color:var(--mp-text);caret-color:var(--mp-text);border-color:var(--mp-border)}\n.mp-signup__consent input{-webkit-appearance:none;appearance:none;width:18px;height:18px;margin:2px 0 0;border:1px solid var(--mp-border);background:var(--mp-field);border-radius:0;position:relative;cursor:pointer}\n.mp-signup__consent input:checked{background:var(--mp-red);border-color:var(--mp-red)}\n.mp-signup__consent input:checked::after{content:'';position:absolute;left:5px;top:1px;width:5px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}\n.mp-signup__consent input:focus-visible{outline:2px solid var(--mp-red-text);outline-offset:2px}\n@media (max-width:480px){\n /* v2: keep a 12px margin all round and never clip the top, so the X stays reachable */\n .mp-signup{padding:12px;padding-bottom:max(12px,env(safe-area-inset-bottom))}\n .mp-signup__dialog{max-height:calc(100dvh - 24px)}\n .mp-signup__hero{height:104px}\n .mp-signup__body{padding:18px 20px 22px}\n .mp-signup__logo{height:15px;margin-bottom:12px}\n .mp-signup__title{font-size:25px}\n .mp-signup__list{margin-top:14px;padding-top:14px;gap:8px}\n .mp-signup__list li{font-size:13.5px;line-height:1.4}\n .mp-signup__form{margin-top:16px}\n .mp-signup__input{height:46px;font-size:16px}\n .mp-signup__consent{margin-top:12px}\n .mp-signup__consent span{font-size:12px}\n .mp-signup__submit{height:50px;margin-top:14px}\n .mp-signup__foot{margin-top:10px}\n}\n@media (max-width:480px) and (max-height:700px){\n /* short phones (iPhone SE): everything a notch tighter, footer line dropped */\n .mp-signup__hero{height:64px}\n .mp-signup__body{padding:16px 14px 16px}\n .mp-signup__logo{margin-bottom:10px}\n .mp-signup__title{font-size:23px}\n .mp-signup__list{margin-top:12px;padding-top:12px;gap:6px}\n .mp-signup__list li{font-size:13px}\n .mp-signup__form{margin-top:12px}\n .mp-signup__consent{margin-top:10px}\n .mp-signup__submit{margin-top:12px;height:48px}\n .mp-signup__foot{display:none}\n}\nbody.mp-signup-open{overflow:hidden}"; 183 var HTML = "<div class=\"mp-signup__overlay\" data-mp-close></div>\n<div class=\"mp-signup__dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"mp-signup-title\">\n <button class=\"mp-signup__close\" type=\"button\" aria-label=\"Close\" data-mp-close>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\"><path d=\"M2 2l14 14M16 2L2 16\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"square\"/></svg>\n </button>\n <img class=\"mp-signup__hero\" src=\"__BASE__/assets/keyart.png\" alt=\"\">\n <div class=\"mp-signup__body\" data-mp-form-view>\n <img class=\"mp-signup__logo\" src=\"__BASE__/assets/microprose-logo.svg\" alt=\"MicroProse\">\n <h2 class=\"mp-signup__title\" id=\"mp-signup-title\">Become a founding player</h2>\n <ol class=\"mp-signup__list\">\n <li><span>01</span>Play in early betas and playtests.</li>\n <li><span>02</span>Hear updates straight from the devs.</li>\n <li><span>03</span>Unlock exclusive in-game items.</li>\n </ol>\n <form class=\"mp-signup__form\" novalidate>\n <input class=\"mp-signup__input\" type=\"email\" name=\"email\" placeholder=\"[email protected]\" autocomplete=\"email\" inputmode=\"email\" required>\n <input class=\"mp-signup__hp\" type=\"text\" name=\"website\" tabindex=\"-1\" autocomplete=\"off\" aria-hidden=\"true\">\n <label class=\"mp-signup__consent\">\n <input type=\"checkbox\" name=\"consent\" required>
183\n <span>By submitting this form and signing up for email, you agree to our <a href=\"https://www.microprose.com/article/terms-and-conditions\" target=\"_blank\" rel=\"noopener\">Terms of Use</a> and <a href=\"https://www.microprose.com/privacy\" target=\"_blank\" rel=\"noopener\">Privacy Policy</a> and consent to receive email updates from MicroProse on things like game news, offers and member rewards. Unsubscribe anytime.</span>\n </label>\n <div class=\"mp-signup__turnstile\"></div>\n <button class=\"mp-signup__submit\" type=\"submit\" disabled>Sign up</button>\n <p class=\"mp-signup__error\" role=\"alert\" hidden></p>\n </form>\n <p class=\"mp-signup__foot\">No spam. We respect your privacy.</p>\n </div>\n <div class=\"mp-signup__body mp-signup__success\" data-mp-success-view hidden>\n <div class=\"mp-signup__success-inner\">\n <div class=\"mp-signup__check\" aria-hidden=\"true\">✓</div>\n <h2 class=\"mp-signup__title mp-signup__title--success\">You're in.</h2>\n <p class=\"mp-signup__success-text\">Welcome, founding player.</p>\n </div>\n <button class=\"mp-signup__submit mp-signup__continue\" type=\"button\" data-mp-close>Continue browsing</button>\n </div>\n</div>"; 184 var FONT_HREF = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@500&display=swap'; 185 var TURNSTILE_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=__mpTurnstileReady'; 186 var FALLBACK_SITEKEY = '0x4AAAAAAE14Pa2a_IkZMnW2'; 187 var DISMISS_KEY = 'mpSignupDismissed', DONE_KEY = 'mpSignedUp', DISMISS_DAYS = 30; 188 var COOKIEBOT_WAIT = 5000, DESKTOP_MIN = 8000, MOBILE_MIN = 12000, SCROLL_DEPTH = 0.4, TURNSTILE_TIMEOUT = 8000; 189 190 // One copy of the path â appid map, shared with the server (appmap.ts) so the popup and the page 191 // view beacon always agree about which title a page is. 192 var LANDING_APPS = [["/landing/b17-kickstarter",2000800],["/landing/cleared-hot",1710820],["/landing/liquidation",896250],["/landing/rise-of-piracy",1400660],["/landing/task-force-admiral",1281220],["/landing/repterra",2768380],["/landing/better-than-dead",2599690]]; 193 var SLUG_APPS = [["sea-power",1286220],["carrier-command-2",1489630],["regiments",1109680],["second-front",1148490],["silent-depth-2",2591010],["mighty-eighth-vr",1325580],["b-17",2000800],["task-force-admiral",1281220],["rise-of-piracy",1400660],["repterra",2768380],["cleared-hot",1710820],["liquidation",896250],["better-than-dead",2599690]]; 194 195 var pageStart = Date.now(); 196 var path = String(location.pathname || '').toLowerCase(); 197 var search = String(location.search || ''); 198 199 function appidFromPage(){ 200 var i; 201 for (i = 0; i < LANDING_APPS.length; i++) if (path.indexOf(LANDING_APPS[i][0]) === 0) return LANDING_APPS[i][1]; 202 for (i = 0; i < SLUG_APPS.length; i++) if (path.indexOf(SLUG_APPS[i][0]) !== -1) return SLUG_APPS[i][1]; 203 return null; 204 } 205 var APPID = appidFromPage(); 206 207 function eligible(){ 208 if (path.indexOf('/landing/') === 0 || path.indexOf('/newsletter') === 0 || path.indexOf('/publishing') === 0) return false; 209 if (path.indexOf('/checkout') !== -1) return false; 210 if (/[?&]nopopup(=|&|$)/.test(search)) return false; 211 return true; 212 } 213 function store(k, v){ try { if (v === undefined) return localStorage.getItem(k); localStorage.setItem(k, v); } catch(e){} return null; } 214 function signedUp(){ return !!store(DONE_KEY); } 215 function dismissedRecently(){ var t = +store(DISMISS_KEY) || 0; return Date.now() - t < DISMISS_DAYS * 864e5; } 216 function closest(el, sel){ 217 while (el && el.nodeType === 1) { if (el.matches ? el.matches(sel) : false) return el; el = el.parentNode; } 218 return null; 219 } 220 function mp(){ return window.MP_TRACK || {}; } 221 // Popup funnel â /api/beacon (tracking.ts owns the queue). Silent if the beacon is off. 222 function track(ev){ try { var m = mp(); if (m.track) m.track(ev, { appid: APPID }); } catch(e){} } 223 function base(){ var m = mp(); return (typeof m.base === 'string' && m.base) ? m.base : '/play'; } 224 225 // ---------- DOM ---------- 226 var root = null, dialog, formView, successView, form, email, consent, submitBtn, errorEl, hp, tsBox, closeBtn; 227 var lastFocus = null, openedAt = 0, typed = false, submitted = false, succeeded = false;
228 229 function inject(){ 230 if (root) return; 231 if (!document.body) return; 232 if (!document.getElementById('mp-signup-font')) { 233 var link = document.createElement('link'); link.id = 'mp-signup-font'; link.rel = 'stylesheet'; link.href = FONT_HREF; 234 (document.head || document.documentElement).appendChild(link); 235 } 236 var style = document.createElement('style'); style.id = 'mp-signup-css'; style.appendChild(document.createTextNode(CSS)); 237 (document.head || document.documentElement).appendChild(style); 238 root = document.createElement('div'); root.className = 'mp-signup'; root.id = 'mp-signup'; root.hidden = true; 239 root.innerHTML = HTML.replace(/__BASE__/g, base()); 240 document.body.appendChild(root); 241 242 dialog = root.querySelector('.mp-signup__dialog'); 243 formView = root.querySelector('[data-mp-form-view]'); 244 successView = root.querySelector('[data-mp-success-view]'); 245 form = root.querySelector('.mp-signup__form'); 246 email = form.querySelector('input[name="email"]'); 247 consent = form.querySelector('input[name="consent"]'); 248 submitBtn = form.querySelector('.mp-signup__submit'); 249 errorEl = form.querySelector('.mp-signup__error'); 250 hp = form.querySelector('input[name="website"]'); 251 tsBox = form.querySelector('.mp-signup__turnstile'); 252 closeBtn = root.querySelector('.mp-signup__close'); 253 254 root.addEventListener('click', function(e){ if (closest(e.target, '[data-mp-close]')) close(); }); 255 dialog.addEventListener('click', function(e){ if (!closest(e.target, '[data-mp-close]')) e.stopPropagation(); }); 256 document.addEventListener('keydown', function(e){ 257 if (!root || root.hidden) return; 258 var key = e.key || e.keyIdentifier; 259 if (key === 'Escape' || key === 'Esc' || e.keyCode === 27) { close(); return; } 260 if (key === 'Tab' || e.keyCode === 9) { 261 var f = dialog.querySelectorAll('button:not([disabled]), input:not([tabindex="-1"]), a[href]'); 262 var list = [], i; 263 for (i = 0; i < f.length; i++) if (f[i].offsetParent !== null || f[i] === closeBtn) list.push(f[i]); 264 if (!list.length) return; 265 var first = list[0], last = list[list.length - 1]; 266 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 267 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 268 } 269 }); 270 email.addEventListener('input', function(){ if (!typed) { typed = true; track('popup_started'); } refresh(); }); 271 consent.addEventListener('change', refresh); 272 form.addEventListener('submit', onSubmit); 273 } 274 275 function valid(){ return /.+@.+\..+/.test(email.value) && consent.checked; } 276 function refresh(){ submitBtn.disabled = !valid(); } 277 function showError(msg){ errorEl.textContent = msg; errorEl.hidden = false; } 278 function hideError(){ errorEl.textContent = ''; errorEl.hidden = true; } 279 280 function open(){ 281 inject(); 282 if (!root || !root.hidden) return; 283 lastFocus = document.activeElement; 284 root.hidden = false; 285 openedAt = Date.now(); 286 track('popup_shown'); 287 document.body.className += ' mp-signup-open'; 288 loadTurnstile(); 289 setTimeout(function(){ try { email.focus({ preventScroll: true }); } catch(e){ email.focus(); } }, 0); 290 } 291 function close(){ 292 if (!root || root.hidden) return; 293 root.hidden = true; 294 // The success panel shows before the server answers, so a close after a submit is not a dismissal. 295 if (!succeeded && !submitted) track('popup_dismissed'); 296 document.body.className = document.body.className.replace(/\s*\bmp-signup-open\b/g, ''); 297 store(DISMISS_KEY, String(Date.now())); 298 if (lastFocus && lastFocus.focus) { try { lastFocus.focus(); } catch(e){} } 299 } 300 301 // ---------- Turnstile (invisible, executed on submit) ---------- 302 var tsWidget = null, tsLoading = false; 303 function sitekey(){ var m = mp(); return (typeof m.turnstile_sitekey === 'string' && m.turnstile_sitekey && m.turnstile_sitekey.indexOf('__') !== 0) ? m.turnstile_sitekey : FALLBACK_SITEKEY; } 304 function renderTurnstile(){ 305 if (tsWidget !== null || !window.turnstile || !tsBox) return; 306 try { 307 tsWidget = window.turnstile.render(tsBox, { sitekey: sitekey(), execution: 'execute', appearance: 'interaction-only', callback: function(t){ tsResolve(t); }, 'error-callback': function(){ tsResolve(null); }, 'expired-callback': function(){}, 'timeout-callback': function(){ tsResolve(null); } }); 308 } catch(e){ tsWidget = null; } 309 } 310 window.__mpTurnstileReady = function(){ renderTurnstile(); }; 311 function loadTurnstile(){ 312 if (window.turnstile) { renderTurnstile(); return; } 313 if (tsLoading) return; tsLoading = true; 314 var s = document.createElement('script'); s.src = TURNSTILE_SRC; s.async = true; s.defer = true; 315 (document.head || document.documentElement).appendChild(s); 316 } 317 var tsWaiter = null; 318 function tsResolve(token){ var w = tsWaiter; tsWaiter = null; if (w) { clearTimeout(w.timer); w.cb(token); } } 319 function getToken(cb){ 320 if (!window.turnstile || tsWidget === null) renderTurnstile(); 321 if (!window.turnstile || tsWidget === null) { cb(null); return; } 322 var done = false;
323 tsWaiter = { cb: function(t){ if (done) return; done = true; cb(t); }, timer: setTimeout(function(){ tsWaiter = null; if (!done) { done = true; cb(null); } }, TURNSTILE_TIMEOUT) }; 324 try { window.turnstile.reset(tsWidget); window.turnstile.execute(tsWidget); } 325 catch(e){ tsResolve(null); } 326 } 327 328 // ---------- submit ---------- 329 function consentModeState(){ 330 // Prefer Cookiebot's marketing category; fall back to Consent Mode v2 state if a tag manager left one. 331 var cb = window.Cookiebot; 332 if (cb && cb.consent && typeof cb.consent.marketing === 'boolean' && (cb.consented || cb.declined || cb.hasResponse)) { 333 return { ad_user_data: cb.consent.marketing, ad_personalization: cb.consent.marketing }; 334 } 335 var g = window.google_tag_data && window.google_tag_data.ics && window.google_tag_data.ics.entries; 336 if (!g) return { ad_user_data: null, ad_personalization: null }; 337 var f = function(k){ var e = g[k]; return e ? (e.update !== undefined ? e.update : e['default']) === true : null; }; 338 return { ad_user_data: f('ad_user_data'), ad_personalization: f('ad_personalization') }; 339 } 340 function buildBody(token){ 341 var m = mp(), a = m.attribution || {}; 342 return { 343 email: email.value.trim(), 344 consents: { email_marketing: true, ad_sharing: true }, 345 turnstile_token: token || null, 346 website: hp ? hp.value : '', 347 source: 'site-popup', 348 page_url: location.href, 349 signup_appid: APPID, 350 utm: a.utm || {}, gclid: a.gclid, fbclid: a.fbclid, rdt_cid: a.rdt_cid, session_id: a.session_id, 351 cmp: consentModeState(), 352 form_started_at: (typeof m.formStartedAt === 'number' ? m.formStartedAt : openedAt) 353 }; 354 } 355 function post(body, cb){ 356 var x = new XMLHttpRequest(); 357 x.open('POST', base() + '/api/signup', true); 358 x.setRequestHeader('content-type', 'application/json'); 359 x.withCredentials = false; 360 x.onreadystatechange = function(){ 361 if (x.readyState !== 4) return; 362 var res = null; try { res = JSON.parse(x.responseText); } catch(e){} 363 if (x.status === 0 || !res) { cb(new Error('network'), null); return; } 364 cb(null, res); 365 }; 366 x.onerror = function(){ cb(new Error('network'), null); }; 367 try { x.send(JSON.stringify(body)); } catch(e){ cb(e, null); } 368 } 369 function onSubmit(e){ 370 e.preventDefault(); 371 if (!valid()) return; 372 submitted = true; track('popup_submitted'); 373 hideError(); 374 submitBtn.setAttribute('aria-busy', 'true'); submitBtn.disabled = true; 375 // Show the success panel straight away (no waiting on the bot check or the network); keep the panel the 376 // same height as the form so the popup does not jump. If the server refuses, come back to the form. 377 successView.style.minHeight = formView.offsetHeight + 'px'; 378 formView.hidden = true; successView.hidden = false; 379 try { root.querySelector('.mp-signup__continue').focus({ preventScroll: true }); } catch(e0){} 380 getToken(function(token){ 381 post(buildBody(token), function(err, res){ 382 if (!err && res && res.ok === true) { 383 succeeded = true; track('popup_succeeded'); 384 store(DONE_KEY, String(Date.now())); store(DISMISS_KEY, String(Date.now())); 385 if (res.player_no) { store('mpPlayerNo', String(res.player_no)); try { if (window.MP_TRACK && window.MP_TRACK.identify) window.MP_TRACK.identify(String(res.player_no), true); } catch(e5){} } 386 if (res.fire && res.fire.meta === true && res.event_id && typeof window.fbq === 'function') { 387 try { window.fbq('track', 'CompleteRegistration', {}, { eventID: res.event_id }); } catch(e2){} 388 } 389 try { closeBtn.focus(); } catch(e3){} 390 return; 391 } 392 successView.hidden = true; formView.hidden = false; 393 submitBtn.removeAttribute('aria-busy'); refresh(); 394 var code = (!err && res && res.error) ? String(res.error) : ''; 395 showError(code === 'invalid_email' ? "That email doesn't look right." : 'Something went wrong. Please try again.'); 396 try { email.focus(); } catch(e4){} 397 }); 398 }); 399 } 400 401 // ---------- eligibility, Cookiebot, timing ---------- 402 var opened = false, armed = false;
403 function cookiebotUndecided(){ var cb = window.Cookiebot; return !!cb && !(cb.consented || cb.declined || cb.hasResponse); } 404 function whenDecided(cb){ 405 var fired = false, since = Date.now(); 406 function go(){ if (fired) return; fired = true; cb(); } 407 function check(){ 408 if (fired) return; 409 var c = window.Cookiebot; 410 if (c) { if (!cookiebotUndecided()) go(); return; } // present: wait for an event if undecided 411 if (Date.now() - since >= COOKIEBOT_WAIT) { go(); return; } // never loaded: treat as decided 412 setTimeout(check, 250); 413 } 414 window.addEventListener('CookiebotOnAccept', go, false); 415 window.addEventListener('CookiebotOnDecline', go, false); 416 check(); 417 } 418 function coarse(){ try { return !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches); } catch(e){ return false; } } 419 function scrolledEnough(){ 420 var d = document.documentElement, b = document.body || d; 421 var h = Math.max(d.scrollHeight, b.scrollHeight, d.offsetHeight, b.offsetHeight); 422 var vh = window.innerHeight || d.clientHeight; 423 var y = window.pageYOffset || d.scrollTop || b.scrollTop || 0; 424 if (h <= vh) return true; 425 return (y + vh) / h >= SCROLL_DEPTH; 426 } 427 function tryOpen(){ 428 if (opened || !armed) return; 429 if (signedUp() || dismissedRecently()) return; 430 if (cookiebotUndecided()) { whenDecided(tryOpen); return; } 431 opened = true; 432 open(); 433 } 434 function arm(){ 435 if (armed) return; armed = true; 436 var mobile = coarse(), min = mobile ? MOBILE_MIN : DESKTOP_MIN; 437 var timeUp = false; 438 function attempt(){ if (timeUp && scrolledEnough()) tryOpen(); } 439 var wait = Math.max(0, min - (Date.now() - pageStart)); 440 setTimeout(function(){ timeUp = true; attempt(); }, wait); 441 window.addEventListener('scroll', attempt, { passive: true }); 442 if (!mobile) { 443 document.addEventListener('mouseout', function(e){ 444 if (opened) return; 445 var to = e.relatedTarget || e.toElement; 446 if (!to && e.clientY <= 0) tryOpen(); 447 }); 448 } 449 } 450 451 window.mpSignup = { open: function(){ opened = true; open(); }, close: close, appid: APPID }; 452 453 // The site's newsletter button opens the popup in place (Arthur, 2026-09-17: no separate newsletter page; 454 // the button simply calls the popup). 455 // Matches: links to /newsletter (any host), href="#signup", or any element with data-mp-signup. 456 document.addEventListener('click', function(e){ 457 var a = closest(e.target, 'a[href], [data-mp-signup]'); 458 if (!a) return; 459 var href = String(a.getAttribute('href') || ''); 460 var hit = a.hasAttribute('data-mp-signup') || href === '#signup' || /(^|\/\/[^\/]+)\/newsletter\/?(#.*)?$/.test(href); 461 if (!hit) return; 462 e.preventDefault(); opened = true; open(); // an explicit click always opens, even after a signup or a dismiss 463 }, true); 464 465 if (!eligible() || signedUp() || dismissedRecently()) return; 466 function boot(){ inject(); whenDecided(arm); } 467 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot(); 468})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.