Beautiful Scrollbar

Fast custom scrollbar with multi-container support, minimal dark settings UI, animated themes, hover-only option, idle thin indicator with delay, and instant quick-jump keys. ESC for settings. 1=top, 0=bottom, 2–9=% instant jump. Optional quick smooth scroll for bar clicks (keys stay instant).

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Beautiful Scrollbar
// @namespace    http://github.com/quantavil/beautiful-scrollbar
// @version      5.6.4
// @description  Fast custom scrollbar with multi-container support, minimal dark settings UI, animated themes, hover-only option, idle thin indicator with delay, and instant quick-jump keys. ESC for settings. 1=top, 0=bottom, 2–9=% instant jump. Optional quick smooth scroll for bar clicks (keys stay instant).
// @author       quantavil
// @license      MIT
// @match        *://*/*
// @run-at       document-start
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(() => {
  'use strict';

  // -------------------------------
  // Config + Themes
  // -------------------------------
  const MIN_THUMB = 30, RATIO = 0.95, Z = 9999999;
  const IDLE_DELAY = 1000; // ms — wait before collapsing to thin indicator

  // New fields per theme:
  // - anim    -> CSS animation shorthand (e.g., "bs-barber 2.2s linear infinite")
  // - bgSize  -> background-size for the thumb
  const THEMES = {
    none:   { name:'⚪ None',       track:'rgba(0,0,0,0.03)', thumb:'rgba(100,100,100,0.3)', hover:'rgba(100,100,100,0.45)', w:8,  radius:0,  glow:'none' },
    modern: { name:'✨ Modern',     track:'rgba(0,0,0,0.08)', thumb:'linear-gradient(135deg,#667eea,#764ba2)', hover:'linear-gradient(135deg,#764ba2,#667eea)', w:10, radius:10, glow:'0 2px 10px rgba(0,0,0,.25)' },
    neon:   { name:'🌟 Neon Glow',  track:'rgba(0,0,0,0.15)', thumb:'linear-gradient(180deg,#00f5ff,#0ea5e9)', hover:'linear-gradient(180deg,#22d3ee,#06b6d4)', w:9,  radius:8,  glow:'0 0 12px rgba(6,182,212,.6)' },
    ocean:  { name:'🌊 Ocean',      track:'rgba(15,23,42,0.2)', thumb:'linear-gradient(180deg,#0ea5e9,#0369a1)', hover:'linear-gradient(180deg,#38bdf8,#0ea5e9)', w:10, radius:10, glow:'0 2px 8px rgba(14,165,233,.25)' },
    sunset: { name:'🌅 Sunset',     track:'rgba(30,15,10,0.15)', thumb:'linear-gradient(180deg,#f59e0b,#dc2626)', hover:'linear-gradient(180deg,#fbbf24,#f97316)', w:10, radius:10, glow:'0 2px 8px rgba(251,191,36,.25)' },
    forest: { name:'🌲 Forest',     track:'rgba(10,20,10,0.15)', thumb:'linear-gradient(180deg,#10b981,#059669)', hover:'linear-gradient(180deg,#34d399,#10b981)', w:10, radius:10, glow:'0 2px 8px rgba(16,185,129,.25)' },
    candy:  { name:'🍬 Candy',      track:'rgba(25,10,25,0.15)', thumb:'linear-gradient(135deg,#ec4899,#a855f7)', hover:'linear-gradient(135deg,#f472b6,#c084fc)', w:10, radius:10, glow:'0 2px 8px rgba(236,72,153,.25)' },
    dark:   { name:'🌑 Dark',       track:'rgba(0,0,0,0.28)', thumb:'#556', hover:'#788', w:8, radius:8, glow:'0 2px 8px rgba(0,0,0,.35)' },
    gold:   { name:'✨ Gold',       track:'rgba(30,25,15,0.15)', thumb:'linear-gradient(135deg,#fbbf24,#d97706)', hover:'linear-gradient(135deg,#fcd34d,#fbbf24)', w:10, radius:10, glow:'0 0 10px rgba(251,191,36,.35)' },
    cyber:  { name:'🤖 Cyber',      track:'rgba(0,0,0,0.3)', thumb:'linear-gradient(180deg,#a78bfa,#7c3aed)', hover:'linear-gradient(180deg,#c4b5fd,#a78bfa)', w:9, radius:8, glow:'0 0 10px rgba(139,92,246,.4)' },
    ruby:   { name:'💎 Ruby',       track:'rgba(20,10,15,0.15)', thumb:'linear-gradient(135deg,#dc2626,#991b1b)', hover:'linear-gradient(135deg,#ef4444,#dc2626)', w:10, radius:10, glow:'0 0 8px rgba(220,38,38,.3)' },
    slate:  { name:'🪨 Slate',      track:'rgba(2,6,23,.16)', thumb:'linear-gradient(180deg,#64748b,#334155)', hover:'linear-gradient(180deg,#94a3b8,#64748b)', w:9, radius:10, glow:'0 2px 10px rgba(2,6,23,.35)' },

    // Animated themes
    rainbow: {
      name:'🌈 Rainbow Flow',
      track:'rgba(0,0,0,0.14)',
      thumb:'linear-gradient(90deg,#ff0080,#ff8c00,#ffd700,#00ff6a,#00cfff,#8a2be2,#ff0080)',
      hover:'linear-gradient(90deg,#ff4da6,#ffa64d,#ffe066,#4dff9a,#66e0ff,#b48cff,#ff4da6)',
      w:10, radius:10, glow:'0 0 12px rgba(255,255,255,.18)',
      bgSize:'400% 100%',
      anim:'bs-rainbow 8s linear infinite'
    },
    glint: {
      name:'✨ Shimmer Glint',
      track:'rgba(0,0,0,0.18)',
      thumb:'linear-gradient(120deg, rgba(255,255,255,.0) 30%, rgba(255,255,255,.45) 50%, rgba(255,255,255,.0) 70%), linear-gradient(180deg,#64748b,#334155)',
      hover:'linear-gradient(120deg, rgba(255,255,255,.0) 30%, rgba(255,255,255,.65) 50%, rgba(255,255,255,.0) 70%), linear-gradient(180deg,#94a3b8,#475569)',
      w:10, radius:10, glow:'0 2px 10px rgba(148,163,184,.25)',
      bgSize:'200% 100%, 100% 100%',
      anim:'bs-glint 2.6s linear infinite'
    },
    holo: {
      name:'🫧 Holo Wave',
      track:'rgba(0,0,0,0.16)',
      thumb:'linear-gradient(120deg, #22d3ee, #a78bfa, #f472b6, #22d3ee)',
      hover:'linear-gradient(120deg, #67e8f9, #c4b5fd, #f9a8d4, #67e8f9)',
      w:10, radius:10, glow:'0 0 14px rgba(99,102,241,.25)',
      bgSize:'200% 200%',
      anim:'bs-pan 6s ease-in-out infinite'
    },
    water: {
      name:'💧 Water Filling',
      track:'rgba(2,6,23,.20)',
      thumb:'radial-gradient(circle at 24px 10px, rgba(255,255,255,.26) 11%, rgba(255,255,255,0) 12%), radial-gradient(circle at 6px 14px, rgba(255,255,255,.18) 9%, rgba(255,255,255,0) 10%), linear-gradient(180deg,#7dd3fc,#38bdf8 55%,#0ea5e9)',
      hover:'radial-gradient(circle at 24px 10px, rgba(255,255,255,.36) 11%, rgba(255,255,255,0) 12%), radial-gradient(circle at 6px 14px, rgba(255,255,255,.28) 9%, rgba(255,255,255,0) 10%), linear-gradient(180deg,#a5e6ff,#7dd3fc 55%,#38bdf8)',
      w:10, radius:10, glow:'0 2px 10px rgba(56,189,248,.28)',
      bgSize:'48px 18px, 64px 20px, 100% 100%',
      anim:'bs-water 4s linear infinite'
    }
  };

  // -------------------------------
  // Utils
  // -------------------------------
  const $root = () => document.scrollingElement || document.documentElement;
  const clamp = (v, a, b) => v < a ? a : v > b ? b : v;
  const toBool = (v) => v === true || v === 1 || v === '1' || v === 'true';

  const Store = {
    get(k, d) { try { if (typeof GM_getValue === 'function') return GM_getValue(k, d); } catch(_){} try { const v = localStorage.getItem(k); return v !== null ? v : d; } catch(_){ return d; } },
    set(k, v) { try { if (typeof GM_setValue === 'function') { GM_setValue(k, v); return; } } catch(_){} try { localStorage.setItem(k, v); } catch(_){} }
  };

  const addCSS = (css) => {
    try { if (typeof GM_addStyle === 'function') return GM_addStyle(css); } catch(_){}
    const st = document.createElement('style'); st.textContent = css; (document.head || document.documentElement).appendChild(st);
  };

  const isEditable = (el) => {
    const t = el?.tagName; return el?.isContentEditable || t === 'INPUT' || t === 'TEXTAREA' || t === 'SELECT';
  };

  const isScrollable = (el) => {
    if (!el || !el.nodeType) return false;
    const cs = getComputedStyle(el);
    if (cs.display === 'none' || cs.visibility === 'hidden') return false;
    return /auto|scroll|overlay/.test((cs.overflowY || cs.overflow || '').toLowerCase())
           && el.scrollHeight > el.clientHeight + 1;
  };

  const findScrollable = (t) => {
    if (!t) return $root();
    if (t.closest('.bs-bar, .bs-ui')) return null;
    for (let el = t; el && el !== document.documentElement; el = el.parentElement) {
      if (el === document.body) return $root();
      if (isScrollable(el)) return el;
    }
    return $root();
  };

  const getDigit = (e) => {
    const k = e.key; if (k?.length === 1 && k >= '0' && k <= '9') return k;
    const c = e.code || ''; if (/^Digit[0-9]$/.test(c)) return c.slice(5);
    if (/^Numpad[0-9]$/.test(c)) return c.slice(6);
    return null;
  };

  // -------------------------------
  // Theme handling
  // -------------------------------
  let CUR_THEME = Store.get('bs-theme', 'modern');
  const setTheme = (name) => {
    if (!THEMES[name]) name = 'modern';
    CUR_THEME = name; Store.set('bs-theme', name);
    const t = THEMES[name], r = document.documentElement.style;
    r.setProperty('--bs-track', t.track);
    r.setProperty('--bs-thumb', t.thumb);
    r.setProperty('--bs-thumb-hover', t.hover || t.thumb);
    r.setProperty('--bs-radius', (t.radius ?? 8) + 'px');
    r.setProperty('--bs-width', (t.w ?? 10) + 'px');
    r.setProperty('--bs-glow', t.glow || 'none');
    r.setProperty('--bs-thumb-anim', t.anim || 'none');
    r.setProperty('--bs-thumb-bg-size', t.bgSize || 'auto');
  };

  // -------------------------------
  // Scrollbar Instance
  // -------------------------------
  class SB {
    constructor(container) {
      this.c = container;
      this.r = container === $root();
      this.el = document.createElement('div');
      this.el.className = 'bs-bar';
      this.thumb = document.createElement('div');
      this.thumb.className = 'bs-thumb';
      this.el.appendChild(this.thumb);
      document.body.appendChild(this.el);
      this.raf = 0;
      this._anim = 0;
      this._idleTO = 0;
      this.isHover = false;
      if (!this.r && this.c && this.c.classList) this.c.classList.add('bs-hide-v');

      this.onScroll = () => {
        this.req();
        this.awake();
        this.scheduleIdle();
      };

      this.bind();
      this.update();
      this.scheduleIdle(); // start collapsed after a delay if untouched
    }

    _setScroll(v) {
      (this.r ? $root() : this.c).scrollTop = v;
    }
    cancelSmooth() {
      if (this._anim) cancelAnimationFrame(this._anim);
      this._anim = 0;
    }
    to(y) { // instant
      this.cancelSmooth();
      const m = this.m(), v = clamp(y, 0, m.max);
      this._setScroll(v);
    }
    smoothTo(y, dur = 180) {
      this.cancelSmooth();
      const m = this.m(), start = m.scroll, end = clamp(y, 0, m.max);
      if (Math.abs(end - start) < 2) { this.to(end); return; }
      const t0 = performance.now();
      const ease = (t) => 1 - Math.pow(1 - t, 3); // easeOutCubic
      const step = (now) => {
        const t = clamp((now - t0) / dur, 0, 1);
        this._setScroll(start + (end - start) * ease(t));
        if (t < 1) this._anim = requestAnimationFrame(step);
        else this._anim = 0;
      };
      this._anim = requestAnimationFrame(step);
    }
    go(y) { // obey smooth toggle for bar actions
      if (SMOOTH_BAR) this.smoothTo(y);
      else this.to(y);
    }

    // Idle/Active state
    awake() {
      clearTimeout(this._idleTO);
      this._idleTO = 0;
      this.el.classList.remove('idle');
    }
    scheduleIdle() {
      if (!this.el) return;
      if (this.isHover || this.el.classList.contains('drag')) return;
      clearTimeout(this._idleTO);
      this._idleTO = setTimeout(() => {
        if (this.el && !this.isHover && !this.el.classList.contains('drag')) {
          this.el.classList.add('idle');
        }
      }, IDLE_DELAY);
    }

    bind() {
      // Track click -> jump
      this.el.addEventListener('click', (e) => {
        if (e.target !== this.el) return;
        // any click is activity
        this.awake();
        this.scheduleIdle();

        const rect = this.el.getBoundingClientRect();
        const th = this.thumb.offsetHeight, space = rect.height - th;
        if (space <= 0) return;
        const p = clamp((e.clientY - rect.top - th / 2) / space, 0, 1);
        const m = this.m(); this.go(m.max * p);
      });

      // Touch tap on track -> jump
      this.el.addEventListener('touchstart', (e) => {
        if (e.target !== this.el) return;
        const t = e.touches && e.touches[0]; if (!t) return;
        this.awake();
        this.scheduleIdle();
        const rect = this.el.getBoundingClientRect();
        const th = this.thumb.offsetHeight, space = rect.height - th; if (space <= 0) return;
        const p = clamp((t.clientY - rect.top - th / 2) / space, 0, 1);
        const m = this.m(); this.go(m.max * p);
      }, { passive: true });

      // Hover state for idle logic
      this.el.addEventListener('mouseenter', () => { this.isHover = true; this.awake(); });
      this.el.addEventListener('mouseleave', () => { this.isHover = false; this.scheduleIdle(); });

      // Drag thumb (always direct)
      this.thumb.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return; e.preventDefault();
        const startY = e.clientY, start = this.m().scroll;
        this.el.classList.add('drag');
        this.awake(); // expand immediately
        this.cancelSmooth();
        const mm = (ev) => {
          ev.preventDefault();
          const m = this.m(), rect = this.el.getBoundingClientRect();
          const th = this.thumb.offsetHeight, space = rect.height - th; if (space <= 0) return;
          this.to(start + ((ev.clientY - startY) / space) * m.max);
        };
        const mu = () => {
          this.el.classList.remove('drag');
          document.removeEventListener('mousemove', mm);
          document.removeEventListener('mouseup', mu);
          this.scheduleIdle(); // collapse after delay when drag ends
        };
        document.addEventListener('mousemove', mm);
        document.addEventListener('mouseup', mu);
      });

      // Touch drag on thumb
      this.thumb.addEventListener('touchstart', (e) => {
        const t = e.touches && e.touches[0]; if (!t) return;
        const startY = t.clientY, start = this.m().scroll;
        this.el.classList.add('drag');
        this.awake();
        this.cancelSmooth();
        const mm = (ev) => {
          const tt = ev.touches && ev.touches[0]; if (!tt) return;
          const m = this.m(), rect = this.el.getBoundingClientRect();
          const th = this.thumb.offsetHeight, space = rect.height - th; if (space <= 0) return;
          this.to(start + ((tt.clientY - startY) / space) * m.max);
          ev.preventDefault();
        };
        const mu = () => {
          this.el.classList.remove('drag');
          document.removeEventListener('touchmove', mm);
          document.removeEventListener('touchend', mu);
          document.removeEventListener('touchcancel', mu);
          this.scheduleIdle();
        };
        document.addEventListener('touchmove', mm, { passive: false });
        document.addEventListener('touchend', mu);
        document.addEventListener('touchcancel', mu);
      });

      (this.r ? window : this.c).addEventListener('scroll', this.onScroll, { passive: true });
    }

    m() {
      if (this.r) {
        const rootEl = $root();
        const scroll = window.pageYOffset || rootEl.scrollTop;
        const vh = window.innerHeight;
        const sh = Math.max(rootEl.scrollHeight || 0, document.body?.scrollHeight || 0, vh);
        return { scroll, vh, sh, max: Math.max(0, sh - vh) };
      }
      const scroll = this.c.scrollTop, vh = this.c.clientHeight, sh = this.c.scrollHeight;
      return { scroll, vh, sh, max: Math.max(0, sh - vh) };
    }

    update() {
      if (!this.el) return;
      const m = this.m();
      let vis = m.max > 0 ? 1 : 0;
      let top = 0, h = window.innerHeight, right = 0;

      if (!this.r) {
        const rect = this.c.getBoundingClientRect();
        if (rect.height <= 0 || rect.bottom <= 0 || rect.top >= window.innerHeight) vis = 0;
        else {
          top = Math.max(0, rect.top);
          h = Math.min(rect.bottom, window.innerHeight) - top;
          right = Math.max(0, window.innerWidth - rect.right);
        }
      }

      this.el.style.opacity = String(vis);
      this.el.style.pointerEvents = vis ? 'auto' : 'none';
      this.el.style.top = top + 'px';
      this.el.style.height = h + 'px';
      this.el.style.right = right + 'px';
      if (!vis) return;

      const barH = this.el.offsetHeight || h;
      const th = Math.max(MIN_THUMB, Math.min(barH * RATIO, barH * (m.vh / Math.max(m.sh, 1))));
      const p = m.max ? (m.scroll / m.max) : 0;
      const y = (barH - th) * p;
      this.thumb.style.height = Math.round(th) + 'px';
      this.thumb.style.transform = `translateY(${Math.round(y)}px)`;
    }

    req() { cancelAnimationFrame(this.raf); this.raf = requestAnimationFrame(() => this.update()); }

    destroy() {
      this.cancelSmooth();
      clearTimeout(this._idleTO);
      cancelAnimationFrame(this.raf);
      (this.r ? window : this.c).removeEventListener('scroll', this.onScroll);
      this.el?.remove();
      if (!this.r && this.c && this.c.classList) this.c.classList.remove('bs-hide-v');
      this.el = this.thumb = this.c = null;
    }
  }

  // -------------------------------
  // Manager (tracks active container)
  // -------------------------------
  class Manager {
    constructor() {
      this.map = new Map();
      this.rootInst = this.get($root());
      this.active = this.rootInst;
      this.bind();
      this.observe();
    }
    get(c) { if (!this.map.has(c)) this.map.set(c, new SB(c)); return this.map.get(c); }
    setActiveFrom(evtOrTarget) {
      const target = evtOrTarget?.target || evtOrTarget;
      if (target?.closest?.('.bs-bar, .bs-ui')) return;
      let path = evtOrTarget?.composedPath?.();
      if (Array.isArray(path) && path.length) {
        for (const el of path) {
          if (!el || !el.nodeType || el === document || el === window) continue;
          if (typeof el.closest === 'function' && el.closest('.bs-bar, .bs-ui')) continue;
          if (isScrollable(el)) { this.active = this.get(el); return; }
          const rn = el.getRootNode?.();
          if (rn && rn.host && isScrollable(rn.host)) { this.active = this.get(rn.host); return; }
        }
      }
      const c = findScrollable(target);
      if (c) this.active = this.get(c);
    }
    bind() {
      const onEvt = (e) => this.setActiveFrom(e);
      document.addEventListener('mouseover', onEvt);
      document.addEventListener('wheel', onEvt, { passive: true });
      document.addEventListener('focusin', onEvt);
      window.addEventListener('resize', () => this.updateAll(), { passive: true });
    }
    updateAll() { this.map.forEach(i => i.req()); }
    clean() {
      this.map.forEach((inst, el) => {
        if (el !== $root() && !el?.isConnected) { inst.destroy(); this.map.delete(el); }
      });
    }
    observe() {
      this._moScheduled = false;
      const schedule = () => {
        if (this._moScheduled) return;
        this._moScheduled = true;
        requestAnimationFrame(() => { this._moScheduled = false; this.clean(); this.updateAll(); });
      };
      const mo = new MutationObserver(schedule);
      mo.observe(document.documentElement, { childList: true, subtree: true });
    }
  }

  // -------------------------------
  // Settings UI (themes + smooth)
  // -------------------------------
  let UI = null, UI_OPEN = false, MGR = null;
  let SMOOTH_BAR = toBool(Store.get('bs-smooth-bar', '0')); // off by default
  let KEYS_SHIFT = toBool(Store.get('bs-keys-shift', '1'));

  const setSmoothBar = (on) => {
    SMOOTH_BAR = !!on;
    Store.set('bs-smooth-bar', SMOOTH_BAR ? '1' : '0');
  };

  const buildUI = () => {
    if (UI) return;
    const cards = Object.entries(THEMES).map(([k, v]) => {
      const extra = [
        v.bgSize ? `background-size:${v.bgSize};` : '',
        v.anim ? `animation:${v.anim};` : ''
      ].join('');
      return `
        <button class="bs-card ${k === CUR_THEME ? 'on' : ''}" data-k="${k}" aria-label="${v.name}">
          <span class="bs-prev">
            <i style="background:${v.track}"></i>
            <b style="background:${v.thumb}; ${extra} box-shadow:${v.glow || 'none'}"></b>
          </span>
          <span class="bs-name">${v.name}</span>
        </button>
      `;
    }).join('');
    UI = document.createElement('div');
    UI.className = 'bs-ui bs-ov';
    UI.innerHTML = `
      <div class="bs-panel" role="dialog" aria-label="Scrollbar Settings" aria-modal="true">
        <div class="bs-top">
          <div class="bs-title">Scrollbar</div>
          <button class="bs-close" aria-label="Close">✕</button>
        </div>

        <div class="bs-subtitle">Themes</div>
        <div class="bs-grid" role="list">${cards}</div>

        <div class="bs-subtitle">Behavior</div>
        <div class="bs-row">
          <div class="bs-row-label">Quick smooth scroll (bar only)</div>
          <button class="bs-switch ${SMOOTH_BAR ? 'on' : ''}" data-k="smooth" role="switch" aria-checked="${SMOOTH_BAR}" aria-label="Quick smooth scroll (bar only)"><i></i></button>
        </div>
        <div class="bs-row">
          <div class="bs-row-label">Require Shift for number jumps</div>
          <button class="bs-switch ${KEYS_SHIFT ? 'on' : ''}" data-k="keys" role="switch" aria-checked="${KEYS_SHIFT}" aria-label="Require Shift for number jumps"><i></i></button>
        </div>

        <div class="bs-meta"><kbd>1</kbd> top • <kbd>0</kbd> bottom • <kbd>2–9</kbd> jump %</div>
      </div>
    `;
    UI.addEventListener('click', (e) => {
      if (e.target.classList.contains('bs-ov')) closeUI();
      if (e.target.classList.contains('bs-close')) closeUI();

      const btn = e.target.closest('.bs-card');
      if (btn) {
        setTheme(btn.dataset.k);
        UI.querySelectorAll('.bs-card').forEach(b => b.classList.toggle('on', b === btn));
        MGR?.updateAll();
        return;
      }

      const sw = e.target.closest('.bs-switch');
      if (sw) {
        const on = !sw.classList.contains('on');
        const which = sw.dataset.k;
        if (which === 'smooth') setSmoothBar(on);
        if (which === 'keys') { KEYS_SHIFT = !!on; Store.set('bs-keys-shift', KEYS_SHIFT ? '1' : '0'); }
        sw.classList.toggle('on', on);
        sw.setAttribute('aria-checked', String(on));
      }
    });
    UI.addEventListener('keydown', (e) => {
      const btn = e.target.closest?.('.bs-card');
      if (btn && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); btn.click(); }
      const sw = e.target.closest?.('.bs-switch');
      if (sw && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); sw.click(); }
      if (e.key === 'Escape') closeUI();
    });
    document.body.appendChild(UI);
  };

  const openUI = () => { buildUI(); UI.classList.add('show'); UI_OPEN = true; setTimeout(() => UI.querySelector('.bs-card.on')?.focus(), 50); };
  const closeUI = () => { UI?.classList.remove('show'); UI_OPEN = false; };

  // -------------------------------
  // Styles (idle thin indicator; cleanup redundant rules)
  // -------------------------------
  const injectStyles = () => addCSS(`
    :root{
      --bs-track: rgba(0,0,0,.08);
      --bs-thumb: #889;
      --bs-thumb-hover: #aab;
      --bs-radius: 10px;
      --bs-width: 10px;
      --bs-glow: none;
      --bs-red: #ef4444;
      --bs-red-strong: #dc2626;
      --bs-thumb-anim: none;
      --bs-thumb-bg-size: auto;

      /* Idle indicator tuning */
      --bs-indicator-width: 3px;  /* collapsed bar width */
      --bs-thin-thumb: 2px;       /* collapsed thumb visible line */
      --bs-idle-opacity: .6;      /* collapsed thumb opacity */
    }
    html::-webkit-scrollbar:vertical, body::-webkit-scrollbar:vertical, .bs-hide-v::-webkit-scrollbar:vertical { width:0!important; display:none!important; }
    html, body { scrollbar-width: none!important; -ms-overflow-style: none!important; }
    .bs-hide-v { scrollbar-width: none!important; -ms-overflow-style: none!important; }

    /* Custom bar */
    .bs-bar{
      position:fixed; right:0; top:0; width:var(--bs-width); height:100vh;
      background:var(--bs-track);
      border-radius:var(--bs-radius) 0 0 var(--bs-radius);
      z-index:${Z}; transition:opacity .2s,width .15s, background .15s;
      opacity:0; pointer-events:none;
    }
    .bs-bar:hover{ width:calc(var(--bs-width) + 3px); }
    .bs-thumb{
      position:absolute; left:0; right:0; top:0;
      min-height:${MIN_THUMB}px;
      background:var(--bs-thumb);
      background-size: var(--bs-thumb-bg-size, auto);
      border-radius:var(--bs-radius);
      box-shadow:var(--bs-glow);
      cursor:grab; will-change:transform;
      transition:background .15s, box-shadow .2s, opacity .15s;
      animation: var(--bs-thumb-anim, none);
      animation-play-state: running;
      touch-action: none;
    }
    .bs-bar:hover .bs-thumb{ background:var(--bs-thumb-hover); box-shadow:var(--bs-glow), 0 2px 8px rgba(0,0,0,.25); }
    .bs-bar.drag .bs-thumb{ cursor:grabbing; }

    /* Idle collapse to thin indicator */
    .bs-bar.idle{
      width: var(--bs-indicator-width);
      background: transparent;
    }
    .bs-bar.idle .bs-thumb{
      opacity: var(--bs-idle-opacity);
      box-shadow: none;
      background: var(--bs-thumb);
      clip-path: inset(0 calc(50% - (var(--bs-thin-thumb) / 2)) 0 calc(50% - (var(--bs-thin-thumb) / 2)));
      animation-play-state: paused;
    }

    

    /* Overlay */
    .bs-ov{ position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(3,6,12,.72); backdrop-filter: blur(6px); z-index:${Z+10}; }
    .bs-ov.show{ display:flex; }

    /* Panel */
    .bs-panel{ width:min(92vw,680px); max-height:88vh; display:flex; flex-direction:column; background:rgba(10,14,22,.92); color:#e5e7eb; border:1px solid rgba(255,255,255,.06); border-radius:14px; box-shadow:0 18px 60px rgba(0,0,0,.6); overflow:hidden; font:13.5px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
    .bs-top{ display:flex; align-items:center; justify-content:space-between; padding:12px 14px; background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,.01)); border-bottom:1px solid rgba(255,255,255,.06); }
    .bs-title{ font-weight:700; font-size:13.5px; color:#cbd5e1; letter-spacing:.2px; }
    .bs-close{ width:32px; height:32px; border-radius:9px; border:1px solid rgba(255,255,255,.12); background:rgba(255,255,255,.06); color:#e5e7eb; font-size:18px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all .15s; }
    .bs-close:hover{ border-color: var(--bs-red); background: rgba(239,68,68,.16); color:#fff; }
    .bs-close:active{ transform: scale(.95); background: rgba(239,68,68,.28); border-color: var(--bs-red-strong); color:#fff; }

    .bs-subtitle{ padding:10px 14px 6px; color:#9aa4b2; font-weight:700; text-transform:uppercase; letter-spacing:.06em; font-size:11.5px; }
    .bs-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(125px,1fr)); gap:10px; padding:12px; overflow:auto; max-height:42vh; }
    .bs-card{ appearance:none; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.04); border-radius:12px; padding:10px; cursor:pointer; text-align:center; color:#e5e7eb; transition:transform .12s, border-color .12s, background .12s, box-shadow .12s; outline:none; width:100%; }
    .bs-card:hover{ transform:translateY(-2px); border-color:rgba(99,102,241,.6); background:rgba(99,102,241,.12); box-shadow:0 6px 16px rgba(0,0,0,.35); }
    .bs-card.on{ border-color:#60a5fa; box-shadow:0 0 0 2px rgba(96,165,250,.16) inset, 0 10px 24px rgba(0,0,0,.35); background:rgba(96,165,250,.10); }
    .bs-prev{ position:relative; height:52px; border-radius:9px; background:#0b1220; margin-bottom:8px; overflow:hidden; box-shadow:inset 0 2px 8px rgba(0,0,0,.35); display:block; }
    .bs-prev i{ position:absolute; right:6px; top:6px; bottom:6px; width:8px; border-radius:4px; }
    .bs-prev b{ position:absolute; right:6px; top:12px; width:8px; height:20px; border-radius:4px; }

    .bs-row{ display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 14px; border-top:1px solid rgba(255,255,255,.06); }
    .bs-row-label{ color:#cbd5e1; font-weight:600; }
    .bs-switch{ position:relative; width:44px; height:24px; border-radius:999px; background:rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.18); cursor:pointer; transition:all .15s; outline:none; }
    .bs-switch i{ position:absolute; top:2px; left:2px; width:20px; height:20px; background:#e5e7eb; border-radius:999px; transition:all .15s; box-shadow:0 2px 6px rgba(0,0,0,.25); }
    .bs-switch.on{ background:rgba(96,165,250,.35); border-color:rgba(96,165,250,.5); }
    .bs-switch.on i{ transform:translateX(20px); background:#fff; }

    .bs-meta{ padding:10px 12px 12px; text-align:center; color:#94a3b8; font-size:12px; border-top:1px solid rgba(255,255,255,.06); }
    .bs-meta kbd{ background:rgba(148,163,184,.18); border:1px solid rgba(148,163,184,.28); padding:2px 6px; border-radius:6px; font:600 11px ui-monospace,monospace; color:#cbd5e1; }

    /* Scrollbar for the grid (override hide) */
    .bs-grid::-webkit-scrollbar{ width:8px!important; display:block!important; }
    .bs-grid::-webkit-scrollbar-thumb{ background:rgba(96,165,250,.35); border-radius:4px; }
    .bs-grid::-webkit-scrollbar-thumb:hover{ background:rgba(96,165,250,.6); }
    .bs-grid{ scrollbar-width:thin!important; }

    /* Keyframes for animated themes */
    @keyframes bs-rainbow { 0%{background-position:0% 50%} 100%{background-position:100% 50%} }
    @keyframes bs-glint { 0%{background-position:200% 0,0 0} 100%{background-position:-200% 0,0 0} }
    @keyframes bs-water { 0%{background-position:0 4px,0 0,0 0} 100%{background-position:96px 4px,72px 0,0 0} }
    @keyframes bs-pan { 0%{background-position:0% 0%} 50%{background-position:100% 100%} 100%{background-position:0% 0%} }

    @media (prefers-reduced-motion: reduce){
      .bs-thumb, .bs-prev b { animation: none!important; }
      .bs-bar, .bs-thumb, .bs-card, .bs-close, .bs-switch { transition:none!important; }
    }
  `);

  // -------------------------------
  // Keyboard (instant jumps on active container)
  // -------------------------------
  const bindKeys = () => {
    document.addEventListener('keydown', (e) => {
      if (UI_OPEN || isEditable(e.target) || e.ctrlKey || e.metaKey || e.altKey) return;
      if (KEYS_SHIFT && !e.shiftKey) return;
      const d = getDigit(e); if (d == null) return;

      const inst = MGR?.active || MGR?.rootInst; if (!inst) return;
      const m = inst.m();
      e.preventDefault();

      // Keys remain instant
      if (d === '1') inst.to(0);
      else if (d === '0') inst.to(m.max);
      else inst.to(m.max * (parseInt(d, 10) / 10));
    });
  };

  // -------------------------------
  // Init
  // -------------------------------
    const start = () => {
      injectStyles();
      setTheme(CUR_THEME);
      MGR = new Manager();
      bindKeys();
      if (typeof GM_registerMenuCommand === 'function') {
        GM_registerMenuCommand('🎨 Scrollbar Settings', () => (UI_OPEN ? closeUI() : openUI()));
      }
    };

  if (document.body) start();
  else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
  else setTimeout(start, 30);
})();