Initial commit
This commit is contained in:
commit
d0915bc19c
5 changed files with 675 additions and 0 deletions
308
xilly-mouse-trail.user.js
Normal file
308
xilly-mouse-trail.user.js
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
// ==UserScript==
|
||||
// @name Xilly - Mouse Trail
|
||||
// @namespace http://experimenting.website/
|
||||
// @version 0.1
|
||||
// @description Add firefly mouse trail
|
||||
// @author EXPERIMENTING
|
||||
// @match https://x.com/*
|
||||
// @grant none
|
||||
// @run-at document-start
|
||||
// ==/UserScript==
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ----- CONFIG -----
|
||||
const MAX_PARTICLES = 10;
|
||||
const SPAWN_RATE = 0.4;
|
||||
const BASE_SIZE = 1.0;
|
||||
const SIZE_VARIATION = 2.2;
|
||||
const LIFE_MIN = 700;
|
||||
const LIFE_MAX = 1600;
|
||||
const SPEED_FACTOR = 0.02;
|
||||
const TAIL_SMOOTH = 0.85;
|
||||
const FRAME_LIMIT_MS = 16.67;
|
||||
const IGNORE_INPUTS = true;
|
||||
|
||||
const COLORS = [
|
||||
'255,210,120',
|
||||
'255,200,90',
|
||||
'255,170,70',
|
||||
'255,230,160'
|
||||
];
|
||||
|
||||
// ----- CREATE CANVAS OVERLAY -----
|
||||
let canvas, ctx;
|
||||
function createCanvas() {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.left = '0';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
canvas.style.pointerEvents = 'none';
|
||||
canvas.style.zIndex = '2147483646';
|
||||
canvas.id = 'firefly-trail-canvas';
|
||||
document.documentElement.appendChild(canvas);
|
||||
|
||||
ctx = canvas.getContext('2d', { alpha: true });
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas, { passive: true });
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
if (!canvas) return;
|
||||
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||
const w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
||||
const h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
||||
canvas.width = Math.floor(w * dpr);
|
||||
canvas.height = Math.floor(h * dpr);
|
||||
canvas.style.width = w + 'px';
|
||||
canvas.style.height = h + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
|
||||
// ----- PARTICLE SYSTEM -----
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset(0, 0);
|
||||
}
|
||||
reset(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.vx = (Math.random() - 0.5) * 0.3;
|
||||
this.vy = (Math.random() - 0.5) * 0.3;
|
||||
this.size = BASE_SIZE + Math.random() * SIZE_VARIATION;
|
||||
this.life = 0;
|
||||
this.maxLife = LIFE_MIN + Math.random() * (LIFE_MAX - LIFE_MIN);
|
||||
this.hue = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
this.alpha = 1;
|
||||
this.alive = true;
|
||||
this.age = 0;
|
||||
}
|
||||
update(dt) {
|
||||
this.vx *= TAIL_SMOOTH;
|
||||
this.vy *= TAIL_SMOOTH;
|
||||
this.x += this.vx * dt;
|
||||
this.y += this.vy * dt;
|
||||
this.age += dt;
|
||||
this.life = this.age;
|
||||
this.alpha = Math.max(0, 1 - this.life / this.maxLife);
|
||||
if (this.life >= this.maxLife) this.alive = false;
|
||||
}
|
||||
draw(ctx) {
|
||||
const g = ctx;
|
||||
const s = this.size;
|
||||
g.save();
|
||||
// additive blending for glow
|
||||
g.globalCompositeOperation = 'lighter';
|
||||
// radial gradient for soft glow
|
||||
const grad = g.createRadialGradient(this.x, this.y, 0, this.x, this.y, s * 8);
|
||||
const rgba = (a) => `rgba(${this.hue},${a})`;
|
||||
grad.addColorStop(0, rgba(0.95 * this.alpha));
|
||||
grad.addColorStop(0.2, rgba(0.6 * this.alpha));
|
||||
grad.addColorStop(0.5, rgba(0.18 * this.alpha));
|
||||
grad.addColorStop(1, rgba(0));
|
||||
g.fillStyle = grad;
|
||||
g.beginPath();
|
||||
g.arc(this.x, this.y, s * 8, 0, Math.PI * 2);
|
||||
g.fill();
|
||||
|
||||
// small core
|
||||
g.globalCompositeOperation = 'source-over';
|
||||
g.shadowColor = `rgba(${this.hue}, ${0.9 * this.alpha})`;
|
||||
g.shadowBlur = Math.max(6, s * 6);
|
||||
g.fillStyle = `rgba(${this.hue}, ${1 * this.alpha})`;
|
||||
g.beginPath();
|
||||
g.arc(this.x, this.y, s, 0, Math.PI * 2);
|
||||
g.fill();
|
||||
|
||||
g.restore();
|
||||
}
|
||||
}
|
||||
|
||||
let particles = [];
|
||||
let freeList = [];
|
||||
function createParticle(x, y) {
|
||||
let p;
|
||||
if (freeList.length) {
|
||||
p = freeList.pop();
|
||||
p.reset(x, y);
|
||||
} else if (particles.length < MAX_PARTICLES) {
|
||||
p = new Particle();
|
||||
p.reset(x, y);
|
||||
particles.push(p);
|
||||
} else {
|
||||
// recycle the oldest or dead one
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
if (!particles[i].alive) {
|
||||
p = particles[i];
|
||||
p.reset(x, y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!p) {
|
||||
// fallback: overwrite random
|
||||
p = particles[Math.floor(Math.random() * particles.length)];
|
||||
p.reset(x, y);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// ----- MOUSE HANDLING -----
|
||||
let lastX = 0, lastY = 0, lastTime = performance.now();
|
||||
let enabled = true;
|
||||
|
||||
function onMove(e) {
|
||||
if (!enabled) return;
|
||||
if (IGNORE_INPUTS) {
|
||||
const t = e.target;
|
||||
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
||||
}
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
const now = performance.now();
|
||||
const dt = Math.max(1, now - lastTime);
|
||||
const dx = x - lastX;
|
||||
const dy = y - lastY;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
|
||||
|
||||
let toSpawn = Math.min(6, Math.ceil(dist * SPAWN_RATE));
|
||||
|
||||
for (let i = 0; i < toSpawn; i++) {
|
||||
const t = i / Math.max(1, toSpawn);
|
||||
const sx = lastX + dx * t + (Math.random() - 0.5) * 2;
|
||||
const sy = lastY + dy * t + (Math.random() - 0.5) * 2;
|
||||
const p = createParticle(sx, sy);
|
||||
p.vx += dx * SPEED_FACTOR * (0.6 + Math.random() * 0.9);
|
||||
p.vy += dy * SPEED_FACTOR * (0.6 + Math.random() * 0.9) + (Math.random() - 0.5) * 0.8;
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
lastTime = now;
|
||||
}
|
||||
|
||||
function onTouchMove(e) {
|
||||
if (!e.touches || e.touches.length === 0) return;
|
||||
const t = e.touches[0];
|
||||
onMove({ clientX: t.clientX, clientY: t.clientY, target: t.target });
|
||||
}
|
||||
|
||||
// ----- RENDER LOOP -----
|
||||
let rafId = null;
|
||||
function frame(now) {
|
||||
if (!ctx) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const dt = 16;
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
const p = particles[i];
|
||||
if (!p.alive) continue;
|
||||
p.update(dt);
|
||||
p.draw(ctx);
|
||||
}
|
||||
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
if (!particles[i].alive) {
|
||||
freeList.push(particles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
// ----- ENABLE / DISABLE CONTROL -----
|
||||
function enable() {
|
||||
if (enabled) return;
|
||||
enabled = true;
|
||||
canvas.style.display = '';
|
||||
window.addEventListener('mousemove', onMove, { passive: true });
|
||||
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
||||
if (!rafId) rafId = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
if (!enabled) return;
|
||||
enabled = false;
|
||||
canvas.style.display = 'none';
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('touchmove', onTouchMove);
|
||||
if (rafId) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// toggle with Ctrl+Shift+K
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'k') {
|
||||
if (enabled) {
|
||||
disable();
|
||||
flashStatus('Mouse trail: OFF');
|
||||
} else {
|
||||
enable();
|
||||
flashStatus('Mouse trail: ON');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// small on-screen hint that auto-hides
|
||||
let hintEl;
|
||||
function flashStatus(text) {
|
||||
if (!hintEl) {
|
||||
hintEl = document.createElement('div');
|
||||
hintEl.style.position = 'fixed';
|
||||
hintEl.style.right = '18px';
|
||||
hintEl.style.bottom = '18px';
|
||||
hintEl.style.padding = '8px 12px';
|
||||
hintEl.style.fontSize = '13px';
|
||||
hintEl.style.background = 'rgba(20,20,20,0.7)';
|
||||
hintEl.style.color = '#fff';
|
||||
hintEl.style.borderRadius = '8px';
|
||||
hintEl.style.zIndex = '2147483647';
|
||||
hintEl.style.pointerEvents = 'none';
|
||||
document.documentElement.appendChild(hintEl);
|
||||
}
|
||||
hintEl.textContent = text;
|
||||
hintEl.style.opacity = '1';
|
||||
setTimeout(() => {
|
||||
if (hintEl) hintEl.style.transition = 'opacity 700ms';
|
||||
if (hintEl) hintEl.style.opacity = '0';
|
||||
}, 900);
|
||||
}
|
||||
|
||||
// ----- INIT -----
|
||||
function init() {
|
||||
// avoid running inside some embedded frames or if canvas already present
|
||||
try {
|
||||
if (window.top !== window.self) return;
|
||||
} catch (e) {}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', setup, { once: true });
|
||||
} else {
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
||||
function setup() {
|
||||
createCanvas();
|
||||
// initial mouse pos center
|
||||
lastX = (window.innerWidth || document.documentElement.clientWidth) / 2;
|
||||
lastY = (window.innerHeight || document.documentElement.clientHeight) / 2;
|
||||
lastTime = performance.now();
|
||||
|
||||
window.addEventListener('mousemove', onMove, { passive: true });
|
||||
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
||||
|
||||
rafId = requestAnimationFrame(frame);
|
||||
// initial simple hint
|
||||
flashStatus('Firefly trail enabled — Ctrl+Shift+K to toggle');
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue