Jump to content

MediaWiki:Common.js: Difference between revisions

From Doom Relics
No edit summary
No edit summary
Line 120: Line 120:
   - Shoots plasma from demon mouth (real position)
   - Shoots plasma from demon mouth (real position)
*/
*/
/* Doom Relics — Floating Cacodemons w/ Plasma
  UPDATED: Demons must travel FAR before pausing
*/
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {
  // Prevent double-inject if content hook fires more than once
   if (document.getElementById('doom-fx-layer')) return;
   if (document.getElementById('doom-fx-layer')) return;


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


  // Top band settings
   const BAND_TOP_MIN = 20;
   const BAND_TOP_MIN = 20;
   const BAND_TOP_MAX = 120;
   const BAND_TOP_MAX = 120;
   const DEMON_W = 120;
   const DEMON_W = 120;
  const DEMON_H = 120;


  // Plasma mouth offset (tweak if you want)
  // (x,y) relative to demon top-left
   const MOUTH = { x: 88, y: 58 };
   const MOUTH = { x: 88, y: 58 };


  // --- Create layer ---
   const layer = document.createElement('div');
   const layer = document.createElement('div');
   layer.id = 'doom-fx-layer';
   layer.id = 'doom-fx-layer';
   document.body.appendChild(layer);
   document.body.appendChild(layer);


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


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


    // initial state
     const demon = {
     const demon = {
       el,
       el,
       y: randi(BAND_TOP_MIN, BAND_TOP_MAX) + (i * 6), // slightly different heights
       y: randi(BAND_TOP_MIN, BAND_TOP_MAX),
       x: rand(0, window.innerWidth),
       x: rand(0, window.innerWidth),
       vx: -rand(10, 28), // px/sec leftward
       vx: -rand(14, 30),
       bobPhase: rand(0, Math.PI * 2),
       bobPhase: rand(0, Math.PI * 2),
       bobAmp: rand(10, 26),
       bobAmp: rand(12, 28),
       bobSpeed: rand(0.6, 1.2), // radians/sec-ish feel
       bobSpeed: rand(0.6, 1.1),
 
       pauseUntil: 0,
       pauseUntil: 0,
       swapAt: performance.now() + rand(900, 3200),
 
      /* NEW — travel gating */
      lastPauseX: 0,
      minPauseTravel: rand(600, 1200),
 
       swapAt: performance.now() + rand(1200, 5200),
       img: IMG_A,
       img: IMG_A,
       shootingBias: i === 0 ? 1.0 : 0.25, // demon 0 shoots more often
       shootingBias: i === 0 ? 1.0 : 0.25,
     };
     };


     el.style.top = demon.y + 'px';
     demon.lastPauseX = demon.x;
    el.style.left = demon.x + 'px';
    el.style.backgroundImage = `url("${demon.img}")`;


    el.style.backgroundImage = `url("${IMG_A}")`;
     layer.appendChild(el);
     layer.appendChild(el);
     return demon;
     return demon;
Line 183: Line 176:
   const demons = [makeDemon(0), makeDemon(1), makeDemon(2)];
   const demons = [makeDemon(0), makeDemon(1), makeDemon(2)];


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


    // Start at demon mouth (actual position)
     const startX = d.x + MOUTH.x;
     const startX = fromDemon.x + MOUTH.x;
     const startY = d.y + MOUTH.y + Math.sin(d.bobPhase) * d.bobAmp * 0.5;
     const startY = fromDemon.y + MOUTH.y + Math.sin(fromDemon.bobPhase) * fromDemon.bobAmp * 0.5;


    // Shoot left
     const speed = rand(160, 260);
     const speed = rand(160, 260); // px/sec
     const lifeMs = rand(1600, 2400);
     const lifeMs = rand(1600, 2400);
     const born = performance.now();
     const born = performance.now();
Line 200: Line 190:
     function step(t) {
     function step(t) {
       const age = t - born;
       const age = t - born;
      const p = age / lifeMs;
      // Travel left + slight wobble
       const x = startX - speed * (age / 1000);
       const x = startX - speed * (age / 1000);
       const y = startY + Math.sin((age / 1000) * 18) * 2;
       const y = startY + Math.sin(age / 80) * 2;


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


       if (age < lifeMs && x > -80) {
       if (age < lifeMs && x > -60) {
         requestAnimationFrame(step);
         requestAnimationFrame(step);
       } else {
       } else {
Line 216: Line 203:
       }
       }
     }
     }
     requestAnimationFrame(step);
     requestAnimationFrame(step);
   }
   }


  // Main animation loop
   let last = performance.now();
   let last = performance.now();
  let lastPlasmaTime = 0;
  const PLASMA_COOLDOWN = 60000; // 1 minute


   function tick(now) {
   function tick(now) {
Line 230: Line 217:


     for (const d of demons) {
     for (const d of demons) {
       // Random pause behavior
 
       if (now > d.pauseUntil && Math.random() < 0.004) {
       /* ===== PAUSE LOGIC (TRAVEL FIRST) ===== */
        d.pauseUntil = now + rand(900, 2800); // pause 0.9–2.8s
       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);
        }
       }
       }


      // Move unless paused
       if (now < d.pauseUntil) {
       if (now < d.pauseUntil) {
        // still bob a little while paused
         d.bobPhase += dt * d.bobSpeed;
         d.bobPhase += dt * d.bobSpeed;
       } else {
       } else {
Line 243: Line 235:
         d.bobPhase += dt * d.bobSpeed;
         d.bobPhase += dt * d.bobSpeed;


        // If fully offscreen left, respawn right with new height + speed
         if (d.x < -DEMON_W - 40) {
         if (d.x < -DEMON_W - 50) {
           d.x = w + rand(80, 300);
           d.x = w + rand(40, 260);
           d.y = randi(BAND_TOP_MIN, BAND_TOP_MAX);
           d.y = randi(BAND_TOP_MIN, BAND_TOP_MAX);
           d.vx = -rand(10, 28);
           d.vx = -rand(14, 30);
           d.bobAmp = rand(10, 26);
           d.bobAmp = rand(12, 28);
           d.bobSpeed = rand(0.6, 1.2);
           d.bobSpeed = rand(0.6, 1.1);
          d.lastPauseX = d.x;
          d.minPauseTravel = rand(700, 1400);
         }
         }
       }
       }


      // Sprite swap (A/B)
       if (now >= d.swapAt) {
       if (now >= d.swapAt) {
         d.img = (d.img === IMG_A) ? IMG_B : IMG_A;
         d.img = d.img === IMG_A ? IMG_B : IMG_A;
         d.el.style.backgroundImage = `url("${d.img}")`;
         d.el.style.backgroundImage = `url("${d.img}")`;
         d.swapAt = now + rand(700, 4200); // irregular
         d.swapAt = now + rand(1200, 5200);
       }
       }


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


       // Shoot plasma occasionally (biased to demon 0)
       if (
      if (now > d.pauseUntil && Math.random() < 0.00035 * d.shootingBias) {
        now > d.pauseUntil &&
        now - lastPlasmaTime > PLASMA_COOLDOWN &&
        Math.random() < 0.0002 * d.shootingBias
      ) {
        lastPlasmaTime = now;
         spawnPlasma(d);
         spawnPlasma(d);
       }
       }
Line 275: Line 270:


   requestAnimationFrame(tick);
   requestAnimationFrame(tick);
  // Keep things responsive
  window.addEventListener('resize', () => {
    // nothing required; positions will self-correct
  });
});
});

