april-fools-2025/public/script.js
2025-03-31 17:11:52 +02:00

315 lines
12 KiB
JavaScript

const BeansAPI = Object.freeze((() => {
const pointerNuke = pointer => {
if(pointer.element instanceof Element)
document.body.removeChild(pointer.element);
pointer.element = undefined;
pointer.elementHand = undefined;
pointer.elementGlow = undefined;
pointer.elementNumber = undefined;
};
const pointerDraw = pointer => {
pointer.fadeOutStart = false;
if(!pointer.visible) {
pointerNuke(pointer);
return;
}
if(!pointer.element) {
pointer.element = document.createElement('div');
pointer.element.style.width = '64px';
pointer.element.style.height = '64px';
pointer.element.style.pointerEvents = 'none';
pointer.element.style.borderWidth = '0';
pointer.element.style.boxStyle = 'border-box';
pointer.element.style.position = 'fixed';
pointer.element.style.zIndex = pointer.self ? '2147483647' : (10000000 + (pointer.connId ?? 0)).toString();
pointer.element.style.transform = 'scale(.5) rotate(-20deg)';
pointer.element.style.transformOrigin = '-18px 10px';
pointer.elementNumber = document.createElement('div');
pointer.elementNumber.style.position = 'absolute';
pointer.elementNumber.style.top = '28px';
pointer.elementNumber.style.left = '0';
pointer.elementNumber.style.zIndex = '30';
pointer.elementNumber.style.color = 'var(--pointer-colour, #00f)';
pointer.elementNumber.style.fontFamily = 'sans-serif';
pointer.elementNumber.style.fontSize = '24px';
pointer.elementNumber.style.fontWeight = '700';
pointer.elementNumber.style.letterSpacing = '-1px';
pointer.elementNumber.style.textAlign = 'center';
pointer.elementNumber.style.width = pointer.element.style.width;
pointer.elementNumber.style.height = pointer.element.style.height;
pointer.element.appendChild(pointer.elementNumber);
pointer.elementGlow = document.createElement('div');
pointer.elementGlow.style.position = 'absolute';
pointer.elementGlow.style.top = '0';
pointer.elementGlow.style.left = '0';
pointer.elementGlow.style.zIndex = '20';
pointer.elementGlow.style.backgroundColor = 'var(--pointer-colour, #00f)';
pointer.elementGlow.style.width = pointer.element.style.width;
pointer.elementGlow.style.height = pointer.element.style.height;
pointer.elementGlow.style.maskImage = 'url(//static.flash.moe/images/pointer.png)';
pointer.elementGlow.style.webkitMaskImage = 'url(//static.flash.moe/images/pointer.png)';
pointer.elementGlow.style.maskType = 'alpha';
pointer.elementGlow.style.webkitMaskType = 'alpha';
pointer.element.appendChild(pointer.elementGlow);
pointer.elementHand = document.createElement('div');
pointer.elementHand.style.position = 'absolute';
pointer.elementHand.style.top = '0';
pointer.elementHand.style.left = '0';
pointer.elementHand.style.zIndex = '10';
pointer.elementHand.style.width = pointer.element.style.width;
pointer.elementHand.style.height = pointer.element.style.height;
pointer.elementHand.style.backgroundColor = '#fff';
pointer.elementHand.style.maskImage = 'url(//static.flash.moe/images/pointer.png)';
pointer.elementHand.style.webkitMaskImage = 'url(//static.flash.moe/images/pointer.png)';
pointer.elementHand.style.maskType = 'alpha';
pointer.elementHand.style.webkitMaskType = 'alpha';
pointer.element.appendChild(pointer.elementHand);
document.body.appendChild(pointer.element);
}
const opacity = pointer.self ? .5 : 1;
pointer.elementNumber.textContent = pointer.userId ?? pointer.connId ?? 0;
pointer.element.style.setProperty('--pointer-colour', '#' + (pointer.colour ?? 0).toString(16).padStart(6, '0'));
pointer.element.style.opacity = opacity.toString();
pointer.element.style.left = Math.round(window.innerWidth * ((pointer.xPos ?? 0) / 0xFFFF)).toString() + 'px';
pointer.element.style.top = Math.round(window.innerHeight * ((pointer.yPos ?? 0) / 0xFFFF)).toString() + 'px';
if(pointer.click) {
pointer.elementHand.style.maskPosition = '-64px 0';
pointer.elementHand.style.webkitMaskPosition = '-64px 0';
pointer.elementGlow.style.maskPosition = '-64px -64px';
pointer.elementGlow.style.webkitMaskPosition = '-64px -64px';
} else {
pointer.elementHand.style.maskPosition = '0 0';
pointer.elementHand.style.webkitMaskPosition = '0 0';
pointer.elementGlow.style.maskPosition = '0 -64px';
pointer.elementGlow.style.webkitMaskPosition = '0 -64px';
}
pointer.fadeOutStart = undefined;
const fadeOut = time => {
if(pointer.fadeOutStart === false || !pointer.element) {
pointer.fadeOutStart = undefined;
return;
}
if(pointer.fadeOutStart === undefined)
pointer.fadeOutStart = time;
const elapsed = time - pointer.fadeOutStart;
const completion = Math.min(1, Math.max(0, elapsed / 30000));
const eased = completion === 0 ? 0 : Math.pow(2, 10 * completion - 10);
pointer.element.style.opacity = (opacity - (opacity * eased)).toString();
if(completion < 1)
requestAnimationFrame(fadeOut);
else
pointerNuke(pointer);
};
requestAnimationFrame(fadeOut);
};
const pointers = new Map;
let selfPointer;
let sock;
const pub = {};
let connected = false;
const runWhenConnected = [];
const connect = () => {
sock = new WebSocket(`${location.protocol.replace('http', 'ws')}//beans.flashii.net`);
sock.binaryType = 'arraybuffer';
sock.addEventListener('close', ev => {
connected = false;
sock = undefined;
setTimeout(() => { connect(); }, 1000);
});
sock.addEventListener('message', ev => {
if(!(ev.data instanceof ArrayBuffer))
return;
const view = new DataView(ev.data);
let offset = 0;
let connId = offset < view.byteLength ? view.getUint8(offset++) : 0;
connId <<= 8;
connId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
connId <<= 8;
connId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
connId <<= 8;
connId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
const isSelf = !!(connId & 0x80000000);
connId &= 0x7FFFFFFF;
let pointer;
if(pointers.has(connId))
pointer = pointers.get(connId);
else
pointers.set(connId, pointer = { connId, visible: false, xPos: -1000, yPos: -1000, });
if(isSelf) {
if(selfPointer)
selfPointer.self = false;
selfPointer = pointer;
selfPointer.self = true;
connected = true;
for(const callback of runWhenConnected)
callback(pub);
}
const oflags = offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.visible = !!(oflags & 0x10);
pointer.click = !!(oflags & 0x20);
if(oflags & 0x01) {
pointer.userId = offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.userId <<= 8;
pointer.userId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.userId <<= 8;
pointer.userId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.userId <<= 8;
pointer.userId |= offset < view.byteLength ? view.getUint8(offset++) : 0;
}
if(oflags & 0x02) {
pointer.xPos = offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.xPos <<= 8;
pointer.xPos |= offset < view.byteLength ? view.getUint8(offset++) : 0;
}
if(oflags & 0x04) {
pointer.yPos = offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.yPos <<= 8;
pointer.yPos |= offset < view.byteLength ? view.getUint8(offset++) : 0;
}
if(oflags & 0x08) {
pointer.colour = offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.colour <<= 8;
pointer.colour |= offset < view.byteLength ? view.getUint8(offset++) : 0;
pointer.colour <<= 8;
pointer.colour |= offset < view.byteLength ? view.getUint8(offset++) : 0;
}
pointerDraw(pointer);
});
};
const pointerUpdate = ev => {
if(!connected)
return;
let iflags = 0;
let oflags = 0;
const buffer = [];
const xPos = Math.round((ev.clientX / window.innerWidth) * 0xFFFF);
const yPos = Math.round((ev.clientY / window.innerHeight) * 0xFFFF);
const click = !!(ev.buttons & 1);
if(!click !== !selfPointer.click) {
selfPointer.click = click;
iflags |= 0x20;
if(click) oflags |= 0x20;
buffer.push(click ? 0x78 : 0x58);
}
if(xPos !== selfPointer.xPos) {
selfPointer.xPos = xPos;
iflags |= 0x02;
buffer.push((xPos & 0xFF00) >> 8);
buffer.push(xPos & 0xFF);
}
if(yPos !== selfPointer.yPos) {
selfPointer.yPos = yPos;
iflags |= 0x04;
buffer.push((yPos & 0xFF00) >> 8);
buffer.push(yPos & 0xFF);
}
if(iflags) {
if(iflags & 0x01)
buffer.unshift(oflags);
buffer.unshift(iflags);
}
if(buffer.length > 0) {
sock.send(new Uint8Array(buffer));
pointerDraw(selfPointer);
}
};
window.addEventListener('pointermove', pointerUpdate);
window.addEventListener('pointerup', pointerUpdate);
window.addEventListener('pointerdown', pointerUpdate);
connect();
pub.runWhenConnected = callback => {
if(typeof callback !== 'function')
return;
runWhenConnected.push(callback);
if(connected)
callback(pub);
};
pub.setUserInfo = (id, colour) => {
if(!connected)
return;
if(typeof id === 'string')
id = parseInt(id);
if(typeof id !== 'number' || !Number.isInteger(id))
id = undefined;
if(typeof colour === 'string')
colour = colour.startsWith('#') ? parseInt(colour.substring(1), 16) : parseInt(colour);
if(typeof colour !== 'number' || !Number.isInteger(colour))
colour = undefined;
let iflags = 0x10;
const buffer = [0x10];
selfPointer.visible = true;
if(id !== undefined && id !== selfPointer.userId) {
selfPointer.userId = id;
iflags |= 0x01;
buffer.push((id & 0xFF000000) >> 24);
buffer.push((id & 0xFF0000) >> 16);
buffer.push((id & 0xFF00) >> 8);
buffer.push(id & 0xFF);
}
if(colour !== undefined && colour !== selfPointer.colour) {
selfPointer.colour = colour;
iflags |= 0x08;
buffer.push((colour & 0xFF0000) >> 16);
buffer.push((colour & 0xFF00) >> 8);
buffer.push(colour & 0xFF);
}
if(iflags)
buffer.unshift(iflags);
if(buffer.length > 0) {
sock.send(new Uint8Array(buffer));
pointerDraw(selfPointer);
}
};
return pub;
})());