// ==UserScript==
// @name CollabVM CRT Mode Button
// @name:es Botón de modo CRT de CollabVM
// @match https://computernewb.com/collab-vm/*
// @grant none
// @description Adds a toggleable CRT-style effect to the CollabVM display.
// @description:es Agrega un efecto estilo CRT alternable a la pantalla CollabVM.
// @license MIT
// @version 0.0.1.30250617163501
// @namespace https://greasyfork.runtimutd.eu.org/users/1484733
// ==/UserScript==
(function () {
// console.log("[CRT Script] Script loaded!");
function getVmDisplay() {
// Returns the #vmDisplay element if it exists, otherwise logs an error and returns null.
const el = document.getElementById("vmDisplay");
if (!el) {
console.log("[CRT Script] #vmDisplay not found.");
return null;
}
return el;
}
function ensureCRTEffects(vmDisplay) {
// Injects CRT effect styles if not already present,
// and ensures CRT overlay divs exist inside #vmDisplay.
try {
if (!document.getElementById("crt-effects-style")) {
const style = document.createElement("style");
style.id = "crt-effects-style";
style.textContent = `
#vmDisplay {
display: table;
margin: 24px auto;
position: relative;
}
#vmDisplay.crt-canvas {
background: #191919 radial-gradient(ellipse at 60% 40%, #222 75%, #111 100%);
overflow: hidden;
will-change: transform;
line-height: 0;
}
#vmDisplay.crt-canvas canvas {
display: block;
margin: 0;
padding: 0;
border-radius: 8px;
/* Slight retro warp */
filter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="w"><feTurbulence type="turbulence" baseFrequency="0.009 0.012" numOctaves="2" result="t"/><feDisplacementMap in2="t" in="SourceGraphic" scale="2" xChannelSelector="R" yChannelSelector="G"/></filter></svg>#w');
border: none !important;
outline: none !important;
box-shadow: none !important;
}
#vmDisplay.crt-canvas::before {
content: '';
pointer-events: none;
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: repeating-linear-gradient(rgba(0,0,0,0.16) 0px, rgba(0,0,0,0.16) 1.4px, transparent 1.4px, transparent 3px);
mix-blend-mode: multiply;
z-index: 10;
}
#vmDisplay.crt-canvas::after {
content: '';
pointer-events: none;
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: radial-gradient(ellipse at center, transparent 85%, rgba(0,0,0,0.10) 100%);
z-index: 11;
}
.crt-bulge {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 2;
border-radius: 32px;
background: radial-gradient(ellipse at 60% 45%, rgba(255,255,255,0.17) 0%, rgba(0,0,0,0.39) 100%);
opacity: 0.24;
mix-blend-mode: lighten;
}
.crt-scanlines {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 3;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 1.15px,
rgba(0,0,0,0.20) 1.15px,
rgba(0,0,0,0.06) 1.8px,
transparent 2.2px,
transparent 3.3px
);
opacity: 0.19;
mix-blend-mode: multiply;
}
.crt-dotmask {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 4;
background: repeating-linear-gradient(
to right,
rgba(255,0,0,0.12) 0px, rgba(255,0,0,0.12) 1px, transparent 1.5px, transparent 3px
),
repeating-linear-gradient(
to bottom,
transparent 0px, transparent 2px, rgba(0,255,0,0.12) 2px, transparent 3px
);
opacity: 0.15;
mix-blend-mode: screen;
}
.crt-phosphor {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 5;
box-shadow: 0 0 24px 8px #aaffaa, 0 0 64px 14px #00bfff;
opacity: 0.20;
filter: blur(1.5px) brightness(1.1);
}
`;
document.head.appendChild(style);
}
// Ensure CRT overlays exist
["crt-bulge", "crt-scanlines", "crt-dotmask", "crt-phosphor"].forEach(cls => {
if (!vmDisplay.querySelector("." + cls)) {
const div = document.createElement("div");
div.className = cls;
vmDisplay.appendChild(div);
}
});
} catch (e) {
console.error("[CRT Script] Error in ensureCRTEffects:", e);
}
}
let crtFrameCount = 0;
let crtFlickerValue = 1;
let crtFlickerFrame;
let retroEffect = false;
function crtFlicker() {
// Animates CRT flicker and simulates an antialiased filter.
try {
crtFrameCount++;
if (crtFrameCount % 2 === 0) {
const target = 0.98 + Math.random() * 0.06;
crtFlickerValue += (target - crtFlickerValue) * 0.25;
const vmDisplay = getVmDisplay();
if (vmDisplay && vmDisplay.classList.contains("crt-canvas")) {
const canvas = vmDisplay.querySelector("canvas");
if (canvas) {
// "FXAA" style softening:
canvas.style.filter =
`contrast(1.05) brightness(${crtFlickerValue}) saturate(1.05) blur(0.4px) drop-shadow(0 0 1px #fff5)`;
}
}
}
crtFlickerFrame = requestAnimationFrame(crtFlicker);
} catch (e) {
console.error("[CRT Script] Error in crtFlicker:", e);
}
}
function startCrtFlicker() {
// Enables CRT flicker and ensures CRT overlays/styles are present.
try {
const vmDisplay = getVmDisplay();
if (vmDisplay) {
ensureCRTEffects(vmDisplay);
}
crtFrameCount = 0;
crtFlickerValue = 1;
crtFlicker();
console.log("[CRT Script] CRT flicker started.");
} catch (e) {
console.error("[CRT Script] Error in startCrtFlicker:", e);
}
}
function stopCrtFlicker() {
// Disables the CRT flicker animation and resets frame reference.
try {
if (typeof crtFlickerFrame !== "undefined") {
cancelAnimationFrame(crtFlickerFrame);
crtFlickerFrame = undefined;
console.log("[CRT Script] CRT flicker stopped.");
}
} catch (e) {
console.error("[CRT Script] Error in stopCrtFlicker:", e);
}
}
function addCrtBtn() {
// Adds the CRT toggle button to the UI and manages its behavior.
try {
const btns = document.getElementById("btns");
if (!btns) {
// Throttle log spam
if (!addCrtBtn.lastLog || Date.now() - addCrtBtn.lastLog > 2000) {
console.log("[CRT Script] #btns not found, will try again.");
addCrtBtn.lastLog = Date.now();
}
return;
}
let wrapper = document.getElementById("crtBtnWrapper");
if (!wrapper) {
wrapper = document.createElement("div");
wrapper.id = "crtBtnWrapper";
wrapper.style.display = "inline-block";
// Create the CRT button
const btn = document.createElement("button");
btn.id = "crtBtn";
btn.className = "btn btn-secondary";
btn.style.display = "inline";
btn.style.margin = "0";
btn.textContent = retroEffect ? "🖥️ Normal Mode" : "📺 CRT Mode";
btn.onclick = function () {
retroEffect = !retroEffect;
btn.textContent = retroEffect ? "🖥️ Normal Mode" : "📺 CRT Mode";
const vmDisplay = getVmDisplay();
if (!vmDisplay) return;
vmDisplay.classList.toggle("crt-canvas", retroEffect);
if (retroEffect) {
startCrtFlicker();
} else {
stopCrtFlicker();
const canvas = vmDisplay.querySelector("canvas");
if (canvas) canvas.style.removeProperty("filter");
["crt-bulge", "crt-scanlines", "crt-dotmask", "crt-phosphor"].forEach(cls => {
const el = vmDisplay.querySelector(`.${cls}`);
if (el) el.remove();
});
}
};
wrapper.appendChild(btn);
btns.insertBefore(wrapper, btns.firstChild);
} else {
// Update button text to match state if already exists
const btn = wrapper.querySelector("#crtBtn");
if (btn) btn.textContent = retroEffect ? "🖥️ Normal Mode" : "📺 CRT Mode";
}
} catch (e) {
console.error("[CRT Script] Error in addCrtBtn:", e);
}
}
addCrtBtn();
})();