Revision as of 23:06, 25 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';
    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 });
})();
/* Doom Relics: Floating Cacodemons + real plasma shots
   - Spawns 3 demons in top band
   - Random-ish drift, bob, pause
   - Swaps between 2 images
   - Shoots plasma from demon mouth (real position)
*/
/* Doom Relics — Floating Cacodemons w/ Plasma
   UPDATED: Demons must travel FAR before pausing
*/

mw.hook('wikipage.content').add(function () {
  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: 88, y: 58 };

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

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

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

    const demon = {
      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,

      /* NEW — travel gating */
      lastPauseX: 0,
      minPauseTravel: rand(600, 1200),

      swapAt: performance.now() + rand(1200, 5200),
      img: IMG_A,
      shootingBias: i === 0 ? 1.0 : 0.25,
    };

    demon.lastPauseX = demon.x;

    el.style.backgroundImage = `url("${IMG_A}")`;
    layer.appendChild(el);
    return demon;
  }

  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 = 60000; // 1 minute

  function tick(now) {
    const dt = (now - last) / 1000;
    last = now;

    const w = window.innerWidth;

    for (const d of demons) {

      /* ===== PAUSE LOGIC (TRAVEL FIRST) ===== */
      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.img = d.img === IMG_A ? IMG_B : IMG_A;
        d.el.style.backgroundImage = `url("${d.img}")`;
        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() < 0.0002 * d.shootingBias
      ) {
        lastPlasmaTime = now;
        spawnPlasma(d);
      }
    }

    requestAnimationFrame(tick);
  }

  requestAnimationFrame(tick);
});