ellij / Tumble Together Binary Display (Safe)

// ==UserScript==
// @name         Tumble Together Binary Display (Safe)
// @namespace    http://tampermonkey.net/
// @version      3.0
// @license      MIT
// @description  A bit shower for https://tumble-together.herokuapp.com
// @match        https://tumble-together.herokuapp.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let overlay = null;
    let lastUpdate = 0;
    let animFrame = null;

    function createOverlay() {
        if (overlay) return;
        overlay = document.createElement('div');
        overlay.id = 'tm-binary-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 10000;
        `;
        document.body.appendChild(overlay);
    }

    function updateLabels() {
        if (!overlay) createOverlay();
        if (!overlay) return;

        // Verwijder oude labels
        while (overlay.firstChild) overlay.removeChild(overlay.firstChild);

        const board = document.getElementById('board');
        if (!board) return;

        const positions = board.querySelectorAll('.position');
        for (const pos of positions) {
            // Zoek het flippable onderdeel (bit, gearbit, gear)
            let part = null;
            for (const child of pos.children) {
                if (child.classList && (child.classList.contains('bit') || child.classList.contains('gearbit') || child.classList.contains('gear'))) {
                    part = child;
                    break;
                }
            }
            if (!part) continue;

            const isFlipped = part.classList.contains('flipped');
            const isLocked = part.classList.contains('locked');
            const value = isFlipped ? '0' : '1';

            const rect = part.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) continue;

            const label = document.createElement('div');
            label.textContent = value;
            label.style.cssText = `
                position: absolute;
                left: ${rect.left + rect.width/2}px;
                top: ${rect.top + rect.height/2}px;
                transform: translate(-50%, -50%);
                background: rgba(0,0,0,0.7);
                color: white;
                font-size: 14px;
                font-weight: bold;
                font-family: monospace;
                width: 22px;
                height: 22px;
                line-height: 22px;
                text-align: center;
                border-radius: 11px;
                pointer-events: none;
            `;
            if (isLocked) label.style.background = 'rgba(100,100,100,0.7)';
            overlay.appendChild(label);
        }
    }

    function tick(timestamp) {
        // Update maximaal 10x per seconde (genoeg voor smooth updates)
        if (timestamp - lastUpdate > 100) {
            lastUpdate = timestamp;
            updateLabels();
        }
        animFrame = requestAnimationFrame(tick);
    }

    // Start de loop zodra de pagina geladen is
    window.addEventListener('load', () => {
        createOverlay();
        animFrame = requestAnimationFrame(tick);
    });

    // Opruimen als de pagina sluit
    window.addEventListener('beforeunload', () => {
        if (animFrame) cancelAnimationFrame(animFrame);
        if (overlay) overlay.remove();
    });
})();