Replace s4.zstatic.net includes (MWICombatSimulatorTest)

Remove includes from https://s4.zstatic.net/ and inject working CDN Bootstrap + i18next libs.

// ==UserScript==
// @name         Replace s4.zstatic.net includes (MWICombatSimulatorTest)
// @namespace    https://shykai.github.io/
// @version      1.0
// @description  Remove includes from https://s4.zstatic.net/ and inject working CDN Bootstrap + i18next libs.
// @match        https://shykai.github.io/MWICombatSimulatorTest/dist/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  const resources = [
    { type: 'link', href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css',
      attrs: { rel: 'stylesheet', integrity: 'sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==', crossorigin: 'anonymous', referrerpolicy: 'no-referrer' } },
    { type: 'script', src: 'https://cdnjs.cloudflare.com/ajax/libs/i18next/21.6.10/i18next.min.js',
      attrs: { integrity: 'sha512-4WlJJ18bTOvB8ask0iufUvS/N3NDs2NJUKXGeK9CGoFayxUASLGsiBAvMtO+dfdp+MNzaOZLStxIwc2rZz5hSg==', crossorigin: 'anonymous', referrerpolicy: 'no-referrer' } },
    { type: 'script', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery-i18next/1.2.1/jquery-i18next.min.js',
      attrs: { integrity: 'sha512-79RgNpOyaf8AvNEUdanuk1x6g53UPoB6Fh2uogMkOMGADBG6B0DCzxc+dDktXkVPg2rlxGvPeAFKoZxTycVooQ==', crossorigin: 'anonymous', referrerpolicy: 'no-referrer' } },
    { type: 'script', src: 'https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/6.1.3/i18nextBrowserLanguageDetector.min.js',
      attrs: { integrity: 'sha512-w/1n/omCK6/QXznOMYrp+arhVcWM87ioNqNkpWyFYhDFBgLNiwUG9Omp/YGPwy5Q/p7lktUI1eXZX3nN/TDDAA==', crossorigin: 'anonymous', referrerpolicy: 'no-referrer' } },
    { type: 'script', src: 'https://cdnjs.cloudflare.com/ajax/libs/i18next-http-backend/1.3.2/i18nextHttpBackend.min.js',
      attrs: { integrity: 'sha512-6KSl9S4uqyiqhlImZ5pcEQKCU7Mk/nyr+nJQAZe9ZY9en6cUmQKG/b3v1Db/1fOiV5zdOjjwWQ/S90p7eDLxgA==', crossorigin: 'anonymous', referrerpolicy: 'no-referrer' } }
  ];

  const brokenPrefix = 'https://s4.zstatic.net/';

  function removeBroken(el) {
    if (!el || !el.getAttribute) return;
    const href = el.getAttribute('href') || el.getAttribute('src');
    if (!href) return;
    if (href.startsWith(brokenPrefix)) el.remove();
  }

  function createNode(res) {
    if (res.type === 'link') {
      const l = document.createElement('link');
      for (const k of Object.keys(res.attrs || {})) l.setAttribute(k, res.attrs[k]);
      l.href = res.href;
      return l;
    } else {
      const s = document.createElement('script');
      for (const k of Object.keys(res.attrs || {})) s.setAttribute(k, res.attrs[k]);
      s.src = res.src;
      s.async = false;
      return s;
    }
  }

  function injectSequential() {
    return resources.reduce((p, r) => p.then(() => new Promise((resolve) => {
      const node = createNode(r);
      node.addEventListener('load', resolve);
      node.addEventListener('error', resolve);
      document.head.appendChild(node);
      // fallback resolve in case load doesn't fire
      setTimeout(resolve, 1200);
    })), Promise.resolve());
  }

  // initial cleanup + inject when head exists
  function start() {
    document.querySelectorAll('link, script').forEach(removeBroken);
    injectSequential();
    // observe for dynamic re-insertions of broken includes
    const mo = new MutationObserver((mutations) => {
      for (const m of mutations) {
        m.addedNodes.forEach((n) => {
          if (n.nodeType === 1 && (n.tagName === 'SCRIPT' || n.tagName === 'LINK')) removeBroken(n);
        });
      }
    });
    mo.observe(document.head || document.documentElement, { childList: true, subtree: true });
  }

  if (document.head) start();
  else new MutationObserver((o, obs) => { if (document.head) { obs.disconnect(); start(); } }).observe(document.documentElement, { childList: true });
})();