Streamlined floating chaos - crisp drag physics, surgical positioning
// ==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);
}
}
`);
})();