<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>密技 on cheukblog</title>
    <link>/tags/%E5%AF%86%E6%8A%80/</link>
    <description>Recent content in 密技 on cheukblog</description>
    <generator>Hugo</generator>
    <language>zh-Hant</language>
    <lastBuildDate>Mon, 24 Aug 2026 15:05:41 +0800</lastBuildDate>
    <atom:link href="/tags/%E5%AF%86%E6%8A%80/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>把 YouTube 擋掉</title>
      <link>/posts/yt-ublock/</link>
      <pubDate>Mon, 24 Aug 2026 15:05:41 +0800</pubDate>
      <guid>/posts/yt-ublock/</guid>
      <description>&lt;p&gt;NewPipe 要沒落了，可是手機的網頁版 YouTube 又很爛，所以用了一段時間用用研究擋掉所有分心的東西。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Untrap for Youtube&lt;/li&gt;&#xA;&lt;li&gt;SponsorBlock&lt;/li&gt;&#xA;&lt;li&gt;uBlock 在 Custom Filters 寫這兩個&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;m.youtube.com##ytm-fullscreen-related-videos-entry-point-view-model&#xA;m.youtube.com##.ytFullscreenVideoRecommendationsHost&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把這個去掉 -&amp;gt; &lt;a href=&#34;/images/ublock.jpeg&#34;&gt;圖片&lt;/a&gt;&lt;br&gt;&#xA;然後就可以時間浪費其他不是在演算法上的影片了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>中文文案排版指北</title>
      <link>/posts/chinese-copywriting-guidelines/</link>
      <pubDate>Tue, 11 Aug 2026 18:29:28 +0800</pubDate>
      <guid>/posts/chinese-copywriting-guidelines/</guid>
      <description>&lt;p&gt;在 Youtube 或其他網站上常看到不好的中文排版，所以如果你看到，請五分鐘看完&lt;a href=&#34;https://github.com/sparanoid/chinese-copywriting-guidelines&#34;&gt;這篇&lt;/a&gt;，個人打字潔淨度直接上升 87%，考試每次都拿一百分。&lt;/p&gt;</description>
    </item>
    <item>
      <title>YT Music sidechain</title>
      <link>/posts/yt-music-sidechain/</link>
      <pubDate>Tue, 11 Aug 2026 18:20:33 +0800</pubDate>
      <guid>/posts/yt-music-sidechain/</guid>
      <description>&lt;p&gt;Youtube 看教學但又不想音樂直接蓋過人聲，所以用 Gemini 寫了個音樂 sidechain 的 script。&lt;/p&gt;&#xA;&lt;p&gt;現在學 C++ 可以有背景音樂，爽。&lt;/p&gt;&#xA;&lt;p&gt;Script -&amp;gt; &lt;a href=&#34;https://privatebin.net/?2c089873bcacb051#69uB1Uqt4zsVTUwjeU1P5xr1Qxy4wnxoqr6AMDtmrrtZ&#34;&gt;點我&lt;/a&gt;（失效）&lt;/p&gt;&#xA;&lt;p&gt;更：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// ==UserScript==&#xA;// @name         Universal Audio Sidechain with UI (v6.3 Multi-Tab Stabilized)&#xA;// @namespace    http://tampermonkey.net/&#xA;// @version      6.3&#xA;// @description  Hijacks native audio pipelines with sidechain ducking. Multi-tab registry prevents volume pumping.&#xA;// @author       You&#xA;// @match        *://*/*&#xA;// @grant        GM_setValue&#xA;// @grant        GM_getValue&#xA;// @grant        GM_addValueChangeListener&#xA;// @run-at       document-start&#xA;// ==/UserScript==&#xA;&#xA;(function () {&#xA;  &amp;#39;use strict&amp;#39;;&#xA;&#xA;  // Helper to safely parse localStorage values&#xA;  function getStoredFloat(key, defaultValue) {&#xA;      const val = localStorage.getItem(key);&#xA;      if (val === null) return defaultValue;&#xA;      const parsed = parseFloat(val);&#xA;      return isNaN(parsed) ? defaultValue : parsed;&#xA;  }&#xA;&#xA;  // Generate unique ID for this browser tab instance&#xA;  const TAB_ID = &amp;#39;tab_&amp;#39; + Math.random().toString(36).substring(2, 9);&#xA;&#xA;  // =========================================================================&#xA;  // USER CONFIGURATION (DEFAULT SETTINGS)&#xA;  // =========================================================================&#xA;  const CONFIG = {&#xA;    ENABLED: true,&#xA;    DUCK_VOLUME: getStoredFloat(&amp;#39;sc_duck_volume&amp;#39;, 0.10),&#xA;    ATTACK_TIME: getStoredFloat(&amp;#39;sc_attack_time&amp;#39;, 0.05),&#xA;    RELEASE_TIME: getStoredFloat(&amp;#39;sc_release_time&amp;#39;, 0.60),&#xA;&#xA;    CARRIER_DOMAINS: [&#xA;        &amp;#39;www.youtube.com&amp;#39;,&#xA;        &amp;#39;twitch.tv&amp;#39;,&#xA;        &amp;#39;bilibili.com&amp;#39;&#xA;    ],&#xA;    MODULATED_DOMAINS: [&#xA;        &amp;#39;music.youtube.com&amp;#39;,&#xA;        &amp;#39;app.chillhop.com&amp;#39;,&#xA;        &amp;#39;open.spotify.com&amp;#39;,&#xA;        &amp;#39;soundcloud.com&amp;#39;,&#xA;        &amp;#39;music.apple.com&amp;#39;&#xA;    ]&#xA;  };&#xA;  // =========================================================================&#xA;&#xA;  const currentHost = window.location.hostname;&#xA;  const isCarrier = CONFIG.CARRIER_DOMAINS.some(domain =&amp;gt; currentHost.includes(domain));&#xA;  const isModulated = CONFIG.MODULATED_DOMAINS.some(domain =&amp;gt; currentHost.includes(domain));&#xA;&#xA;  if (!isCarrier &amp;amp;&amp;amp; !isModulated) return;&#xA;&#xA;  // ==========================================&#xA;  // 1. CARRIER BEHAVIOR (Sender - Tab Registry System)&#xA;  // ==========================================&#xA;  if (isCarrier) {&#xA;    window.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, () =&amp;gt; {&#xA;      let lastTriggerState = false;&#xA;      let lastWriteTime = 0;&#xA;      let triggerHoldTimer = 0;&#xA;&#xA;      // Clean up tab registration on exit&#xA;      window.addEventListener(&amp;#39;beforeunload&amp;#39;, () =&amp;gt; {&#xA;        const carriers = GM_getValue(&amp;#39;sc_active_carriers&amp;#39;) || {};&#xA;        delete carriers[TAB_ID];&#xA;        GM_setValue(&amp;#39;sc_active_carriers&amp;#39;, carriers);&#xA;      });&#xA;&#xA;      setInterval(() =&amp;gt; {&#xA;        const mediaElems = document.querySelectorAll(&amp;#39;video, audio&amp;#39;);&#xA;        let isPlaying = false;&#xA;&#xA;        for (const media of mediaElems) {&#xA;          // Filter out tiny thumbnail preview elements (e.g. YouTube hover videos)&#xA;          const isMiniPreview = media.offsetWidth &amp;gt; 0 &amp;amp;&amp;amp; media.offsetWidth &amp;lt; 180 &amp;amp;&amp;amp; media.offsetHeight &amp;lt; 180;&#xA;          if (!isMiniPreview &amp;amp;&amp;amp; !media.paused &amp;amp;&amp;amp; !media.ended &amp;amp;&amp;amp; media.readyState &amp;gt; 2) {&#xA;            isPlaying = true;&#xA;            break;&#xA;          }&#xA;        }&#xA;&#xA;        const now = Date.now();&#xA;        if (isPlaying) {&#xA;          triggerHoldTimer = now;&#xA;        }&#xA;&#xA;        // 800ms hold time prevents volume pumping during video buffering or seek transitions&#xA;        const isTriggered = isPlaying || (now - triggerHoldTimer &amp;lt; 800);&#xA;&#xA;        if (isTriggered !== lastTriggerState || now - lastWriteTime &amp;gt; 400) {&#xA;          const carriers = GM_getValue(&amp;#39;sc_active_carriers&amp;#39;) || {};&#xA;&#xA;          if (isTriggered) {&#xA;            carriers[TAB_ID] = now;&#xA;          } else {&#xA;            delete carriers[TAB_ID];&#xA;          }&#xA;&#xA;          // Prune stale tabs (&amp;gt;3000ms inactive)&#xA;          for (const id in carriers) {&#xA;            if (now - carriers[id] &amp;gt; 3000) {&#xA;              delete carriers[id];&#xA;            }&#xA;          }&#xA;&#xA;          GM_setValue(&amp;#39;sc_active_carriers&amp;#39;, carriers);&#xA;          lastTriggerState = isTriggered;&#xA;          lastWriteTime = now;&#xA;        }&#xA;      }, 50);&#xA;    });&#xA;  }&#xA;&#xA;  // ==========================================&#xA;  // 2. MODULATED BEHAVIOR (Target Receiver + UI)&#xA;  // ==========================================&#xA;  if (isModulated) {&#xA;&#xA;    const injectCode = `&#xA;      (function(DUCK_VOL, ATTACK, RELEASE, INITIAL_ENABLE) {&#xA;        let duckTarget = 1.0;&#xA;        let duckCurrent = 1.0;&#xA;        let configDuckVol = DUCK_VOL;&#xA;        let configAttack = ATTACK;&#xA;        let configRelease = RELEASE;&#xA;        let isEnabled = INITIAL_ENABLE;&#xA;&#xA;        const htmlMedia = new Set();&#xA;        const webAudioGains = new Set();&#xA;        let activeAnalyser = null;&#xA;        const historyScope = new Array(200).fill(1.0);&#xA;&#xA;        // [Engine 1]: HTMLMediaElement Hijack&#xA;        const origVolDesc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, &amp;#39;volume&amp;#39;);&#xA;        if (origVolDesc) {&#xA;            Object.defineProperty(HTMLMediaElement.prototype, &amp;#39;volume&amp;#39;, {&#xA;                get() {&#xA;                    return this._userBaseVol !== undefined ? this._userBaseVol : origVolDesc.get.call(this);&#xA;                },&#xA;                set(v) {&#xA;                    this._userBaseVol = v;&#xA;                    origVolDesc.set.call(this, v * (isEnabled ? duckCurrent : 1.0));&#xA;                }&#xA;            });&#xA;            const origPlay = HTMLMediaElement.prototype.play;&#xA;            HTMLMediaElement.prototype.play = function() {&#xA;                htmlMedia.add(this);&#xA;                if (this._userBaseVol === undefined) this._userBaseVol = origVolDesc.get.call(this);&#xA;                origVolDesc.set.call(this, this._userBaseVol * (isEnabled ? duckCurrent : 1.0));&#xA;                return origPlay.apply(this, arguments);&#xA;            };&#xA;        }&#xA;&#xA;        // [Engine 2]: Web Audio API Hijack&#xA;        const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;&#xA;        if (OriginalAudioContext) {&#xA;            window.AudioContext = new Proxy(OriginalAudioContext, {&#xA;                construct(target, args) {&#xA;                    const ctx = new target(...args);&#xA;                    const masterGain = ctx.createGain();&#xA;                    const analyser = ctx.createAnalyser();&#xA;                    analyser.fftSize = 512;&#xA;                    activeAnalyser = analyser;&#xA;&#xA;                    masterGain.gain.value = isEnabled ? duckCurrent : 1.0;&#xA;                    masterGain.connect(analyser);&#xA;                    analyser.connect(ctx.destination);&#xA;                    webAudioGains.add(masterGain);&#xA;&#xA;                    Object.defineProperty(ctx, &amp;#39;destination&amp;#39;, { get: () =&amp;gt; masterGain });&#xA;                    return ctx;&#xA;                }&#xA;            });&#xA;            window.webkitAudioContext = window.AudioContext;&#xA;        }&#xA;&#xA;        // [DSP &amp;amp; Animation Loop]&#xA;        let lastTime = performance.now();&#xA;        function loop() {&#xA;            const time = performance.now();&#xA;            const dt = Math.min((time - lastTime) / 1000, 0.1);&#xA;            lastTime = time;&#xA;&#xA;            const activeTarget = isEnabled ? duckTarget : 1.0;&#xA;&#xA;            // Math precision snapping prevents floating-point volume jitter&#xA;            if (Math.abs(duckCurrent - activeTarget) &amp;gt; 0.001) {&#xA;                const step = activeTarget &amp;lt; duckCurrent&#xA;                    ? (1 / Math.max(0.01, configAttack)) * dt&#xA;                    : (1 / Math.max(0.01, configRelease)) * dt;&#xA;&#xA;                if (activeTarget &amp;lt; duckCurrent) {&#xA;                    duckCurrent = Math.max(activeTarget, duckCurrent - step);&#xA;                } else {&#xA;                    duckCurrent = Math.min(activeTarget, duckCurrent + step);&#xA;                }&#xA;&#xA;                applyGainToAll(duckCurrent);&#xA;            } else if (duckCurrent !== activeTarget) {&#xA;                duckCurrent = activeTarget;&#xA;                applyGainToAll(duckCurrent);&#xA;            }&#xA;&#xA;            historyScope.push(duckCurrent);&#xA;            if (historyScope.length &amp;gt; 200) historyScope.shift();&#xA;&#xA;            renderVisualizers();&#xA;        }&#xA;&#xA;        function applyGainToAll(gainVal) {&#xA;            htmlMedia.forEach(m =&amp;gt; {&#xA;                if(m._userBaseVol !== undefined) {&#xA;                    origVolDesc.set.call(m, m._userBaseVol * gainVal);&#xA;                }&#xA;            });&#xA;&#xA;            webAudioGains.forEach(g =&amp;gt; {&#xA;                if(g.context.state !== &amp;#39;closed&amp;#39;) g.gain.value = gainVal;&#xA;            });&#xA;        }&#xA;&#xA;        function renderVisualizers() {&#xA;            const cAdsr = document.getElementById(&amp;#39;sc-canvas-adsr&amp;#39;);&#xA;            if (cAdsr) {&#xA;                const ctx = cAdsr.getContext(&amp;#39;2d&amp;#39;);&#xA;                const w = cAdsr.width;&#xA;                const h = cAdsr.height;&#xA;                ctx.clearRect(0, 0, w, h);&#xA;&#xA;                ctx.strokeStyle = &amp;#39;#222&amp;#39;;&#xA;                ctx.lineWidth = 1;&#xA;                ctx.beginPath();&#xA;                ctx.moveTo(0, h * 0.5); ctx.lineTo(w, h * 0.5);&#xA;                ctx.stroke();&#xA;&#xA;                const pad = 8;&#xA;                const totalDur = configAttack + 0.3 + configRelease;&#xA;                const xAttack = pad + (configAttack / totalDur) * (w - pad * 2);&#xA;                const xSustain = xAttack + (0.3 / totalDur) * (w - pad * 2);&#xA;                const xRelease = w - pad;&#xA;&#xA;                const yMax = pad;&#xA;                const yDuck = pad + (1 - configDuckVol) * (h - pad * 2);&#xA;&#xA;                ctx.strokeStyle = &amp;#39;#00d2ff&amp;#39;;&#xA;                ctx.lineWidth = 2;&#xA;                ctx.beginPath();&#xA;                ctx.moveTo(pad, yMax);&#xA;                ctx.lineTo(xAttack, yDuck);&#xA;                ctx.lineTo(xSustain, yDuck);&#xA;                ctx.lineTo(xRelease, yMax);&#xA;                ctx.stroke();&#xA;&#xA;                let dotX = pad;&#xA;                let dotY = pad + (1 - duckCurrent) * (h - pad * 2);&#xA;&#xA;                if (duckCurrent &amp;lt; 0.99) {&#xA;                    if (duckTarget &amp;lt; 1.0) {&#xA;                        dotX = pad + (1 - (duckCurrent - configDuckVol) / Math.max(0.001, 1 - configDuckVol)) * (xAttack - pad);&#xA;                    } else {&#xA;                        dotX = xSustain + ((duckCurrent - configDuckVol) / Math.max(0.001, 1 - configDuckVol)) * (xRelease - xSustain);&#xA;                    }&#xA;                }&#xA;&#xA;                ctx.fillStyle = &amp;#39;#ff0055&amp;#39;;&#xA;                ctx.beginPath();&#xA;                ctx.arc(Math.min(w - pad, Math.max(pad, dotX)), dotY, 4, 0, Math.PI * 2);&#xA;                ctx.fill();&#xA;            }&#xA;&#xA;            const cScope = document.getElementById(&amp;#39;sc-canvas-scope&amp;#39;);&#xA;            if (cScope) {&#xA;                const ctx = cScope.getContext(&amp;#39;2d&amp;#39;);&#xA;                const w = cScope.width;&#xA;                const h = cScope.height;&#xA;                ctx.fillStyle = &amp;#39;#0a0a0a&amp;#39;;&#xA;                ctx.fillRect(0, 0, w, h);&#xA;&#xA;                ctx.lineWidth = 1.5;&#xA;                ctx.strokeStyle = &amp;#39;#00ff66&amp;#39;;&#xA;                ctx.shadowColor = &amp;#39;#00ff66&amp;#39;;&#xA;                ctx.shadowBlur = 4;&#xA;                ctx.beginPath();&#xA;&#xA;                if (activeAnalyser &amp;amp;&amp;amp; activeAnalyser.context.state === &amp;#39;running&amp;#39;) {&#xA;                    const bufferLength = activeAnalyser.frequencyBinCount;&#xA;                    const dataArray = new Uint8Array(bufferLength);&#xA;                    activeAnalyser.getByteTimeDomainData(dataArray);&#xA;&#xA;                    const sliceWidth = w / bufferLength;&#xA;                    let x = 0;&#xA;                    for (let i = 0; i &amp;lt; bufferLength; i++) {&#xA;                        const v = dataArray[i] / 128.0;&#xA;                        const y = (v * h) / 2;&#xA;                        if (i === 0) ctx.moveTo(x, y);&#xA;                        else ctx.lineTo(x, y);&#xA;                        x += sliceWidth;&#xA;                    }&#xA;                } else {&#xA;                    const sliceWidth = w / historyScope.length;&#xA;                    let x = 0;&#xA;                    for (let i = 0; i &amp;lt; historyScope.length; i++) {&#xA;                        const y = h - (historyScope[i] * (h - 10) + 5);&#xA;                        if (i === 0) ctx.moveTo(x, y);&#xA;                        else ctx.lineTo(x, y);&#xA;                        x += sliceWidth;&#xA;                    }&#xA;                }&#xA;                ctx.stroke();&#xA;                ctx.shadowBlur = 0;&#xA;            }&#xA;        }&#xA;&#xA;        setInterval(loop, 16);&#xA;&#xA;        window.addEventListener(&amp;#39;message&amp;#39;, e =&amp;gt; {&#xA;            if (e.source !== window || !e.data) return;&#xA;            if (e.data.type === &amp;#39;SC_DUCK&amp;#39;) duckTarget = configDuckVol;&#xA;            if (e.data.type === &amp;#39;SC_RESTORE&amp;#39;) duckTarget = 1.0;&#xA;            if (e.data.type === &amp;#39;SC_CONFIG_UPDATE&amp;#39;) {&#xA;                configDuckVol = e.data.duckVol;&#xA;                configAttack = e.data.attack;&#xA;                configRelease = e.data.release;&#xA;                isEnabled = e.data.enabled;&#xA;                if (duckTarget &amp;lt; 1.0) duckTarget = configDuckVol;&#xA;            }&#xA;        });&#xA;      })(${CONFIG.DUCK_VOLUME}, ${CONFIG.ATTACK_TIME}, ${CONFIG.RELEASE_TIME}, ${CONFIG.ENABLED});&#xA;    `;&#xA;&#xA;    const script = document.createElement(&amp;#39;script&amp;#39;);&#xA;    script.textContent = injectCode;&#xA;    document.documentElement.appendChild(script);&#xA;    script.remove();&#xA;&#xA;    // UI Control Panel&#xA;    function renderControlUI() {&#xA;      if (document.getElementById(&amp;#39;sc-ui-panel&amp;#39;)) return;&#xA;&#xA;      const panel = document.createElement(&amp;#39;div&amp;#39;);&#xA;      panel.id = &amp;#39;sc-ui-panel&amp;#39;;&#xA;      panel.style.cssText = `&#xA;        position: fixed; bottom: 20px; left: 20px; z-index: 999999;&#xA;        background: rgba(18, 18, 18, 0.94); color: #fff;&#xA;        padding: 12px 16px; border-radius: 10px;&#xA;        border: 1px solid rgba(255, 255, 255, 0.15);&#xA;        font-family: -apple-system, BlinkMacSystemFont, &amp;#34;Segoe UI&amp;#34;, Roboto, sans-serif;&#xA;        font-size: 12px; width: 240px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);&#xA;        backdrop-filter: blur(8px); pointer-events: auto; user-select: none;&#xA;      `;&#xA;&#xA;      panel.innerHTML = `&#xA;        &amp;lt;div style=&amp;#34;display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.1); padding-bottom:6px;&amp;#34;&amp;gt;&#xA;          &amp;lt;span style=&amp;#34;font-weight:700; color:#00d2ff; display:flex; align-items:center; gap:5px;&amp;#34;&amp;gt;🎛️ Sidechain DSP&amp;lt;/span&amp;gt;&#xA;          &amp;lt;span id=&amp;#34;sc-toggle-btn&amp;#34; style=&amp;#34;cursor:pointer; color:#888; font-size:11px;&amp;#34;&amp;gt;[Collapse]&amp;lt;/span&amp;gt;&#xA;        &amp;lt;/div&amp;gt;&#xA;        &amp;lt;div id=&amp;#34;sc-body&amp;#34;&amp;gt;&#xA;          &amp;lt;div style=&amp;#34;display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;&amp;#34;&amp;gt;&#xA;            &amp;lt;label style=&amp;#34;color:#ccc;&amp;#34;&amp;gt;Enable Sidechain&amp;lt;/label&amp;gt;&#xA;            &amp;lt;input type=&amp;#34;checkbox&amp;#34; id=&amp;#34;sc-chk-enable&amp;#34; ${CONFIG.ENABLED ? &amp;#39;checked&amp;#39; : &amp;#39;&amp;#39;} style=&amp;#34;cursor:pointer;&amp;#34;&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div style=&amp;#34;margin-bottom:8px;&amp;#34;&amp;gt;&#xA;            &amp;lt;div style=&amp;#34;display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;&amp;#34;&amp;gt;&#xA;              &amp;lt;span&amp;gt;Duck Volume:&amp;lt;/span&amp;gt;&#xA;              &amp;lt;span id=&amp;#34;sc-val-duck&amp;#34;&amp;gt;${Math.round(CONFIG.DUCK_VOLUME * 100)}%&amp;lt;/span&amp;gt;&#xA;            &amp;lt;/div&amp;gt;&#xA;            &amp;lt;input type=&amp;#34;range&amp;#34; id=&amp;#34;sc-rng-duck&amp;#34; min=&amp;#34;0&amp;#34; max=&amp;#34;1&amp;#34; step=&amp;#34;0.01&amp;#34; value=&amp;#34;${CONFIG.DUCK_VOLUME}&amp;#34; style=&amp;#34;width:100%; cursor:pointer; accent-color:#00d2ff;&amp;#34;&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div style=&amp;#34;margin-bottom:8px;&amp;#34;&amp;gt;&#xA;            &amp;lt;div style=&amp;#34;display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;&amp;#34;&amp;gt;&#xA;              &amp;lt;span&amp;gt;Attack:&amp;lt;/span&amp;gt;&#xA;              &amp;lt;span id=&amp;#34;sc-val-attack&amp;#34;&amp;gt;${Math.round(CONFIG.ATTACK_TIME * 1000)}ms&amp;lt;/span&amp;gt;&#xA;            &amp;lt;/div&amp;gt;&#xA;            &amp;lt;input type=&amp;#34;range&amp;#34; id=&amp;#34;sc-rng-attack&amp;#34; min=&amp;#34;0.01&amp;#34; max=&amp;#34;1&amp;#34; step=&amp;#34;0.01&amp;#34; value=&amp;#34;${CONFIG.ATTACK_TIME}&amp;#34; style=&amp;#34;width:100%; cursor:pointer; accent-color:#00d2ff;&amp;#34;&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div style=&amp;#34;margin-bottom:8px;&amp;#34;&amp;gt;&#xA;            &amp;lt;div style=&amp;#34;display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;&amp;#34;&amp;gt;&#xA;              &amp;lt;span&amp;gt;Release:&amp;lt;/span&amp;gt;&#xA;              &amp;lt;span id=&amp;#34;sc-val-release&amp;#34;&amp;gt;${Math.round(CONFIG.RELEASE_TIME * 1000)}ms&amp;lt;/span&amp;gt;&#xA;            &amp;lt;/div&amp;gt;&#xA;            &amp;lt;input type=&amp;#34;range&amp;#34; id=&amp;#34;sc-rng-release&amp;#34; min=&amp;#34;0.05&amp;#34; max=&amp;#34;2&amp;#34; step=&amp;#34;0.05&amp;#34; value=&amp;#34;${CONFIG.RELEASE_TIME}&amp;#34; style=&amp;#34;width:100%; cursor:pointer; accent-color:#00d2ff;&amp;#34;&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div style=&amp;#34;margin-top:10px; margin-bottom:6px;&amp;#34;&amp;gt;&#xA;            &amp;lt;div style=&amp;#34;color:#aaa; font-size:10px; margin-bottom:2px;&amp;#34;&amp;gt;Envelope ADSR Curve:&amp;lt;/div&amp;gt;&#xA;            &amp;lt;canvas id=&amp;#34;sc-canvas-adsr&amp;#34; width=&amp;#34;240&amp;#34; height=&amp;#34;45&amp;#34; style=&amp;#34;background:#111; border-radius:4px; border:1px solid #333; display:block;&amp;#34;&amp;gt;&amp;lt;/canvas&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div style=&amp;#34;margin-top:6px; margin-bottom:6px;&amp;#34;&amp;gt;&#xA;            &amp;lt;div style=&amp;#34;color:#aaa; font-size:10px; margin-bottom:2px;&amp;#34;&amp;gt;Oscilloscope:&amp;lt;/div&amp;gt;&#xA;            &amp;lt;canvas id=&amp;#34;sc-canvas-scope&amp;#34; width=&amp;#34;240&amp;#34; height=&amp;#34;45&amp;#34; style=&amp;#34;background:#0a0a0a; border-radius:4px; border:1px solid #333; display:block;&amp;#34;&amp;gt;&amp;lt;/canvas&amp;gt;&#xA;          &amp;lt;/div&amp;gt;&#xA;&#xA;          &amp;lt;div id=&amp;#34;sc-status&amp;#34; style=&amp;#34;font-size:10px; text-align:right; color:#777; margin-top:6px; font-weight:600;&amp;#34;&amp;gt;&#xA;            Status: Idle&#xA;          &amp;lt;/div&amp;gt;&#xA;        &amp;lt;/div&amp;gt;&#xA;      `;&#xA;&#xA;      document.body.appendChild(panel);&#xA;&#xA;      const chkEnable = document.getElementById(&amp;#39;sc-chk-enable&amp;#39;);&#xA;      const rngDuck = document.getElementById(&amp;#39;sc-rng-duck&amp;#39;);&#xA;      const rngAttack = document.getElementById(&amp;#39;sc-rng-attack&amp;#39;);&#xA;      const rngRelease = document.getElementById(&amp;#39;sc-rng-release&amp;#39;);&#xA;      const toggleBtn = document.getElementById(&amp;#39;sc-toggle-btn&amp;#39;);&#xA;      const scBody = document.getElementById(&amp;#39;sc-body&amp;#39;);&#xA;&#xA;      let isCollapsed = false;&#xA;      toggleBtn.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {&#xA;        isCollapsed = !isCollapsed;&#xA;        scBody.style.display = isCollapsed ? &amp;#39;none&amp;#39; : &amp;#39;block&amp;#39;;&#xA;        toggleBtn.innerText = isCollapsed ? &amp;#39;[Expand]&amp;#39; : &amp;#39;[Collapse]&amp;#39;;&#xA;      });&#xA;&#xA;      const updateConfig = () =&amp;gt; {&#xA;        CONFIG.ENABLED = chkEnable.checked;&#xA;        CONFIG.DUCK_VOLUME = parseFloat(rngDuck.value);&#xA;        CONFIG.ATTACK_TIME = parseFloat(rngAttack.value);&#xA;        CONFIG.RELEASE_TIME = parseFloat(rngRelease.value);&#xA;&#xA;        localStorage.setItem(&amp;#39;sc_duck_volume&amp;#39;, CONFIG.DUCK_VOLUME);&#xA;        localStorage.setItem(&amp;#39;sc_attack_time&amp;#39;, CONFIG.ATTACK_TIME);&#xA;        localStorage.setItem(&amp;#39;sc_release_time&amp;#39;, CONFIG.RELEASE_TIME);&#xA;&#xA;        document.getElementById(&amp;#39;sc-val-duck&amp;#39;).innerText = Math.round(CONFIG.DUCK_VOLUME * 100) + &amp;#39;%&amp;#39;;&#xA;        document.getElementById(&amp;#39;sc-val-attack&amp;#39;).innerText = Math.round(CONFIG.ATTACK_TIME * 1000) + &amp;#39;ms&amp;#39;;&#xA;        document.getElementById(&amp;#39;sc-val-release&amp;#39;).innerText = Math.round(CONFIG.RELEASE_TIME * 1000) + &amp;#39;ms&amp;#39;;&#xA;&#xA;        window.postMessage({&#xA;          type: &amp;#39;SC_CONFIG_UPDATE&amp;#39;,&#xA;          enabled: CONFIG.ENABLED,&#xA;          duckVol: CONFIG.DUCK_VOLUME,&#xA;          attack: CONFIG.ATTACK_TIME,&#xA;          release: CONFIG.RELEASE_TIME&#xA;        }, &amp;#39;*&amp;#39;);&#xA;      };&#xA;&#xA;      [chkEnable, rngDuck, rngAttack, rngRelease].forEach(el =&amp;gt; el.addEventListener(&amp;#39;input&amp;#39;, updateConfig));&#xA;    }&#xA;&#xA;    window.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, () =&amp;gt; {&#xA;      renderControlUI();&#xA;      setInterval(() =&amp;gt; {&#xA;        if (!document.getElementById(&amp;#39;sc-ui-panel&amp;#39;)) renderControlUI();&#xA;      }, 2000);&#xA;    });&#xA;&#xA;    let localTriggerState = false;&#xA;&#xA;    function checkActiveCarriers(carriers) {&#xA;      if (!carriers || typeof carriers !== &amp;#39;object&amp;#39;) return false;&#xA;      const now = Date.now();&#xA;      for (const id in carriers) {&#xA;        if (now - carriers[id] &amp;lt; 3000) {&#xA;          return true; // Active playing carrier tab exists&#xA;        }&#xA;      }&#xA;      return false;&#xA;    }&#xA;&#xA;    function updateStatusUI(isTriggered) {&#xA;      const statusEl = document.getElementById(&amp;#39;sc-status&amp;#39;);&#xA;      if (statusEl) {&#xA;        if (!CONFIG.ENABLED) {&#xA;            statusEl.innerText = &amp;#39;Status: Disabled&amp;#39;;&#xA;            statusEl.style.color = &amp;#39;#777&amp;#39;;&#xA;        } else if (isTriggered) {&#xA;            statusEl.innerText = &amp;#39;Status: 🔊 Ducking Audio&amp;#39;;&#xA;            statusEl.style.color = &amp;#39;#00d2ff&amp;#39;;&#xA;        } else {&#xA;            statusEl.innerText = &amp;#39;Status: 🎵 Normal Output&amp;#39;;&#xA;            statusEl.style.color = &amp;#39;#888&amp;#39;;&#xA;        }&#xA;      }&#xA;    }&#xA;&#xA;    GM_addValueChangeListener(&amp;#39;sc_active_carriers&amp;#39;, (name, oldValue, newValue) =&amp;gt; {&#xA;      const isTriggered = checkActiveCarriers(newValue);&#xA;&#xA;      if (isTriggered !== localTriggerState) {&#xA;          window.postMessage({ type: isTriggered ? &amp;#39;SC_DUCK&amp;#39; : &amp;#39;SC_RESTORE&amp;#39; }, &amp;#39;*&amp;#39;);&#xA;          localTriggerState = isTriggered;&#xA;          updateStatusUI(isTriggered);&#xA;      }&#xA;    });&#xA;&#xA;    // Fallback registry heartbeat scan&#xA;    setInterval(() =&amp;gt; {&#xA;      const carriers = GM_getValue(&amp;#39;sc_active_carriers&amp;#39;);&#xA;      const isTriggered = checkActiveCarriers(carriers);&#xA;&#xA;      if (isTriggered !== localTriggerState) {&#xA;        window.postMessage({ type: isTriggered ? &amp;#39;SC_DUCK&amp;#39; : &amp;#39;SC_RESTORE&amp;#39; }, &amp;#39;*&amp;#39;);&#xA;        localTriggerState = isTriggered;&#xA;        updateStatusUI(isTriggered);&#xA;      }&#xA;    }, 500);&#xA;  }&#xA;})();&#xA;&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
  </channel>
</rss>
