MediaWiki:Gadget-ShroomScript.js

From the Super Mario Wiki, the Mario encyclopedia
Jump to navigationJump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* General-purpose JavaScript used for The 'Shroom */

const ShroomUtil = {
	inAndOut: function inAndOut(t) {
		return 3 * t * t - 2 * t * t * t;
	},
	// https://stackoverflow.com/a/36481059
	gaussianRandom: function gaussianRandom(mean, stdev) {
		const u = 1 - Math.random();
		const v = Math.random();
		const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
		return z * (stdev || 1) + (mean || 0);
	},
};

/*
 Creates a card flipping animation.
 */
var cards = document.getElementsByClassName('shroomCardInner');
for (var cardIdx = 0; cardIdx < cards.length; cardIdx++) {
	(function (index) {
		cards[index].addEventListener('click', function () {
			cards[index].classList.toggle('active');
		});
	})(cardIdx);
}
// END OF CARD FLIP SEGMENT

/*
 * Slideshow effect, but you can put whatever you want instead of just an image.
 */
function plusSlides(n, slideShow) {
	changeSlides((slideShow.dataset.slideidx = parseInt(slideShow.dataset.slideidx) + n), slideShow);
}

function changeSlides(n, slideShow) {
	var slides = slideShow.querySelectorAll('.shroomSlideshowSlide');
	var tabs = slideShow.querySelectorAll('.shroomSlideshowTab');
	var indexText = slideShow.querySelector('.shroomSlideshowIndex');
	if (n > slides.length) {
		slideShow.dataset.slideidx = 1;
	}
	if (n < 1) {
		slideShow.dataset.slideidx = slides.length;
	}

	for (i = 0; i < slides.length; i++) {
		slides[i].style.display = 'none';
	}

	for (i = 0; i < tabs.length; i++) {
		tabs[i].classList.remove('active');
	}

	slides[slideShow.dataset.slideidx - 1].style.display = 'block';

	tabs[slideShow.dataset.slideidx - 1].classList.add('active');
	indexText.innerHTML = slideShow.dataset.slideidx + ' / ' + slides.length;
}

var slideShows = document.getElementsByClassName('shroomSlideshow');
for (var slideshowIdx = 0; slideshowIdx < slideShows.length; slideshowIdx++) {
	changeSlides(slideShows[slideshowIdx].dataset.slideidx, slideShows[slideshowIdx]);
}
var leftArrows = document.getElementsByClassName('shroomSlideshowLeft');
for (var leftIdx = 0; leftIdx < leftArrows.length; leftIdx++) {
	leftArrows[leftIdx].addEventListener('click', function () {
		var slideShow = this.closest('.shroomSlideshow');
		plusSlides(-1, slideShow);
	});
}
var rightArrows = document.getElementsByClassName('shroomSlideshowRight');
for (var rightIdx = 0; rightIdx < rightArrows.length; rightIdx++) {
	rightArrows[rightIdx].addEventListener('click', function () {
		var slideShow = this.closest('.shroomSlideshow');
		plusSlides(1, slideShow);
	});
}
var tabs = document.getElementsByClassName('shroomSlideshowTab');
for (var tabsIdx = 0; tabsIdx < tabs.length; tabsIdx++) {
	tabs[tabsIdx].addEventListener('click', function () {
		var slideShow = this.closest('.shroomSlideshow');
		var index = parseInt(this.dataset.index);
		changeSlides(slideShow.dataset.slideidx = index, slideShow);
	});
}
// END OF SLIDESHOW EFFECT

/*
 * Archives
 */

var showAll = document.getElementById('shroomArchiveCollapseAll');
if (showAll) {
	showAll.addEventListener('click', function () {
		var text = showAll.innerText;
		showAll.innerText = text === 'Expand all' ? 'Collapse all' : 'Expand all';
		document.querySelectorAll('.shroom-collapsible.mw-collapsible').forEach(function (item) {
			var expandText = item.dataset.expandtext, collapseText = item.dataset.collapsetext;
			var toggle = item.querySelector('a.mw-collapsible-text');
			if ((text === 'Expand all' && toggle.innerText === expandText) ||
				(text === 'Collapse all' && toggle.innerText === collapseText)) {
				toggle.click();
			}
		});
	});
}

