PageSourceSearch

https://www.microprose.com/play/t.js

js microprose.com collected 2026-10-02 06:21:09 UTC 39,179 bytes, 468 lines download raw bytes

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\">&#10003;</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.