PageSourceSearch

https://retrobowl2.me/w3-report.js?v=ef236abf

js retrobowl2.me collected 2026-09-24 15:05:55 UTC 8,517 bytes, 126 lines download raw bytes

1/* w3-report.js — "Report a problem" button for the fleet's static game sites.
2 *
3 * Self-contained (no CSS or framework needed): a small pill fixed bottom-left that
4 * opens a panel with the reasons the w3admin Game Reports queue understands and an
5 * optional note, and POSTs to https://w3admin.w3technic.com/api/report.php — the
6 * same endpoint the WordPress portals and the gamesite-core ReportGame component
7 * use. Rendered in a shadow root so the host page's CSS cannot touch it and vice
8 * versa. Injected by w3-agents/report-widget/inject.py; the gamesite-core sites
9 * have their own button in the player bar and are skipped by the injector.
10 *
11 * Navbar mode: if the page has an element with `data-w3-report-slot`, the button is
12 * mounted inside it as a plain nav link (inherits the bar's colour/font) and the
13 * panel drops down below it instead of floating bottom-left.
14 *
15 * Slug = last path segment of the page ("home" on /), game name = the page's H1
16 * (falling back to <title>); the page URL and the browser go along with it.
17 */
18(function () {
19  if (window.__w3Report || window.top !== window.self) return; // never inside a game frame
20  window.__w3Report = true;
21  var API = 'https://w3admin.w3technic.com/api/report.php';
22  var REASONS = [
23    ['not_loading', "Won't load"],
24    ['blank_screen', 'Black / blank screen'],
25    ['broken_controls', 'Controls do not work'],
26    ['wrong_game', 'Wrong game'],
27    ['slow', 'Too slow / laggy'],
28    ['other', 'Something else'],
29  ];
30  function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
31  function slug() {
32    var p = location.pathname.replace(/\/+$/, '').replace(/\.html?$/, '');
33    var s = p.split('/').filter(Boolean).pop() || 'home';
34    return s.toLowerCase().replace(/[^a-z0-9/-]+/g, '-').slice(0, 191) || 'home';
35  }
36  function gameName() {
37    var h = document.querySelector('h1');
38    var t = (h && h.textContent.trim()) || document.title || location.hostname;
39    return t.replace(/\s+/g, ' ').slice(0, 120);
40  }
41
42  var host = document.createElement('div');
43  host.id = 'w3-report';
44  host.setAttribute('data-w3-report', '1');
45  var root = host.attachShadow ? host.attachShadow({ mode: 'open' }) : host;
46  root.innerHTML =
47    '<style>' +
48    ':host{all:initial;position:fixed;left:14px;bottom:14px;z-index:2147483000;font:13px/1.35 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#111}' +
49    '@media(max-width:640px){:host{bottom:76px;left:10px}}' +
50    '@media print{:host{display:none}}' +
51    ':host(.inline){position:relative;left:auto;bottom:auto;display:inline-block;z-index:2147483000;font:inherit;color:inherit}' +
52    ':host(.inline) .pill{height:auto;padding:0;border-radius:0;background:none;box-shadow:none;backdrop-filter:none;color:inherit;font:inherit;opacity:1;gap:5px}' +
53    ':host(.inline) .pill:hover{text-decoration:underline}' +
54    ':host(.inline) .panel{bottom:auto;top:calc(100% + 12px);left:-8px}' +
55    '.pill{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px 0 10px;border:0;border-radius:999px;background:rgba(17,17,17,.78);color:#fff;font:600 12px/1 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,.25);backdrop-filter:blur(4px);opacity:.85}' +
56    '.pill:hover{opacity:1}.pill svg{flex:none}' +
57    '.panel{position:absolute;left:0;bottom:40px;width:min(300px,calc(100vw - 28px));padding:12px;border-radius:12px;background:#fff;color:#111;box-shadow:0 8px 30px rgba(0,0,0,.28);border:1px solid #e3e3e3;display:none}' +
58    '.panel.open{display:block}' +
59    '.h{margin:0 0 8px;font-weight:700;font-size:13px}' +
60    '.r{display:flex;flex-wrap:wrap;gap:6px}' +
61    '.r button{border:1px solid #d9d9d9;background:#f4f4f4;color:#444;border-radius:999px;padding:6px 10px;font:500 12px/1 inherit;cursor:pointer}' +
62    '.r button[aria-pressed=true]{background:#1d6ee0;border-color:#1d6ee0;color:#fff}' +
63    'textarea{display:block;width:100%;box-sizing:border-box;margin-top:8px;padding:6px 8px;border:1px solid #d9d9d9;border-radius:8px;font:13px/1.35 inherit;resize:none;color:#111;background:#fff}' +
64    '.f{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:8px}' +
65    '.f small{color:#666;font-size:11px}' +
66    '.send{flex:none;border:0;border-radius:8px;background:#1d6ee0;color:#fff;font:700 12px/1 inherit;padding:8px 12px;cursor:pointer}' +
67    '.send[disabled]{opacity:.6}' +
68    '.done p{margin:0 0 4px}.done .x{margin-top:8px;border:0;border-radius:8px;background:#eee;color:#111;font:600 12px/1 inherit;padding:7px 10px;cursor:pointer}' +
69    '</style>' +
70    '<button class="pill" type="button" aria-expanded="false" aria-label="Report a problem with this game" title="Report a problem">' +
71    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 22V4a1 1 0 0 1 1-1h11l-1 4 1 4H5"/></svg>
71' +
72    '<span>Report a problem</span></button>' +
73    '<div class="panel" role="dialog" aria-label="Report a problem"></div>';
74
75  var pill = root.querySelector('.pill'), panel = root.querySelector('.panel');
76  var reason = 'not_loading', note = '', state = 'idle';
77
78  function render() {
79    var name = gameName();
80    if (state === 'sent') {
81      panel.innerHTML = '<div class="done"><p><strong>Thanks — report sent.</strong></p><p style="color:#666">We check every report and fix or replace the game.</p><button class="x" type="button">Close</button></div>';
82      panel.querySelector('.x').onclick = close;
83      return;
84    }
85    var html = '<p class="h">What is wrong with ' + esc(name) + '?</p><div class="r">';
86    REASONS.forEach(function (r) {
87      html += '<button type="button" data-k="' + r[0] + '" aria-pressed="' + (reason === r[0]) + '">' + r[1] + '</button>';
88    });
89    html += '</div><textarea rows="2" maxlength="500" placeholder="Anything else? (optional)">' + esc(note) + '</textarea>' +
90      '<div class="f"><small>' + (state === 'error' ? 'Could not send — try again in a minute.' : 'Sends the page URL and your browser.') + '</small>' +
91      '<button class="send" type="button"' + (state === 'sending' ? ' disabled' : '') + '>' + (state === 'sending' ? 'Sending…' : 'Send report') + '</button></div>';
92    panel.innerHTML = html;
93    panel.querySelectorAll('.r button').forEach(function (b) {
94      b.onclick = function () { reason = b.getAttribute('data-k'); render(); };
95    });
96    panel.querySelector('textarea').oninput = function (e) { note = e.target.value; };
97    panel.querySelector('.send').onclick = send;
98  }
99  function open() { panel.classList.add('open'); pill.setAttribute('aria-expanded', 'true'); render(); }
100  function close() { panel.classList.remove('open'); pill.setAttribute('aria-expanded', 'false'); }
101  function send() {
102    if (state === 'sending') return;
103    state = 'sending'; render();
104    var body = {
105      slug: slug(), game_name: gameName(), source: location.hostname, page_url: location.href.slice(0, 500), reason: reason,
106      note: (note.trim() ? note.trim().slice(0, 500) + ' · ' : '') + 'UA: ' + navigator.userAgent.
106slice(0, 200),
107    };
108    fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
109      .then(function (r) { return r.json(); })
110      .then(function (d) { state = d && d.ok ? 'sent' : 'error'; render(); })
111      .catch(function () { state = 'error'; render(); });
112  }
113  pill.onclick = function () { panel.classList.contains('open') ? close() : open(); };
114  document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
115  document.addEventListener('mousedown', function (e) { if (panel.classList.contains('open') && e.target !== host) close(); });
116  if (document.querySelector('[data-w3-report-slot]')) pill.querySelector('span').textContent = 'Report';
117
118  var slotEl = document.querySelector('[data-w3-report-slot]');
119  if (slotEl) {
120    host.classList.add('inline'); slotEl.appendChild(host);
121    // nav items usually carry a small z-index (Cookie Clicker: #topBar>div = 1), which
122    // would trap the drop-down under the page; lift the slot itself.
123    if (!slotEl.style.zIndex) { slotEl.style.zIndex = '2147483000'; if (getComputedStyle(slotEl).position === 'static') slotEl.style.position = 'relative'; }
124  }
125  else (document.body || document.documentElement).appendChild(host);
126})();

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.