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; })());