您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
기존 기능 비활성화 + 커스텀 선명도 필터 UI 제공 (Web)
当前为
// ==UserScript== // @name Chzzk 선명한 화면 업그레이드 // @description 기존 기능 비활성화 + 커스텀 선명도 필터 UI 제공 (Web) // @namespace http://tampermonkey.net/ // @icon https://chzzk.naver.com/favicon.ico // @version 2.4 // @match https://chzzk.naver.com/* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (() => { 'use strict'; const STORAGE_KEY_ENABLED = 'chzzkSharpnessEnabled'; const STORAGE_KEY_INTENSITY = 'chzzkSharpnessIntensity'; const FILTER_ID = 'sharpnessFilter'; const SVG_ID = 'sharpnessSVGContainer'; const STYLE_ID = 'sharpnessStyle'; const MENU_SELECTOR = '.pzp-pc__settings'; const FILTER_ITEM_SELECTOR = '.pzp-pc-setting-intro-filter'; class SharpnessFilter extends EventTarget { #enabled = false; #intensity = parseFloat(localStorage.getItem(STORAGE_KEY_INTENSITY)) || 1; #svgContainer; #style; constructor() { super(); this.#svgContainer = this.#createSVG(); this.#style = this.#createStyle(); this.#style.media = 'none'; } get enabled() { return this.#enabled; } get intensity() { return this.#intensity; } #createSVG() { const div = document.createElement('div'); div.id = SVG_ID; div.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;"> <filter id="${FILTER_ID}"> <feConvolveMatrix order="3" divisor="1" kernelMatrix="" /> </filter> </svg>`; return div; } #createStyle() { const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` .pzp-pc .webplayer-internal-video { filter: url(#${FILTER_ID}) !important; } .sharp-slider { accent-color: var(--sharp-accent, #ccc); } `; return style; } #updateFilter() { const k = this.#intensity; const off = -((k - 1) / 4); const matrix = `0 ${off} 0 ${off} ${k} ${off} 0 ${off} 0`; this.#svgContainer.querySelector('feConvolveMatrix').setAttribute('kernelMatrix', matrix); } init() { document.body.append(this.#svgContainer); document.head.append(this.#style); this.#updateFilter(); if (localStorage.getItem(STORAGE_KEY_ENABLED) === 'true') this.enable(false); this.dispatchEvent(new Event('initialized')); } enable(persist = true) { if (this.#enabled) return; this.#enabled = true; this.#style.media = 'all'; if (persist) localStorage.setItem(STORAGE_KEY_ENABLED, 'true'); this.dispatchEvent(new Event('enabled')); } disable(persist = true) { if (!this.#enabled) return; this.#enabled = false; this.#style.media = 'none'; if (persist) localStorage.setItem(STORAGE_KEY_ENABLED, 'false'); this.dispatchEvent(new Event('disabled')); } toggle() { this.enabled ? this.disable() : this.enable(); this.dispatchEvent(new Event('toggle')); } setIntensity(value) { this.#intensity = value; this.#updateFilter(); localStorage.setItem(STORAGE_KEY_INTENSITY, String(value)); this.dispatchEvent(new Event('intensitychange')); } } const sharpness = new SharpnessFilter(); sharpness.init(); function preventBubblingFromOurUI(container) { container.querySelectorAll('.sharp-slider, .sharp-toggle-wrapper').forEach(el => { ['mousedown', 'click', 'keydown', 'input'].forEach(evt => { el.addEventListener(evt, e => { e.stopPropagation(); }); }); }); } function blockDefaultFilter(container) { container.addEventListener('click', e => { if (!e.target.closest('.sharp-toggle-wrapper, .sharp-slider')) { e.stopImmediatePropagation(); e.preventDefault(); } }, true); } function addMenuControls(menu) { if (menu.dataset.sharpEnhanceDone) return; menu.dataset.sharpEnhanceDone = 'true'; let container = menu.querySelector(FILTER_ITEM_SELECTOR); if (!container) { container = document.createElement('div'); container.className = 'pzp-ui-setting-home-item'; container.setAttribute('role', 'menuitem'); container.tabIndex = 0; menu.append(container); } container.innerHTML = ` <div class="pzp-ui-setting-home-item__top"> <div class="pzp-ui-setting-home-item__left"> <span class="pzp-ui-setting-home-item__label">선명한 화면</span> </div> <div class="pzp-ui-setting-home-item__right"> <div role="switch" class="pzp-ui-toggle sharp-toggle-wrapper" aria-label="샤프닝 필터 토글" aria-checked="${sharpness.enabled}" tabindex="0"> <input type="checkbox" class="pzp-ui-toggle__checkbox sharp-toggle" tabindex="-1"> <div class="pzp-ui-toggle__handle"></div> </div> </div> </div> <div class="pzp-ui-setting-home-item__bottom" style="padding:8px; display:flex; align-items:center; gap:8px;"> <label for="sharp-slider" class="visually-hidden">강도 조절</label> <input id="sharp-slider" type="range" min="1" max="3" step="0.1" class="sharp-slider" role="slider" aria-valuemin="1" aria-valuemax="3" aria-valuenow="${sharpness.intensity.toFixed(1)}" aria-valuetext="강도 ${sharpness.intensity.toFixed(1)} 배"> <span id="sharp-intensity-label">(${sharpness.intensity.toFixed(1)}x 배)</span> </div>`; const wrapper = container.querySelector('.sharp-toggle-wrapper'); const checkbox = container.querySelector('.sharp-toggle'); const slider = container.querySelector('#sharp-slider'); const label = container.querySelector('#sharp-intensity-label'); checkbox.checked = sharpness.enabled; wrapper.setAttribute('aria-checked', sharpness.enabled); slider.value = sharpness.intensity; slider.style.accentColor = sharpness.enabled ? '#00f889' : 'gray'; label.textContent = `(${sharpness.intensity.toFixed(1)}x 배)`; wrapper.addEventListener('click', () => sharpness.toggle()); wrapper.addEventListener('keydown', e => { if (['Enter',' '].includes(e.key)) { e.preventDefault(); sharpness.toggle(); } }); sharpness.addEventListener('enabled', () => { checkbox.checked = true; wrapper.setAttribute('aria-checked', 'true'); slider.style.accentColor = '#00f889'; }); sharpness.addEventListener('disabled', () => { checkbox.checked = false; wrapper.setAttribute('aria-checked', 'false'); slider.style.accentColor = 'gray'; }); sharpness.addEventListener('intensitychange', () => { label.textContent = `(${sharpness.intensity.toFixed(1)}x 배)`; }); slider.addEventListener('input', e => { const v = parseFloat(e.target.value); sharpness.setIntensity(v); slider.setAttribute('aria-valuenow', v.toFixed(1)); slider.setAttribute('aria-valuetext', `강도 ${v.toFixed(1)} 배`); label.textContent = `(${v.toFixed(1)}x 배)`; }); slider.addEventListener('keydown', e => { let v = sharpness.intensity; if (['ArrowRight','ArrowUp'].includes(e.key)) v = Math.min(v + 0.1, 3); else if (['ArrowLeft','ArrowDown'].includes(e.key)) v = Math.max(v - 0.1, 1); else return; e.preventDefault(); sharpness.setIntensity(v); slider.value = v; slider.setAttribute('aria-valuenow', v.toFixed(1)); slider.setAttribute('aria-valuetext', `강도 ${v.toFixed(1)} 배`); label.textContent = `(${v.toFixed(1)}x 배)`; }); preventBubblingFromOurUI(container); blockDefaultFilter(container); } const observer = new MutationObserver(muts => { for (const m of muts) for (const node of m.addedNodes) { if (!(node instanceof HTMLElement)) continue; const menu = node.matches(MENU_SELECTOR) ? node : node.querySelector(MENU_SELECTOR); menu && addMenuControls(menu); } }); observer.observe(document.body, { childList:true, subtree:true }); document.querySelector(MENU_SELECTOR) && addMenuControls(document.querySelector(MENU_SELECTOR)); console.log('%c[🔧 샤프닝 필터 v2.4 적용 완료]', 'color:skyblue;font-weight:bold;'); })();