MediaWiki:Common.js: Difference between revisions
Appearance
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 534: | Line 534: | ||
onReady(boot); | onReady(boot); | ||
})(); | })(); | ||
/* default widget */ | |||
.dr-radioBar{ | |||
display:flex; /* was inline-flex */ | |||
width:100%; | |||
box-sizing:border-box; | |||
max-width:520px; /* keeps it compact in content areas */ | |||
} | |||
/* sidebar variant */ | |||
.dr-radio--sidebar .dr-radioBar{ | |||
max-width:100%; | |||
width:100%; | |||
border-radius:10px; | |||
padding:8px; | |||
} | |||
/* title should not force overflow in a narrow sidebar */ | |||
.dr-radio--sidebar .dr-radioTitle{ | |||
max-width:140px; | |||
} | |||
Revision as of 01:26, 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';
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;
return /\bcacodemon\b/i.test(document.body.innerText);
}
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);
}
});
/* 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);
})();
/* default widget */
.dr-radioBar{
display:flex; /* was inline-flex */
width:100%;
box-sizing:border-box;
max-width:520px; /* keeps it compact in content areas */
}
/* sidebar variant */
.dr-radio--sidebar .dr-radioBar{
max-width:100%;
width:100%;
border-radius:10px;
padding:8px;
}
/* title should not force overflow in a narrow sidebar */
.dr-radio--sidebar .dr-radioTitle{
max-width:140px;
}