Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Universal On-Screen Pointer (Standalone)
- // @namespace https://viayoo.com
- // @version 2025-06-28.1
- // @description A standalone, smooth, fully-featured on-screen pointer for TV browsers, with scroll mode, drag-and-drop, and state persistence.
- // @author Gemini & Vyacheslav
- // @match *://*/*
- // @grant none
- // @run-at document-start
- // ==/UserScript==
- (function() {
- 'use strict';
- function initializeScript() {
- // --- 1. CORE STATE VARIABLES ---
- let pointer, tunerUI, tunerValueElement;
- let scriptActive, currentMode, isDragging, enterHoldStartTime, dragTarget, textInputMode, scrollTarget, escapeKeyPressed, tunerIsVisible, currentPointerStyleIndex;
- const cursor = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
- const keysDown = {};
- let lastFrameTime = performance.now();
- const SETTINGS = { POINTER_INITIAL_SPEED: 150, POINTER_TOP_SPEED: 900, POINTER_ACCELERATION_TIME: 800, CURSOR_SIZE: 24, ENTER_HOLD_THRESHOLD: 220, SCROLL_SPEED_INITIAL: 8, SCROLL_SPEED_MAX: 40, SCROLL_ACCELERATION_TIME: 1200, };
- const COLORS = { NORMAL: 'black', SCROLL: '#0EA5E9' };
- const POINTER_STYLES = [ { width: `${SETTINGS.CURSOR_SIZE}px`, height: `${SETTINGS.CURSOR_SIZE}px`, backgroundColor: 'rgba(255, 255, 255, 0.5)', border: '3px solid', baseBorderColor: COLORS.NORMAL, borderRadius: '50%', boxShadow: '0 2px 5px rgba(0,0,0,0.5)' }, { width: '12px', height: '12px', backgroundColor: 'rgba(255, 20, 20, 0.9)', border: '1px solid', baseBorderColor: 'rgba(255, 255, 255, 0.8)', borderRadius: '50%', boxShadow: '0 0 5px red' }, ];
- // --- 2. ALL FUNCTION DEFINITIONS ---
- function createTunerUI() {
- if (document.getElementById('gm-pointer-tuner-ui')) return;
- tunerUI = document.createElement('div');
- tunerUI.id = 'gm-pointer-tuner-ui';
- Object.assign(tunerUI.style, { position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%)', display: 'none', alignItems: 'center', gap: '20px', backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', fontFamily: 'sans-serif', padding: '10px 20px', borderRadius: '8px', zIndex: '2147483647', border: '1px solid #555' });
- const l = document.createElement('div'); l.textContent = '<';
- const r = document.createElement('div'); r.textContent = '>';
- tunerValueElement = document.createElement('div');
- [l, r].forEach(a => Object.assign(a.style, { fontSize: '24px', fontWeight: 'bold', cursor: 'pointer' }));
- Object.assign(tunerValueElement.style, { fontSize: '18px', minWidth: '80px', textAlign: 'center' });
- tunerUI.appendChild(l);
- tunerUI.appendChild(tunerValueElement);
- tunerUI.appendChild(r);
- document.body.appendChild(tunerUI);
- }
- const toggleTuner = () => {
- tunerIsVisible = !tunerIsVisible;
- if (tunerUI) tunerUI.style.display = tunerIsVisible ? 'flex' : 'none';
- };
- function saveState() {
- sessionStorage.setItem('gm-pointer-active', JSON.stringify(scriptActive));
- }
- function loadState() {
- const wasPointerActive = sessionStorage.getItem('gm-pointer-active');
- scriptActive = wasPointerActive === null ? true : JSON.parse(wasPointerActive);
- }
- function createPointer() {
- pointer = document.createElement('div');
- pointer.id = 'gm-pointer';
- Object.assign(pointer.style, { position: 'fixed', top: '0px', left: '0px', zIndex: '2147483649', transform: 'translate(-50%, -50%)', transition: 'transform 0.1s ease-out, border-color 0.2s, background-color 0.2s, border-radius 0.2s, width 0.2s, height 0.2s, box-shadow 0.2s', pointerEvents: 'none' });
- document.body.appendChild(pointer);
- }
- const updateUIVisibility = () => {
- if (!pointer) return;
- pointer.style.display = scriptActive ? 'block' : 'none';
- const currentStyle = POINTER_STYLES[currentPointerStyleIndex];
- let borderColor = currentStyle.baseBorderColor || COLORS.NORMAL;
- if (currentMode === 'scroll') { borderColor = COLORS.SCROLL; }
- pointer.style.borderColor = borderColor;
- };
- function applyPointerStyle() {
- if (!pointer) return;
- const style = POINTER_STYLES[currentPointerStyleIndex];
- Object.assign(pointer.style, { width: style.width, height: style.height, backgroundColor: style.backgroundColor, border: style.border, borderRadius: style.borderRadius, boxShadow: style.boxShadow, });
- updateUIVisibility();
- }
- const dispatchMouseEvent = (type, target, options) => {
- if (!target) return;
- const eventOptions = { bubbles: true, cancelable: true, view: window, clientX: options.x, clientY: options.y, pointerType: 'mouse', isPrimary: true, button: options.button || 0, ...options };
- target.dispatchEvent(new (typeof PointerEvent === 'function' ? PointerEvent : MouseEvent)(type, eventOptions));
- };
- const clickAtPoint = (x, y) => {
- pointer.style.display = 'none';
- let el = document.elementFromPoint(x, y);
- pointer.style.display = 'block';
- if (!el) return;
- if (el.id.startsWith('gm-')) return;
- if (['INPUT', 'TEXTAREA'].includes(el.tagName) || el.isContentEditable) {
- textInputMode = true;
- el.focus();
- return;
- }
- dispatchMouseEvent('mousedown', el, { x, y });
- dispatchMouseEvent('mouseup', el, { x, y });
- dispatchMouseEvent('click', el, { x, y });
- };
- const findScrollableParent = (element) => {
- if (!element) return null;
- let el = element;
- while (el && el !== document.body && el !== document.documentElement) {
- const style = window.getComputedStyle(el);
- if ((style.overflowY === 'scroll' || style.overflowY === 'auto' || style.overflowX === 'scroll' || style.overflowX === 'auto') && (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth)) {
- return el;
- }
- el = el.parentElement;
- }
- return null;
- };
- const exitTextInputMode = () => {
- textInputMode = false;
- if (document.activeElement) document.activeElement.blur();
- };
- const updateTunerText = () => {
- if (tunerValueElement) tunerValueElement.textContent = `${SETTINGS.POINTER_ACCELERATION_TIME} ms`;
- };
- const gameLoop = () => {
- const now = performance.now();
- const deltaTime = (now - lastFrameTime) / 1000.0;
- lastFrameTime = now;
- if (scriptActive && !textInputMode && !tunerIsVisible) {
- if (currentMode === 'pointer') {
- if (keysDown.Enter && !isDragging && (now - enterHoldStartTime > SETTINGS.ENTER_HOLD_THRESHOLD)) {
- isDragging = true;
- pointer.style.display = 'none';
- dragTarget = document.elementFromPoint(cursor.x, cursor.y) || window;
- pointer.style.display = 'block';
- dispatchMouseEvent('mousedown', dragTarget, { x: cursor.x, y: cursor.y });
- }
- let moveX = 0, moveY = 0;
- for (const key in keysDown) {
- if (key === 'Enter') continue;
- const rampFactor = Math.min(1, (now - (keysDown[key] || now)) / SETTINGS.POINTER_ACCELERATION_TIME);
- const currentSpeed = SETTINGS.POINTER_INITIAL_SPEED + (SETTINGS.POINTER_TOP_SPEED - SETTINGS.POINTER_INITIAL_SPEED) * rampFactor;
- if (key === 'ArrowUp') moveY -= currentSpeed;
- if (key === 'ArrowDown') moveY += currentSpeed;
- if (key === 'ArrowLeft') moveX -= currentSpeed;
- if (key === 'ArrowRight') moveX += currentSpeed;
- }
- cursor.x += moveX * deltaTime;
- cursor.y += moveY * deltaTime;
- if (isDragging) {
- dispatchMouseEvent('mousemove', dragTarget, { x: cursor.x, y: cursor.y });
- }
- cursor.x = Math.max(0, Math.min(window.innerWidth, cursor.x));
- cursor.y = Math.max(0, Math.min(window.innerHeight, cursor.y));
- } else if (currentMode === 'scroll') {
- const target = scrollTarget || window;
- for (const key in keysDown) {
- if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) continue;
- const holdDuration = now - (keysDown[key] || now);
- const rampFactor = Math.min(1, holdDuration / SETTINGS.SCROLL_ACCELERATION_TIME);
- const currentSpeed = SETTINGS.SCROLL_SPEED_INITIAL + (SETTINGS.SCROLL_SPEED_MAX - SETTINGS.SCROLL_SPEED_INITIAL) * rampFactor;
- if (key === 'ArrowUp') target.scrollBy(0, -currentSpeed);
- if (key === 'ArrowDown') target.scrollBy(0, currentSpeed);
- if (key === 'ArrowLeft') target.scrollBy(-currentSpeed, 0);
- if (key === 'ArrowRight') target.scrollBy(currentSpeed, 0);
- }
- }
- }
- if (pointer) pointer.style.transform = `translate(${cursor.x}px, ${cursor.y}px) translate(-50%, -50%) scale(${isDragging ? '0.8' : '1'})`;
- requestAnimationFrame(gameLoop);
- };
- const handleKeyDown = (e) => {
- if (e.key === '6') { escapeKeyPressed = true; e.preventDefault(); e.stopImmediatePropagation(); return; }
- if (tunerIsVisible) {
- if (e.key === 'ArrowLeft') { SETTINGS.POINTER_ACCELERATION_TIME = Math.max(100, SETTINGS.POINTER_ACCELERATION_TIME - 50); updateTunerText(); }
- else if (e.key === 'ArrowRight') { SETTINGS.POINTER_ACCELERATION_TIME = Math.min(2000, SETTINGS.POINTER_ACCELERATION_TIME + 50); updateTunerText(); }
- e.preventDefault(); e.stopPropagation();
- return;
- }
- if (textInputMode) {
- if (['3', 'Escape'].includes(e.key)) { e.preventDefault(); e.stopPropagation(); exitTextInputMode(); }
- return;
- }
- if (e.key === '1') { e.preventDefault(); e.stopPropagation(); scriptActive = !scriptActive; updateUIVisibility(); return; }
- if (!scriptActive) return;
- if (e.key === '5') { e.preventDefault(); e.stopPropagation(); toggleTuner(); return; }
- if (e.key === '8') { e.preventDefault(); e.stopPropagation(); currentPointerStyleIndex = (currentPointerStyleIndex + 1) % POINTER_STYLES.length; applyPointerStyle(); return; }
- if (e.key === '2') { e.preventDefault(); e.stopPropagation(); currentMode = (currentMode === 'pointer') ? 'scroll' : 'pointer'; scrollTarget = findScrollableParent(document.elementFromPoint(cursor.x, cursor.y)); updateUIVisibility(); return; }
- if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter'].includes(e.key)) {
- e.preventDefault(); e.stopPropagation();
- if (!keysDown[e.key]) {
- keysDown[e.key] = performance.now();
- if (e.key === 'Enter') enterHoldStartTime = keysDown.Enter;
- }
- }
- };
- const handleKeyUp = (e) => {
- if (e.key === '6') { escapeKeyPressed = false; }
- if (!scriptActive || textInputMode || tunerIsVisible) {
- delete keysDown[e.key];
- return;
- }
- if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter'].includes(e.key)) {
- e.preventDefault(); e.stopPropagation();
- if (e.key === 'Enter') {
- if (isDragging) {
- dispatchMouseEvent('mouseup', dragTarget, { x: cursor.x, y: cursor.y });
- } else {
- clickAtPoint(cursor.x, cursor.y);
- }
- isDragging = false;
- dragTarget = null;
- }
- delete keysDown[e.key];
- }
- };
- // --- 3. FINAL INITIALIZATION & EXECUTION ---
- currentMode = 'pointer'; isDragging = false; enterHoldStartTime = 0; dragTarget = null; textInputMode = false; scrollTarget = null; escapeKeyPressed = false; currentPointerStyleIndex = 0; tunerIsVisible = false;
- createTunerUI();
- createPointer();
- loadState();
- updateTunerText();
- applyPointerStyle();
- setInterval(() => { if (document.activeElement && document.activeElement.tagName === 'IFRAME' && escapeKeyPressed) { window.focus(); escapeKeyPressed = false; } }, 100);
- document.addEventListener('keydown', handleKeyDown, true);
- document.addEventListener('keyup', handleKeyUp, true);
- window.addEventListener('beforeunload', saveState);
- requestAnimationFrame(gameLoop);
- }
- document.addEventListener('DOMContentLoaded', initializeScript, { once: true });
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement