MediaWiki:Gadget-ShroomScript.js: Difference between revisions
m (Upcoming 'Shroom release) |
m (Fix fireworks) |
||
(One intermediate revision by one other user not shown) | |||
Line 1: | Line 1: | ||
/* General-purpose JavaScript used for The 'Shroom */ | /* 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(); | |||
} | |||
}); | |||
}); | |||
} | |||
/* | /* | ||
Line 54: | Line 160: | ||
var openingDuration = this.opening ? Date.now() - this.opening : 0; | var openingDuration = this.opening ? Date.now() - this.opening : 0; | ||
var openingProgress = openingDuration / (this.canvas.width / window.devicePixelRatio * 4 + 1000); | var openingProgress = openingDuration / (this.canvas.width / window.devicePixelRatio * 4 + 1000); | ||
this.ctx.fillStyle = 'rgba(0,0,0,'.concat(1 - | 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.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); | ||
this.renderCurtain(timestamp, true, openingDuration); | this.renderCurtain(timestamp, true, openingDuration); | ||
Line 65: | Line 171: | ||
var foldWidth = this.canvas.width / neededFolds / 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 curtainOffset = Math.pow(Math.min(openingDuration / (this.canvas.width / window.devicePixelRatio * 4 + 1000), 1), 2) * this.canvas.width / 2; | ||
var curtainSkewOffset = ( | 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); | var gr = this.ctx.createLinearGradient(left ? this.canvas.width : 0, 0, this.canvas.width / 2, 0); | ||
gr.addColorStop(0, '#900'); | gr.addColorStop(0, '#900'); | ||
Line 93: | Line 199: | ||
} | } | ||
this.ctx.restore(); | this.ctx.restore(); | ||
}; | }; | ||
var curtainContainer = document.querySelector('.shroom195curtain'); | var curtainContainer = document.querySelector('.shroom195curtain'); | ||
Line 101: | Line 204: | ||
new ShroomCurtain(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'; | |||
img.loading = 'eager'; | |||
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 |
Latest revision as of 18:02, May 28, 2024
/* 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';
img.loading = 'eager';
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