/*
 * Special Issue 195: dynamic curtain
 */

function ShroomCurtain(container) {
	this.container = container;
	this.canvas = document.createElement('canvas');
	this.button = document.createElement('button');
	this.ctx = this.canvas.getContext('2d');
	this.raf = this.renderCurtains.bind(this);
	this.oc = this.openCurtain.bind(this);
	this.canvas.className = 'shroom195curtain-canvas';
	this.button.className = 'shroom195curtain-button';
	this.button.textContent = 'Open the curtains';
	var ct = localStorage.getItem('shroom_195curtain');
	var execute = ct ? parseInt(ct) + 30 * 60 * 1000 < Date.now() : true;
	if (execute) {
		this.container.insertBefore(this.button, this.container.firstChild);
		this.container.insertBefore(this.canvas, this.container.firstChild);
		requestAnimationFrame(this.raf);
		this.button.addEventListener('click', this.oc);
		window.addEventListener('resize', this.setCanvasSize.bind(this));
		this.setCanvasSize();
	}
	var placeholder = document.querySelector('.shroom195curtain-placeholder');
	placeholder.classList.add('exit');
	setTimeout(function () {
		return placeholder.parentNode.removeChild(placeholder);
	}, 1000);
}

ShroomCurtain.prototype.openCurtain = function openCurtain() {
	var _this = this;
	this.opening = Date.now();
	this.button.removeEventListener('click', this.oc);
	this.button.classList.add('exit');
	setTimeout(function () {
		return _this.button.parentNode.removeChild(_this.button);
	}, 1000);
	localStorage.setItem('shroom_195curtain', Date.now().toString());
};
ShroomCurtain.prototype.setCanvasSize = function setCanvasSize() {
	var containerSize = this.container.getBoundingClientRect();
	this.canvas.width = Math.round(containerSize.width * window.devicePixelRatio);
	this.canvas.height = Math.round(containerSize.height * window.devicePixelRatio);
	this.canvas.style.width = ''.concat(containerSize.width, 'px');
	this.canvas.style.height = ''.concat(containerSize.height, 'px');
};
ShroomCurtain.prototype.renderCurtains = function renderCurtains(timestamp) {
	this.ctx.resetTransform();
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	var openingDuration = this.opening ? Date.now() - this.opening : 0;
	var openingProgress = openingDuration / (this.canvas.width / window.devicePixelRatio * 4 + 1000);
	this.ctx.fillStyle = 'rgba(0,0,0,'.concat(1 - ShroomUtil.inAndOut(Math.min(openingProgress, 1)), ')');
	this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
	this.renderCurtain(timestamp, true, openingDuration);
	this.renderCurtain(timestamp, false, openingDuration);
	if (openingProgress <= 1) requestAnimationFrame(this.raf); else this.container.removeChild(this.canvas);
};
ShroomCurtain.prototype.renderCurtain = function renderCurtain(timestamp, left, openingDuration) {
	var m = left ? -1 : 1;
	var neededFolds = Math.max(Math.round(this.canvas.width / window.devicePixelRatio / 90), 2);
	var foldWidth = this.canvas.width / neededFolds / 2;
	var curtainOffset = Math.pow(Math.min(openingDuration / (this.canvas.width / window.devicePixelRatio * 4 + 1000), 1), 2) * this.canvas.width / 2;
	var curtainSkewOffset = (ShroomUtil.inAndOut(Math.min(openingDuration / 3000, 1)) * m * -150 + Math.sin(timestamp / 1000) * (foldWidth / 6)) * window.devicePixelRatio;
	var gr = this.ctx.createLinearGradient(left ? this.canvas.width : 0, 0, this.canvas.width / 2, 0);
	gr.addColorStop(0, '#900');
	gr.addColorStop(.95, '#c00');
	gr.addColorStop(1, '#a00');
	this.ctx.fillStyle = gr;
	this.ctx.save();
	this.ctx.beginPath();
	this.ctx.moveTo(left ? this.canvas.width : 0, 0);
	this.ctx.lineTo(this.canvas.width / 2 + m * (1 - curtainOffset), 0);
	this.ctx.lineTo(this.canvas.width / 2 + curtainSkewOffset + m * (1 - curtainOffset), this.canvas.height);
	this.ctx.lineTo(left ? this.canvas.width : 0, this.canvas.height);
	this.ctx.closePath();
	this.ctx.fill();
	this.ctx.clip();
	for (var i = 0; i < neededFolds; i++) {
		var foldX = left ? this.canvas.width - foldWidth * (i + 1) : foldWidth * i;
		var slideOffset = curtainOffset / ((neededFolds - i - 1) / (neededFolds - 1) + 1) * m;
		var subtractSway = -Math.tan(curtainSkewOffset / this.canvas.height) * (i / neededFolds);
		var foldGr = this.ctx.createLinearGradient(foldX - slideOffset, 0, foldX + foldWidth - slideOffset, 0);
		for (var grI = 0; grI <= 1; grI += .1)
			// cos((x - .5) * PI * 2) + 1
			foldGr.addColorStop(grI, 'rgba(0,0,0,'.concat((Math.cos((grI - .5) * Math.PI * 2) + 1) * .1, ')'));
		this.ctx.setTransform(1, 0, Math.tan(foldWidth / 3 / this.canvas.height) * Math.cos(timestamp / (1000 + Math.cos(foldX) * 100) + Math.cos(foldX * 1.2)) - subtractSway, 1, 0, 0);
		this.ctx.fillStyle = foldGr;
		this.ctx.fillRect(foldX - slideOffset, 0, foldWidth, this.canvas.height);
	}
	this.ctx.restore();
};
var curtainContainer = document.querySelector('.shroom195curtain');
if (curtainContainer) {
	new ShroomCurtain(curtainContainer);
}

