MediaWiki:Common.js: Difference between revisions
Appearance
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 () { | ||
if (document.getElementById('doom-fx-layer')) return; | if (document.getElementById('doom-fx-layer')) return; | ||
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'; | ||
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 MOUTH = { x: 88, y: 58 }; | const MOUTH = { x: 88, y: 58 }; | ||
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; } | ||
function randi(min, max) { return Math.floor(rand(min, max + 1)); } | |||
function randi(min, max) { | |||
function makeDemon(i) { | function makeDemon(i) { | ||
const el = document.createElement('div'); | const el = document.createElement('div'); | ||
el.className = 'doom-demon'; | el.className = 'doom-demon'; | ||
const demon = { | const demon = { | ||
el, | el, | ||
y: randi(BAND_TOP_MIN, BAND_TOP_MAX | y: randi(BAND_TOP_MIN, BAND_TOP_MAX), | ||
x: rand(0, window.innerWidth), | x: rand(0, window.innerWidth), | ||
vx: -rand( | vx: -rand(14, 30), | ||
bobPhase: rand(0, Math.PI * 2), | bobPhase: rand(0, Math.PI * 2), | ||
bobAmp: rand( | bobAmp: rand(12, 28), | ||
bobSpeed: rand(0.6, 1. | bobSpeed: rand(0.6, 1.1), | ||
pauseUntil: 0, | pauseUntil: 0, | ||
swapAt: performance.now() + rand( | |||
/* 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, | shootingBias: i === 0 ? 1.0 : 0.25, | ||
}; | }; | ||
demon.lastPauseX = demon.x; | |||
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)]; | ||
function spawnPlasma(d) { | |||
function spawnPlasma( | |||
const orb = document.createElement('div'); | const orb = document.createElement('div'); | ||
orb.className = 'doom-plasma'; | orb.className = 'doom-plasma'; | ||
layer.appendChild(orb); | layer.appendChild(orb); | ||
const startX = d.x + MOUTH.x; | |||
const startX = | const startY = d.y + MOUTH.y + Math.sin(d.bobPhase) * d.bobAmp * 0.5; | ||
const startY = | |||
const speed = rand(160, 260); | |||
const speed = rand(160, 260); | |||
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 x = startX - speed * (age / 1000); | const x = startX - speed * (age / 1000); | ||
const y = startY + Math.sin | 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 = | orb.style.opacity = 1 - Math.max(0, (age - lifeMs * 0.7) / (lifeMs * 0.3)); | ||
if (age < lifeMs && x > - | if (age < lifeMs && x > -60) { | ||
requestAnimationFrame(step); | requestAnimationFrame(step); | ||
} else { | } else { | ||
| Line 216: | Line 203: | ||
} | } | ||
} | } | ||
requestAnimationFrame(step); | requestAnimationFrame(step); | ||
} | } | ||
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) { | ||
// | |||
if (now > d.pauseUntil && Math.random() < 0. | /* ===== 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) { | if (now < d.pauseUntil) { | ||
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 (d.x < -DEMON_W - 40) { | |||
if (d.x < -DEMON_W - | d.x = w + rand(80, 300); | ||
d.x = w + rand( | |||
d.y = randi(BAND_TOP_MIN, BAND_TOP_MAX); | d.y = randi(BAND_TOP_MIN, BAND_TOP_MAX); | ||
d.vx = -rand( | d.vx = -rand(14, 30); | ||
d.bobAmp = rand( | d.bobAmp = rand(12, 28); | ||
d.bobSpeed = rand(0.6, 1. | d.bobSpeed = rand(0.6, 1.1); | ||
d.lastPauseX = d.x; | |||
d.minPauseTravel = rand(700, 1400); | |||
} | } | ||
} | } | ||
if (now >= d.swapAt) { | if (now >= d.swapAt) { | ||
d.img = | 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( | d.swapAt = now + rand(1200, 5200); | ||
} | } | ||
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'; | ||
if ( | |||
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); | ||
}); | }); | ||
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);
});