Jump to content

MediaWiki:Common.js: Difference between revisions

From Doom Relics
No edit summary
Tag: Reverted
m Protected "MediaWiki:Common.js" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))
 
(4 intermediate revisions by the same user not shown)
Line 92: Line 92:


     const header = document.createElement('div');
     const header = document.createElement('div');
    header.className = HEADER_CLASS;
header.className = HEADER_CLASS;
    header.textContent = 'DOOM RELICS';
header.textContent = 'DOOM RELICS';
    styleHeader(header);
 
/* Make it clickable */
header.style.cursor = 'pointer';
header.addEventListener('click', () => {
  window.location.href = mw.util.getUrl('Main_Page');
});
 
styleHeader(header);
 


     // Insert header before the first list item / first link area
     // Insert header before the first list item / first link area
Line 127: Line 135:
   const FORCE_EVENT = false;  // <-- set to false after you confirm it works
   const FORCE_EVENT = false;  // <-- set to false after you confirm it works


  function cacoConditionMet() {
function cacoConditionMet() {
    if (FORCE_EVENT) return true;
  if (FORCE_EVENT) return true;
    return /\bcacodemon\b/i.test(document.body.innerText);
 
  }
  // MediaWiki page title (underscores instead of spaces)
  if (!cacoConditionMet()) return;
  const pageName = mw.config.get('wgPageName') || "";
 
  // Trigger ONLY if the page title contains "cacodemon"
  return /cacodemon/i.test(pageName);
}
 
if (!cacoConditionMet()) return;
 


   if (document.getElementById('doom-fx-layer')) return;
   if (document.getElementById('doom-fx-layer')) return;