/*
 * Special Issue 200
 */

// Fireworks
function ShroomFireworks(container, debug) {
	this.debug = debug || false;
	const _this = this;
	this.varOff = 0.08;
	this.gravity = 0.6;
	this.radius = 1.5;
	this.particles = [];
	this.container = container;
	this.drawCanvas = document.createElement('canvas');
	this.drawCtx = this.drawCanvas.getContext('2d');
	this.setWorker();
	container.querySelectorAll('.fireworks-images img').forEach(function (img) {
		img.crossOrigin = 'anonymous';
		if (img.complete) _this.addSpritesheet(img);
		else img.onload = function () {
			_this.addSpritesheet(img);
		};
	});
	container.append(this.drawCanvas);
	this.setCanvasConfig();
	const observer = new ResizeObserver(this.setCanvasConfig.bind(this));
	observer.observe(container);
	requestAnimationFrame(this.render.bind(this));
}

ShroomFireworks.prototype.setCanvasConfig = function setCanvasConfig() {
	const width = this.container.clientWidth, height = Math.min(this.container.clientHeight, window.innerHeight);
	const f = width < 640 || height < 640 ? 1.5 : 1;
	this.width = width * f;
	this.height = height * f;
	this.drawCanvas.width = width * window.devicePixelRatio;
	this.drawCanvas.height = height * window.devicePixelRatio;
	this.drawCanvas.style.width = width + 'px';
	this.drawCanvas.style.height = height + 'px';
	this.drawCtx.resetTransform();
	this.drawCtx.scale(window.devicePixelRatio / f, window.devicePixelRatio / f);
	this.drawCtx.globalCompositeOperation = 'lighter';
};
ShroomFireworks.prototype.setWorker = function setWorker() {
	this.worker = new Worker(URL.createObjectURL(new Blob([
		'const images = [];',
		'const calcCanvas = new OffscreenCanvas(256,256);',
		'const calcCtx = calcCanvas.getContext("2d", {willReadFrequently: true});',
		'self.onmessage = async(event)=>{',
		'  if (event.data.ib) {',
		'    const ib = event.data.ib;',
		'    const sprites = Math.floor(ib.width / ib.height);',
		'    for (let i = 0; i < sprites; i++) {',
		'      const sprite = await createImageBitmap(ib, ib.height * i, 0, ib.height, ib.height);',
		'      images.push(sprite);',
		'    }',
		'  }',
		'  if (event.data.rf) {',
		'    calculate();',
		'  }',
		'};',
		'function calculate() {',
		'  if (!images.length)',
		'    return;',
		'  const index = images.length > 1 ? Math.floor(Math.random() * (images.length - 1)) : 0;',
		'  const img = images[index];',
		'  images.splice(index, 1);',
		'  images.push(img);',
		'  calcCtx.clearRect(0, 0, calcCanvas.width, calcCanvas.height);',
		'  calcCtx.drawImage(img, 0, 0, calcCanvas.width, calcCanvas.height);',
		'  const calculatedParticles = [];',
		'  calculatedParticles.push({',
		'    ttl: 2e3,',
		'    speed: calcCanvas.width / 2,',
		'    img',
		'  });',
		'  for (let i = 0; i < 5e3; i++) {',
		'    const x = Math.floor(Math.random() * calcCanvas.width);',
		'    const y = Math.floor(Math.random() * calcCanvas.height);',
		'    const data = calcCtx.getImageData(x, y, 1, 1);',
		'    if (data.data[3] < 30)',
		'      continue;',
		'    calculatedParticles.push({',
		'      r: data.data[0],',
		'      g: data.data[1],',
		'      b: data.data[2],',
		'      speedX: x - calcCanvas.width / 2,',
		'      speedY: y - calcCanvas.width / 2,',
		'      ttl: gaussianRandom(3e3, 500)',
		'    });',
		'    if (calculatedParticles.length >= 400)',
		'      break;',
		'  }',
		'  self.postMessage({',
		'    f: calculatedParticles',
		'  });',
		'}',
		ShroomUtil.gaussianRandom.toString(),
	])));
	const _this = this;
	this.worker.onmessage = function (event) {
		if (event.data.f) _this.summonFireworks(event.data.f);
	};
};
ShroomFireworks.prototype.addSpritesheet = function addSpritesheet(img) {
	const _this = this;
	createImageBitmap(img).then(function (ib) {
		_this.worker.postMessage({ib: ib});
	});
};
ShroomFireworks.prototype.spawnFireworks = function spawnFireworks() {
	clearTimeout(this.spawnTimeout);
	this.spawnTimeout = setTimeout(this.spawnFireworks.bind(this), Math.min(ShroomUtil.gaussianRandom(1500, 1e3), 4e3));
	if (this.particles.length >= 300) return;
	this.worker.postMessage({rf: true});
};
ShroomFireworks.prototype.summonFireworks = function summonFireworks(calculatedParticles) {
	if (this.particles.length >= 300) return;
	const spawnX = ShroomUtil.gaussianRandom(this.width / 2, this.width / 4);
	const spawnY = ShroomUtil.gaussianRandom(this.height / 2, this.height / 6);
	const speedFactor = ShroomUtil.gaussianRandom(0.8, 0.2);
	const _this = this;
	calculatedParticles.forEach(function (cp) {
		'speed' in cp ? _this.particles.push({
			x: spawnX,
			y: spawnY,
			speed: cp.speed * speedFactor,
			addY: _this.gravity,
			ttl: cp.ttl,
			img: cp.img,
			birth: performance.now(),
			scale: 0,
		}) : _this.particles.push({
			r: cp.r,
			g: cp.g,
			b: cp.b,
			x: spawnX,
			y: spawnY,
			speedX: cp.speedX * speedFactor,
			speedY: cp.speedY * speedFactor,
			addX: Math.random() * _this.varOff - _this.varOff / 2,
			addY: Math.random() * _this.varOff - _this.varOff / 2 + _this.gravity,
			ttl: cp.ttl,
			birth: performance.now(),
		});
	});
};
ShroomFireworks.prototype.stopSpawnFireworks = function stopSpawnFireworks() {
	clearTimeout(this.spawnTimeout);
};
ShroomFireworks.prototype.render = function render(ts) {
	requestAnimationFrame(this.render.bind(this));
	const start = this.debug && performance.now();
	this.drawCtx.clearRect(0, 0, this.width, this.height);
	for (var i = 0; i < this.particles.length; i++) {
		const p = this.particles[i];
		const tl = -ts + p.birth + p.ttl;
		if (tl <= 0) {
			this.particles.splice(i, 1);
			--i;
			continue;
		}
		if ('img' in p) {
			this.drawCtx.globalAlpha = tl > 2000 ? 0.7 : ShroomUtil.inAndOut(tl / 2000) * 0.7;
			this.drawCtx.drawImage(p.img, p.x - p.scale / 2, p.y - p.scale / 2, p.scale, p.scale);
			p.y += p.addY;
			p.scale += p.speed / 7.5;
			p.speed = p.speed * 0.98;
		} else {
			this.drawCtx.fillStyle = 'rgb(' + p.r + ' ' + p.g + ' ' + p.b + ')';
			this.drawCtx.globalAlpha = 1;
			this.drawCtx.beginPath();
			this.drawCtx.arc(p.x, p.y, this.radius * (tl > 1000 ? 1 : ShroomUtil.inAndOut(tl / 1000)), 0, 2 * Math.PI);
			this.drawCtx.closePath();
			this.drawCtx.fill();
			this.drawCtx.globalAlpha = 0.1;
			this.drawCtx.beginPath();
			this.drawCtx.arc(p.x, p.y, this.radius * (tl > 1000 ? 5 : 5 * ShroomUtil.inAndOut(tl / 1000)), 0, 2 * Math.PI);
			this.drawCtx.closePath();
			this.drawCtx.fill();
			p.x += p.speedX / 15 + p.addX;
			p.y += p.speedY / 15 + p.addY;
			p.speedX = p.speedX * 0.98;
			p.speedY = p.speedY * 0.98;
		}
	}
	if (this.debug) {
		const end = performance.now();
		this.drawCtx.fillStyle = '#fff';
		this.drawCtx.globalAlpha = 1;
		this.drawCtx.fillText(this.particles.length + ' particles     ' + (end - start).toFixed(1) + 'ms', 10, 20);
	}
};
const fireworksContainer = document.querySelector('.shroom-fireworks');
if (fireworksContainer) {
	const fireworks = new ShroomFireworks(fireworksContainer);
	const logo200container = document.getElementById('shroom200logo');
	if (logo200container) {
		const startBtn = document.createElement('button');
		const stopBtn = document.createElement('button');
		startBtn.className = 'start';
		stopBtn.className = 'stop';
		startBtn.innerText = 'Click here to start the show';
		stopBtn.innerText = '× Stop';
		logo200container.append(startBtn, stopBtn);
		var animTimeout;
		startBtn.addEventListener('click', function () {
			clearTimeout(animTimeout);
			logo200container.classList.add('transition');
			animTimeout = setTimeout(function () {
				logo200container.classList.add('opened');
				logo200container.classList.remove('transition');
				fireworks.spawnFireworks();
			}, 750);
		});
		stopBtn.addEventListener('click', function () {
			fireworks.stopSpawnFireworks();
			clearTimeout(animTimeout);
			logo200container.classList.add('transition');
			animTimeout = setTimeout(function () {
				logo200container.classList.remove('opened');
				logo200container.classList.remove('transition');
			}, 750);
		});
	} else {
		fireworks.spawnFireworks();
	}
}
// /fireworks

// Vending machine
var vendingMachine = document.getElementById('vending-machine-container');
if (vendingMachine) {
	var itemContainer = document.getElementById('vending-machine-items');
	itemContainer.classList.add('hidden');
	var items = document.querySelectorAll('#vending-machine-items li');

	var button = document.createElement('button');
	button.className = 'vending-machine';
	vendingMachine.querySelector('.center').prepend(button);
	button.append(vendingMachine.querySelector('.floatnone'));

	var closeButton = document.createElement('button');
	closeButton.className = 'close';
	closeButton.disabled = true;
	closeButton.textContent = '×';
	itemContainer.append(closeButton);

	button.addEventListener('click', function () {
		button.disabled = true;
		closeButton.disabled = false;
		var selected = Math.floor(Math.random() * items.length);
		for (var i = 0; i < items.length; i++)
			items[i].style.display = selected === i ? '' : 'none';
		itemContainer.classList.remove('hidden');
		closeButton.focus();
	});

	closeButton.addEventListener('click', function () {
		button.disabled = false;
		closeButton.disabled = true;
		itemContainer.classList.add('hidden');
		button.focus();
	});

	setTimeout(function () {
		vendingMachine.classList.add('initialised');
	}, 1);
}
// /vending machine