您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a toggleable CRT-style effect to the CollabVM display.
// ==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.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(); })();