Line 294: Line 309:
   }
   }
});
});
/* Doom Relics RADIO widget (drop anywhere with <span class="dr-radio"></span>) */
(function () {
  const PLAYLIST_ID = "PLbskP5RAOv8iZNrpqyc12gQzhnOfmbpdO";
  const STORAGE_KEY = "drRadioEnabled"; // remembers if user started it
  function injectStylesOnce() {
    if (document.getElementById("dr-radio-style")) return;
    const css = `
.dr-radioBar{
  display:inline-flex;
  align-items:center;
  gap:10px;
  padding:8px 10px;
  border-radius:12px;
  border:1px solid rgba(255,120,0,.32);
  background: linear-gradient(180deg, rgba(20,10,0,.68), rgba(0,0,0,.55));
  box-shadow: 0 0 14px rgba(255,120,0,.10), inset 0 0 18px rgba(255,120,0,.06);
  color:#fff;
  max-width:min(520px, 100%);
  font-family: inherit;
}
.dr-radioBadge{
  display:inline-flex;
  align-items:center;
  gap:6px;
  padding:6px 8px;
  border-radius:10px;
  background: rgba(0,0,0,.45);
  border:1px solid rgba(255,120,0,.22);
  text-transform:uppercase;
  letter-spacing:.10em;
  font-weight:800;
  font-size:12px;
  line-height:1;
}
.dr-radioBadge .dot{
  width:8px; height:8px; border-radius:50%;
  background: rgba(255,80,0,.95);
  box-shadow: 0 0 10px rgba(255,120,0,.65);
}
.dr-radioTitle{
  min-width:140px;
  max-width:300px;
  overflow:hidden;
  white-space:nowrap;
  text-overflow:ellipsis;
  opacity:.95;
  font-size:13px;
}
.dr-radioBtns{
  display:inline-flex;
  gap:6px;
}
.dr-radioBtn{
  cursor:pointer;
  padding:6px 9px;
  border-radius:10px;
  border:1px solid rgba(255,120,0,.28);
  background: rgba(10,10,10,.55);
  color:#fff;
  font-weight:800;
  font-size:12px;
  letter-spacing:.06em;
  text-transform:uppercase;
}
.dr-radioBtn:hover{
  background: rgba(25,25,25,.70);
}
.dr-radioBtn:disabled{
  opacity:.45;
  cursor:not-allowed;
}
#dr-ytHidden{
  width:1px; height:1px; overflow:hidden;
  position:absolute; left:-9999px; top:-9999px;
}`;
    const style = document.createElement("style");
    style.id = "dr-radio-style";
    style.textContent = css;
    document.head.appendChild(style);
  }
  function loadYouTubeAPI() {
    return new Promise((resolve) => {
      if (window.YT && window.YT.Player) return resolve();
      if (window.__drYTLoading) {
        const t = setInterval(() => {
          if (window.YT && window.YT.Player) { clearInterval(t); resolve(); }
        }, 100);
        return;
      }
      window.__drYTLoading = true;
      const tag = document.createElement("script");
      tag.src = "https://www.youtube.com/iframe_api";
      document.head.appendChild(tag);
      const prev = window.onYouTubeIframeAPIReady;
      window.onYouTubeIframeAPIReady = function () {
        if (typeof prev === "function") prev();
        resolve();
      };
    });
  }
  function ensureHiddenPlayerDiv() {
    if (document.getElementById("dr-ytHidden")) return;
    const div = document.createElement("div");
    div.id = "dr-ytHidden";
    document.body.appendChild(div);
  }
  // Single global player shared by all widgets
  let player = null;
  let playerReady = false;
  function setAllTitles(text) {
    document.querySelectorAll(".dr-radioTitle").forEach(el => el.textContent = text);
  }
  function enableAllControls(on) {
    document.querySelectorAll(".dr-radioBtn[data-action='replay'], .dr-radioBtn[data-action='next']")
      .forEach(btn => (btn.disabled = !on));
  }
  function initPlayerOnce() {
    if (player) return;
    player = new YT.Player("dr-ytHidden", {
      height: "1",
      width: "1",
      playerVars: {
        controls: 0,
        modestbranding: 1,
        rel: 0,
        listType: "playlist",
        list: PLAYLIST_ID,
        autoplay: 0
      },
      events: {
        onReady: () => {
          playerReady = true;
          setAllTitles("Ready");
          // If user previously started radio, don't autoplay (browsers hate that),
          // but keep it ready.
        },
        onStateChange: (e) => {
          if (e.data === YT.PlayerState.PLAYING || e.data === YT.PlayerState.BUFFERING) {
            const d = player.getVideoData && player.getVideoData();
            if (d && d.title) setAllTitles(d.title);
            enableAllControls(true);
          }
        }
      }
    });
  }
  function playFromUserGesture() {
    if (!player || !playerReady) return;
    try { localStorage.setItem(STORAGE_KEY, "1"); } catch (e) {}
    player.playVideo();
    setTimeout(() => {
      const d = player.getVideoData && player.getVideoData();
      if (d && d.title) setAllTitles(d.title);
      enableAllControls(true);
    }, 600);
  }
  function nextTrack() {
    if (!player || !playerReady) return;
    player.nextVideo();
    setTimeout(() => {
      const d = player.getVideoData && player.getVideoData();
      if (d && d.title) setAllTitles(d.title);
    }, 400);
  }
  function replayTrack() {
    if (!player || !playerReady) return;
    player.seekTo(0, true);
    player.playVideo();
  }
  function renderWidget(el) {
    // avoid double-render
    if (el.dataset.rendered === "1") return;
    el.dataset.rendered = "1";
    el.innerHTML = `
      <span class="dr-radioBar" role="group" aria-label="Doom Relics Radio">
        <span class="dr-radioBadge"><span class="dot"></span> RADIO</span>
        <span class="dr-radioTitle">Loading…</span>
        <span class="dr-radioBtns">
          <button class="dr-radioBtn" data-action="play"  type="button">Play</button>
          <button class="dr-radioBtn" data-action="replay" type="button" disabled>↻</button>
          <button class="dr-radioBtn" data-action="next"  type="button" disabled>Next</button>
        </span>
      </span>
    `;
    el.querySelectorAll(".dr-radioBtn").forEach(btn => {
      btn.addEventListener("click", () => {
        const a = btn.getAttribute("data-action");
        if (a === "play") playFromUserGesture();
        if (a === "next") nextTrack();
        if (a === "replay") replayTrack();
      });
    });
  }
  function boot() {
    injectStylesOnce();
    ensureHiddenPlayerDiv();
    // Render widgets anywhere you place <span class="dr-radio"></span>
    const widgets = document.querySelectorAll(".dr-radio");
    if (!widgets.length) return;
    widgets.forEach(renderWidget);
    loadYouTubeAPI().then(() => {
      initPlayerOnce();
    });
  }
  // MediaWiki friendly: run on load and after AJAX page changes
  function onReady(fn) {
    if (window.mw && mw.hook) {
      mw.hook("wikipage.content").add(() => fn());
    }
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", fn);
    } else {
      fn();
    }
  }
  onReady(boot);
})();

Latest revision as of 13:40, 26 December 2025

/* Any JavaScript here will be loaded for all users on every page load. */

