CollabVM CRT Mode Button

Adds a toggleable CRT-style effect to the CollabVM display.

Per 20-06-2025. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();

})();