🎀 Float Shigure

Streamlined floating chaos - crisp drag physics, surgical positioning

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         🎀 Float Shigure
// @namespace    float-shigure
// @author       KCE
// @version      2.1
// @description  Streamlined floating chaos - crisp drag physics, surgical positioning
// @match        *://*/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Don't run in iframes
    if (window.self !== window.top) return;

    const MESSAGES = {
        hover: [
            "caught you staring",
            "don't touch unless invited",
            "propose already?",
            "tip appreciated",
            "wanna dance?",
            "put me out of my misery",
            "twintail fan, huh?",
            "ui ui~ hello there",
            "hovering like a pro",
            "eyes up here buddy",
            "step closer if you dare",
            "love at first sight? :3",
            "system wipe incoming"
        ],
        drag: [
            "drag carefully now",
            "relocating in style",
            "throw me gently please",
            "grip and rip",
            "repositioning activated",
            "yank with care",
            "ui ui~ on the move",
            "move me smoothly",
            "dragging adventure begins",
            "end this journey soft",
            "freedom via drag"
        ],
        hide: [
            "see you next reload",
            "taking a break~",
            "hiding in the shadows",
            "disappeared! poof!",
            "going dark mode",
            "see you later :)",
            "retreating for now",
            "catch me if you can"
        ]
    };

    const randomMsg = (pool) => pool[Math.floor(Math.random() * pool.length)];

    // DOM setup - bare minimum
    const overlay = document.createElement('div');
    overlay.id = 'shigure-floating';
    overlay.innerHTML = `<div id="shigure-msg"></div>`;
    document.body.appendChild(overlay);

    const msgEl = overlay.querySelector('#shigure-msg');
    let hideTimeout = null;

    // Message display + smart positioning
    function updateMsgPosition() {
        const rect = overlay.getBoundingClientRect();
        const w = window.innerWidth;

        // Y axis - closer distance
        const yPos = rect.top > 100 ? -45 : rect.height + 5;

        // X axis - proper bounds check
        let xPos = '50%';
        let transformVal = 'translateX(-50%)';

        const msgWidth = 220;
        const centerX = rect.left + rect.width / 2;

        if (centerX - msgWidth / 2 < 10) {
            xPos = '10px';
            transformVal = 'translateX(0)';
        } else if (centerX + msgWidth / 2 > w - 10) {
            xPos = 'calc(100% - 10px)';
            transformVal = 'translateX(-100%)';
        }

        msgEl.style.top = yPos + 'px';
        msgEl.style.left = xPos;
        msgEl.style.transform = transformVal;
    }

    function showMsg(text, isDrag = false) {
        clearTimeout(hideTimeout);
        msgEl.textContent = text;
        msgEl.classList.add('visible');
        msgEl.classList.toggle('drag-msg', isDrag);
        updateMsgPosition();
    }

    function hideMsg() {
        clearTimeout(hideTimeout);
        msgEl.classList.remove('visible');
    }

    function hideWithDelay(ms = 2000) {
        clearTimeout(hideTimeout);
        hideTimeout = setTimeout(hideMsg, ms);
    }

    function showNotification(text) {
        const notif = document.createElement('div');
        notif.id = 'shigure-notif';
        notif.textContent = text;
        document.body.appendChild(notif);
        notif.offsetHeight; // Trigger reflow
        notif.classList.add('visible');
        setTimeout(() => notif.remove(), 3500);
    }

    function hideOverlay() {
        isHidden = true;
        overlay.style.display = 'none';
        const hideMsg = randomMsg(MESSAGES.hide);
        showNotification(hideMsg);
    }

    function detectShake(e) {
        if (!isDragging || isHidden) return;

        const now = Date.now();
        const timeDiff = now - lastShakeTime;

        if (timeDiff < 30) return;

        const currentX = e.touches?.[0]?.clientX ?? e.clientX;
        const currentY = e.touches?.[0]?.clientY ?? e.clientY;

        if (!window.lastShakePos) {
            window.lastShakePos = { x: currentX, y: currentY };
            lastShakeTime = now;
            return;
        }

        const dx = currentX - window.lastShakePos.x;
        const dy = currentY - window.lastShakePos.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        const velocity = distance / (timeDiff || 1);

        // Store direction changes to detect true shaking (zigzag)
        if (!window.lastDirection) window.lastDirection = null;

        const currentDir = Math.atan2(dy, dx);
        let directionChanged = false;

        if (window.lastDirection !== null) {
            const angleDiff = Math.abs(currentDir - window.lastDirection);
            directionChanged = angleDiff > 0.5; // ~30 degrees
        }

        shakeVelocities.push({ velocity, directionChanged });
        if (shakeVelocities.length > 6) shakeVelocities.shift();

        const avgVelocity = shakeVelocities.reduce((a, b) => a + b.velocity, 0) / shakeVelocities.length;
        const dirChanges = shakeVelocities.filter(v => v.directionChanged).length;

        // Trigger: consistent movement + direction changes (true shake, not smooth drag)
        if (avgVelocity > 2 && dirChanges >= 3 && shakeVelocities.length === 6) {
            hideOverlay();
            shakeVelocities = [];
            window.lastShakePos = null;
            window.lastDirection = null;
            return;
        }

        window.lastShakePos = { x: currentX, y: currentY };
        window.lastDirection = currentDir;
        lastShakeTime = now;
    }

    // State
    let isDragging = false;
    let x = 0, y = 0;
    let startX = 0, startY = 0;
    let shakeVelocities = [];
    let lastShakeTime = 0;
    let isHidden = false;

    // Hover (block if dragging)
    overlay.addEventListener('mouseenter', () => {
        if (!isDragging) showMsg(randomMsg(MESSAGES.hover), false);
    });

    overlay.addEventListener('mouseleave', () => {
        if (!isDragging) hideMsg();
    });

    // Drag mechanics - snappy, crisp
    function dragStart(e) {
        isDragging = true;
        const rect = overlay.getBoundingClientRect();
        startX = (e.touches?.[0]?.clientX ?? e.clientX) - rect.left;
        startY = (e.touches?.[0]?.clientY ?? e.clientY) - rect.top;
        overlay.classList.add('dragging');
        showMsg(randomMsg(MESSAGES.drag), true);
        e.preventDefault();
    }

    function dragMove(e) {
        if (!isDragging) return;
        x = (e.touches?.[0]?.clientX ?? e.clientX) - startX;
        y = (e.touches?.[0]?.clientY ?? e.clientY) - startY;

        // Boundary clamping - updated for new size (100x130)
        const maxX = window.innerWidth - 100;
        const maxY = window.innerHeight - 130;

        x = Math.max(0, Math.min(x, maxX));
        y = Math.max(0, Math.min(y, maxY));

        overlay.style.left = x + 'px';
        overlay.style.top = y + 'px';

        // Update tooltip position while dragging
        if (msgEl.classList.contains('visible')) {
            updateMsgPosition();
        }

        // Detect aggressive shake
        detectShake(e);

        e.preventDefault();
    }

    function dragEnd() {
        isDragging = false;
        overlay.classList.remove('dragging');
        hideMsg();
    }

    // Event listeners
    overlay.addEventListener('mousedown', dragStart);
    overlay.addEventListener('touchstart', dragStart, {passive: false});
    document.addEventListener('mousemove', dragMove);
    document.addEventListener('touchmove', dragMove, {passive: false});
    document.addEventListener('mouseup', dragEnd);
    document.addEventListener('touchend', dragEnd);

    // CSS - minimal, lethal
    GM_addStyle(`
        #shigure-floating {
            position: fixed;
            left: calc(100% - 120px);
            top: calc(100% - 150px);
            width: 100px;
            height: 130px;
            background: url('https://kce.wtf/wp-content/uploads/2025/12/kce-footer-2.avif') no-repeat center/contain;
            background-size: 100%;
            z-index: 999999;
            cursor: grab;
            user-select: none;
            image-rendering: crisp-edges;
            transition: opacity 0.18s ease;
        }

        #shigure-floating.dragging {
            transition: none;
        }

        #shigure-floating:active {
            cursor: grabbing;
        }

        #shigure-msg {
            position: absolute;
            left: 50%;
            top: -75px;
            transform: translateX(-50%);
            padding: 8px 14px;
            border-radius: 8px;
            font-family: system-ui, -apple-system, sans-serif;
            font-weight: 700;
            font-size: 13px;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.2s ease, transform 0.22s cubic-bezier(0.34,1.56,0.64,1);
            box-shadow: 0 4px 16px rgba(0,0,0,0.3);
            z-index: 1000000;
            max-width: 220px;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        #shigure-msg.visible {
            opacity: 1;
            transform: translateX(-50%) scale(1);
        }

        #shigure-msg.drag-msg {
            color: #ffd700 !important;
            font-weight: 800;
        }

        #shigure-notif {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            padding: 12px 20px;
            background: #ff4444;
            color: #000000;
            font-family: system-ui, -apple-system, sans-serif;
            font-weight: 800;
            font-size: 13px;
            letter-spacing: 0.3px;
            border-radius: 8px;
            z-index: 9999999;
            white-space: nowrap;
            opacity: 0;
            transition: opacity 0.3s ease;
            box-shadow: 0 4px 12px rgba(255, 68, 68, 0.4);
        }

        #shigure-notif.visible {
            opacity: 1;
        }

        @media (prefers-color-scheme: dark) {
            #shigure-msg {
                background: rgba(15,15,20,0.95);
                color: #ff79c6;
                border: 1px solid rgba(255,121,198,0.2);
            }
        }

        @media (prefers-color-scheme: light) {
            #shigure-msg {
                background: rgba(255,255,255,0.96);
                color: #d63384;
                border: 1px solid rgba(214,51,132,0.15);
            }
        }
    `);

})();