Kick & YouTube Night Boost

Real switch UI. Faded when idle, full on hover. OFF: no change, ON: gamma=0.7.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Kick & YouTube Night Boost
// @name:tr     Kick & YouTube Gece Görüş
// @description:tr  Gerçek switch görünümü. Beklemede yarı şeffaf, hover'da tam görünür. OFF: değişiklik yok, ON: gamma=0.7.
// @description  Real switch UI. Faded when idle, full on hover. OFF: no change, ON: gamma=0.7.
// @namespace   http://tampermonkey.net/
// @version     1.1
// @author      baris
// @match       *://*.kick.com/*
// @match       *://kick.com/*
// @match       *://*.youtube.com/*
// @match       *://youtube.com/*
// @match       *://youtu.be/*
// @grant       none
// @license     GNU GPLv3
// @run-at      document-idle
// ==/UserScript==

(() => {
  const STORAGE_KEY = 'nb.switch.gamma.enabled';
  const DEF_ENABLED = false;     // OFF by default
  const GAMMA_EXP   = 0.7;       // ON exponent
  const UI_BOTTOM_PX = 80;
  const UI_FALLBACK_LEFT = 340;  // if audio widget not found
  const SVG_ID = 'nb-switch-gamma-svg';
  const FILTER_ID = 'nb-switch-gamma-filter';
  const UI_HOST_ID = 'nb-switch-gamma-toggle-host';

  let enabled = loadEnabled();

  function loadEnabled() {
    try { const r = localStorage.getItem(STORAGE_KEY); if (r != null) return JSON.parse(r); } catch {}
    return DEF_ENABLED;
  }
  function saveEnabled() {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(enabled)); } catch {}
  }

  // Shadow DOM UI (no site CSS conflicts)
  function ensureUI() {
    if (document.getElementById(UI_HOST_ID)) return;

    const host = document.createElement('div');
    host.id = UI_HOST_ID;
    host.style.position = 'fixed';
    host.style.bottom = UI_BOTTOM_PX + 'px';
    host.style.left = guessLeftPosition() + 'px';
    host.style.zIndex = '2147483647';
    host.style.pointerEvents = 'auto';
    host.style.userSelect = 'none';
    document.body.appendChild(host);

    const root = host.attachShadow({ mode: 'open' });
    root.innerHTML = `
      <style>
        :host { all: initial; }
        .wrap {
          all: unset;
          display: inline-flex;
          align-items: center;
          gap: 8px;
          background: rgba(0,0,0,0.85);
          color: #fff;
          font: 11px/1 monospace;
          padding: 6px 10px;
          border-radius: 8px;
          border: 1px solid rgba(255,255,255,0.35);
          box-shadow: 0 2px 10px rgba(0,0,0,0.6);
          cursor: pointer;
          opacity: .12;                   /* idle: slightly transparent */
          transition: opacity .25s ease, transform .1s ease;
        }
        .wrap:hover { opacity: 1; }       /* hover: fully visible */

        .switch {
          width: 44px; height: 22px;
          border-radius: 999px;
          background: ${enabled ? '#3b82f6' : '#555'};
          position: relative;
          box-shadow: inset 0 0 0 1px rgba(255,255,255,0.15);
          transition: background .18s ease;
        }
        .knob {
          width: 18px; height: 18px;
          border-radius: 50%;
          background: #fff;
          position: absolute; top: 2px; left: ${enabled ? '24px' : '2px'};
          transition: left .18s ease;
          box-shadow: 0 1px 2px rgba(0,0,0,.4);
        }
        .label { margin: 0; }
      </style>
      <div class="wrap" id="wrap">
        <div class="switch" id="sw"><div class="knob" id="knob"></div></div>
        <div class="label">Gamma 0.7</div>
      </div>
    `;

    const wrap = root.getElementById('wrap');
    const sw   = root.getElementById('sw');
    const knob = root.getElementById('knob');

    function render() {
      sw.style.background = enabled ? '#3b82f6' : '#555';
      knob.style.left = enabled ? '24px' : '2px';
    }
    function toggle(on) {
      enabled = typeof on === 'boolean' ? on : !enabled;
      saveEnabled();
      render();
      wrap.style.transform = 'scale(0.96)';
      setTimeout(() => wrap.style.transform = 'scale(1)', 90);
      applyToAll();
    }

    wrap.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); toggle(); });
    wrap.addEventListener('contextmenu', e => { e.preventDefault(); toggle(false); });

    // track audio widget and keep our box to its right
    setInterval(() => {
      const left = guessLeftPosition();
      if (host.style.left !== left + 'px') host.style.left = left + 'px';
    }, 1500);

    render();
  }

  // Place to the right of "Maximizer by Barış" if present
  function guessLeftPosition() {
    try {
      const allDivs = document.querySelectorAll('div');
      for (const d of allDivs) {
        const txt = d.textContent || '';
        if (txt.includes('Maximizer by Barış')) {
          const rect = d.getBoundingClientRect();
          if (rect && rect.width) return Math.max(10, Math.round(rect.left + rect.width + 16));
        }
      }
    } catch {}
    return UI_FALLBACK_LEFT;
  }

  // Global SVG gamma filter (so CSS url(#id) resolves)
  function ensureSVG() {
    if (document.getElementById(SVG_ID)) return;
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('id', SVG_ID);
    svg.setAttribute('width', '0');
    svg.setAttribute('height', '0');
    svg.style.position = 'absolute';
    svg.style.left = '-9999px';
    svg.style.top = '-9999px';

    const defs   = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
    const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
    filter.setAttribute('id', FILTER_ID);

    const comp = document.createElementNS('http://www.w3.org/2000/svg', 'feComponentTransfer');
    const mk = tag => {
      const n = document.createElementNS('http://www.w3.org/2000/svg', tag);
      n.setAttribute('type', 'gamma');
      n.setAttribute('amplitude', '1');
      n.setAttribute('offset', '0');
      n.setAttribute('exponent', String(GAMMA_EXP));
      return n;
    };
    comp.appendChild(mk('feFuncR'));
    comp.appendChild(mk('feFuncG'));
    comp.appendChild(mk('feFuncB'));
    filter.appendChild(comp);
    defs.appendChild(filter);
    svg.appendChild(defs);
    document.documentElement.appendChild(svg);
  }

  function applyToVideo(video) {
    if (!video) return;
    if (!enabled) {
      if (video.dataset.nbsPrevFilter !== undefined) {
        video.style.filter = video.dataset.nbsPrevFilter;
        delete video.dataset.nbsPrevFilter;
        delete video.dataset.nbsApplied;
      }
      return;
    }
    ensureSVG();
    if (video.dataset.nbsPrevFilter === undefined) {
      video.dataset.nbsPrevFilter = video.style.filter || '';
    }
    const base = (video.dataset.nbsPrevFilter || '').trim();
    if (!/url\(#nb-switch-gamma-filter\)/.test(base)) {
      video.style.filter = `${base} url(#${FILTER_ID})`.trim();
    } else {
      video.style.filter = base;
    }
    video.dataset.nbsApplied = '1';
  }

  function applyToAll() {
    document.querySelectorAll('video').forEach(v => applyToVideo(v));
  }

  function init() {
    if (!document.body) return;
    ensureUI();
    applyToAll();

    // watch for new video elements
    const mo = new MutationObserver(() => {
      document.querySelectorAll('video').forEach(v => applyToVideo(v));
      if (!document.getElementById(UI_HOST_ID)) ensureUI();
    });
    mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
  }

  // YouTube SPA
  function ytHooks() {
    window.addEventListener('yt-navigate-finish', () => setTimeout(init, 500));
    window.addEventListener('popstate', () => setTimeout(init, 500));
  }

  // URL change poller
  let lastURL = location.href;
  setInterval(() => {
    if (location.href !== lastURL) {
      lastURL = location.href;
      setTimeout(init, 400);
    }
  }, 800);

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => { init(); if (location.hostname.includes('youtube.com')) ytHooks(); }, { once: true });
  } else {
    init();
    if (location.hostname.includes('youtube.com')) ytHooks();
  }
})();