// Vector 2022: pin main menu for logged-out users on desktop
if (!mw.config.get('wgUserName') && window.matchMedia('(min-width: 1120px)').matches) {
  const observer = new MutationObserver((_, obs) => {
    if (document.documentElement.classList.contains('vector-animations-ready')) {
      const pin = document.querySelector('[data-event-name="pinnable-header.vector-main-menu.pin"]');
      if (pin) pin.click();
      obs.disconnect();
    }
  });
  observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
}
// Remove "Main menu" headings/labels injected by Vector/Vector 2022 (works across variants)
(function () {
  function killMainMenuText(root = document) {
    // Look for common heading/label elements
    const candidates = root.querySelectorAll(
      '#vector-main-menu, #vector-main-menu-pinned-container, #mw-panel, nav.vector-main-menu, #vector-main-menu-dropdown'
    );

    candidates.forEach(container => {
      // Remove elements whose visible text is exactly "Main menu"
      container.querySelectorAll('*').forEach(el => {
        const txt = (el.textContent || '').trim();
        if (txt === 'Main menu') {
          el.style.display = 'none';
        }
        // Also catch the longer hint text variants
        if (txt.toLowerCase().includes('main menu has moved here')) {
          el.style.display = 'none';
        }
      });
    });
  }

  // Run now, on load, and after Vector finishes UI init
  killMainMenuText();
  window.addEventListener('load', () => killMainMenuText());

  const obs = new MutationObserver(() => killMainMenuText());
  obs.observe(document.documentElement, { childList: true, subtree: true });
})();
// DOOM ARCHIVES header: inject above your Doom Titles links (guaranteed)
(function () {
  const HEADER_CLASS = 'doom-archives-header';

  function styleHeader(header) {
    header.style.display = 'block';
    header.style.width = '100%';
    header.style.boxSizing = 'border-box';
    header.style.margin = '10px 0 10px';
    header.style.padding = '12px 14px';
    header.style.background = 'rgba(0,0,0,0.75)';
    header.style.border = '1px solid #3a0000';
    header.style.borderRadius = '12px';
    header.style.boxShadow = 'inset 0 0 20px rgba(180,0,0,0.30), 0 0 8px rgba(0,0,0,0.8)';
    header.style.color = '#f5c542';
    header.style.fontWeight = '900';
    header.style.letterSpacing = '0.22em';
    header.style.textTransform = 'uppercase';
    header.style.textAlign = 'center';
    header.style.fontFamily = "'Bebas Neue', Arial, sans-serif";
    header.style.textShadow = '0 0 3px rgba(245,197,66,0.35)';
    header.style.fontSize = '22px';
  }

  function findAnchor() {
    // Use a link you KNOW exists in your sidebar
    return (
      document.querySelector('a[href*="Category:Doom_(1993)"]') ||
      document.querySelector('a[href*="Category:Doom_II"]') ||
      document.querySelector('a[href*="Category:Doom_3"]') ||
      document.querySelector('a[href*="Category:Doom_Eternal"]')
    );
  }

  function ensureHeader() {
    const a = findAnchor();
    if (!a) return;

    // Find the menu content container that holds the link list
    const menuContent =
      a.closest('.vector-menu-content') ||
      a.closest('ul') ||
      a.parentElement;

    if (!menuContent) return;

    // Avoid duplicates
    if (menuContent.querySelector('.' + HEADER_CLASS)) return;

    const header = document.createElement('div');
header.className = HEADER_CLASS;
header.textContent = 'DOOM RELICS';

/* Make it clickable */
header.style.cursor = 'pointer';
header.addEventListener('click', () => {
  window.location.href = mw.util.getUrl('Main_Page');
});

styleHeader(header);


    // Insert header before the first list item / first link area
    menuContent.insertBefore(header, menuContent.firstChild);
  }

  function run() {
    ensureHeader();
    setTimeout(ensureHeader, 150);
    setTimeout(ensureHeader, 600);
    setTimeout(ensureHeader, 1200);
  }

  // Run now + keep alive if Vector re-renders
  run();
  window.addEventListener('load', run);

  const obs = new MutationObserver(() => ensureHeader());
  obs.observe(document.documentElement, { childList: true, subtree: true });
})();
/* ==================================================
   CACODEMON EVENT — NO-FLICKER + PLASMA
   Trigger: word "cacodemon" anywhere in page text
   ================================================== */

mw.hook('wikipage.content').add(function () {

  /* ====== QUICK TEST SWITCH ======
     true  = force demons on (debug)
     false = only when "cacodemon" text exists
  */
  const FORCE_EVENT = false;   // <-- set to false after you confirm it works

 function cacoConditionMet() {
  if (FORCE_EVENT) return true;

  // MediaWiki page title (underscores instead of spaces)
  const pageName = mw.config.get('wgPageName') || "";

  // Trigger ONLY if the page title contains "cacodemon"
  return /cacodemon/i.test(pageName);
}

if (!cacoConditionMet()) return;


  if (document.getElementById('doom-fx-layer')) return;

  const IMG_A = '/index.php/Special:FilePath/Cacodemon.png';
  const IMG_B = '/index.php/Special:FilePath/Cacodemon_alt.png';

  const BAND_TOP_MIN = 20;
  const BAND_TOP_MAX = 120;
  const DEMON_W = 120;
  const MOUTH = { x: 35, y: 60 };

  function rand(min, max) { return Math.random() * (max - min) + min; }
  function randi(min, max) { return Math.floor(rand(min, max + 1)); }

  function preload(url){
    return new Promise(resolve => {
      const img = new Image();
      img.onload = () => resolve(true);
      img.onerror = () => resolve(false);
      img.src = url;
    });
  }

  Promise.all([preload(IMG_A), preload(IMG_B)]).then(startEvent);

  function startEvent(){
    const layer = document.createElement('div');
    layer.id = 'doom-fx-layer';
    document.body.appendChild(layer);

    function makeDemon(i){
      const el = document.createElement('div');
      el.className = 'doom-demon';

      const frameA = document.createElement('div');
      frameA.className = 'doom-frame frame-a';
      frameA.style.backgroundImage = `url("${IMG_A}")`;

      const frameB = document.createElement('div');
      frameB.className = 'doom-frame frame-b';
      frameB.style.backgroundImage = `url("${IMG_B}")`;

      el.appendChild(frameA);
      el.appendChild(frameB);
      layer.appendChild(el);

      const d = {
        el,
        y: randi(BAND_TOP_MIN, BAND_TOP_MAX),
        x: rand(0, window.innerWidth),
        vx: -rand(14, 30),
        bobPhase: rand(0, Math.PI * 2),
        bobAmp: rand(12, 28),
        bobSpeed: rand(0.6, 1.1),

        pauseUntil: 0,
        lastPauseX: 0,
        minPauseTravel: rand(700, 1400),

        isAlt: false,
        swapAt: performance.now() + rand(1200, 5200),

        shootingBias: i === 0 ? 1.0 : 0.25,
      };

      d.lastPauseX = d.x;
      return d;
    }

    const demons = [makeDemon(0), makeDemon(1), makeDemon(2)];

    function spawnPlasma(d){
      const orb = document.createElement('div');
      orb.className = 'doom-plasma';
      layer.appendChild(orb);

      const startX = d.x + MOUTH.x;
      const startY = d.y + MOUTH.y + Math.sin(d.bobPhase) * d.bobAmp * 0.5;

      const speed = rand(160, 260);
      const lifeMs = rand(1600, 2400);
      const born = performance.now();

      function step(t){
        const age = t - born;
        const x = startX - speed * (age / 1000);
        const y = startY + Math.sin(age / 80) * 2;

        orb.style.left = x + 'px';
        orb.style.top = y + 'px';
        orb.style.opacity = 1 - Math.max(0, (age - lifeMs * 0.7) / (lifeMs * 0.3));

        if (age < lifeMs && x > -60) requestAnimationFrame(step);
        else orb.remove();
      }
      requestAnimationFrame(step);
    }

    let last = performance.now();
    let lastPlasmaTime = 0;
    const PLASMA_COOLDOWN = 12000; // 15s minimum
    const PLASMA_CHANCE = 0.0150;  // medium-rare

    function tick(now){
      const dt = (now - last) / 1000;
      last = now;
      const w = window.innerWidth;

      for (const d of demons){

        if (now > d.pauseUntil){
          const traveled = Math.abs(d.x - d.lastPauseX);
          if (traveled > d.minPauseTravel && Math.random() < 0.0025){
            d.pauseUntil = now + rand(900, 2200);
            d.lastPauseX = d.x;
            d.minPauseTravel = rand(700, 1400);
          }
        }

        if (now < d.pauseUntil){
          d.bobPhase += dt * d.bobSpeed;
        } else {
          d.x += d.vx * dt;
          d.bobPhase += dt * d.bobSpeed;

          if (d.x < -DEMON_W - 40){
            d.x = w + rand(80, 300);
            d.y = randi(BAND_TOP_MIN, BAND_TOP_MAX);
            d.vx = -rand(14, 30);
            d.bobAmp = rand(12, 28);
            d.bobSpeed = rand(0.6, 1.1);
            d.lastPauseX = d.x;
            d.minPauseTravel = rand(700, 1400);
          }
        }

        if (now >= d.swapAt){
          d.isAlt = !d.isAlt;
          d.el.classList.toggle('is-alt', d.isAlt);
          d.swapAt = now + rand(1200, 5200);
        }

        const bobY = Math.sin(d.bobPhase) * d.bobAmp;
        d.el.style.left = d.x + 'px';
        d.el.style.top = (d.y + bobY) + 'px';

        if (
          now > d.pauseUntil &&
          now - lastPlasmaTime > PLASMA_COOLDOWN &&
          Math.random() < PLASMA_CHANCE * d.shootingBias
        ){
          lastPlasmaTime = now;
          spawnPlasma(d);
        }
      }

      requestAnimationFrame(tick);
    }

    requestAnimationFrame(tick);
  }
});