Instantly share code, notes, and snippets.
Last active
August 12, 2026 04:46
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save klydeinside/348c673b051fda32ad4cd63dde544626 to your computer and use it in GitHub Desktop.
Twitch userscripts - Liquid Glass Sidebar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Twitch – Liquid Glass Sidebar | |
| // @namespace https://cody-tools.local | |
| // @version 1.6 | |
| // @description Frosted, glossy "Liquid Glass" treatment for the Twitch left sidebar (original iOS 26 beta style — almost fully see-through with a crisp bright rim) plus a cursor-tracked shine on hover. Hides the sidebar scrollbar. Cycle intensity with the bottom-right pill. | |
| // @match https://www.twitch.tv/* | |
| // @grant GM_setValue | |
| // @grant GM_getValue | |
| // @updateURL https://gist.githubusercontent.com/klydeinside/348c673b051fda32ad4cd63dde544626/raw/twitch-liquid-glass-sidebar.user.js | |
| // @downloadURL https://gist.githubusercontent.com/klydeinside/348c673b051fda32ad4cd63dde544626/raw/twitch-liquid-glass-sidebar.user.js | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const STYLE_ID = 'lgSidebarStyle'; | |
| const TOGGLE_ID = 'lgSidebarToggle'; | |
| const LEVEL_KEY = 'lgSidebarLevel'; | |
| const LEVELS = ['Off', 'Subtle', 'Full']; | |
| const SIDE_NAV = '[data-a-target="side-nav-bar"]'; | |
| const SCROLL_AREA = '.side-nav__scrollable_content'; | |
| const HEADER = '.followed-side-nav-header, .side-nav-header'; | |
| const CARD = '.side-nav-card'; | |
| const AVATAR_IMG = '.side-nav-card__avatar img'; | |
| function getLevel() { | |
| const v = GM_getValue(LEVEL_KEY, 2); | |
| return (v === 0 || v === 1 || v === 2) ? v : 2; | |
| } | |
| function setLevel(n) { | |
| GM_setValue(LEVEL_KEY, n); | |
| applyStyle(); | |
| updateToggleLabel(); | |
| } | |
| function applyStyle() { | |
| let style = document.getElementById(STYLE_ID); | |
| if (!style) { | |
| style = document.createElement('style'); | |
| style.id = STYLE_ID; | |
| document.head.appendChild(style); | |
| } | |
| const level = getLevel(); | |
| if (level === 0) { | |
| style.textContent = ''; | |
| return; | |
| } | |
| const base = ` | |
| ${SIDE_NAV} { | |
| position: relative; | |
| background: | |
| linear-gradient(180deg, rgba(255,255,255,0.09), rgba(255,255,255,0.01) 35%, rgba(255,255,255,0.04)), | |
| color-mix(in srgb, var(--color-background-body, #0e0e10) 20%, transparent) !important; | |
| -webkit-backdrop-filter: blur(14px) saturate(140%); | |
| backdrop-filter: blur(14px) saturate(140%); | |
| border-right: 1px solid rgba(255,255,255,0.35); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255,255,255,0.5), | |
| inset -1px 0 0 rgba(255,255,255,0.08), | |
| 1px 0 0 rgba(120,180,255,0.12), | |
| 14px 0 34px rgba(0,0,0,0.5); | |
| } | |
| ${SCROLL_AREA} { | |
| background: transparent !important; | |
| scrollbar-width: none; | |
| -ms-overflow-style: none; | |
| } | |
| ${SCROLL_AREA}::-webkit-scrollbar { | |
| display: none; | |
| } | |
| ${HEADER} { | |
| position: relative; | |
| -webkit-backdrop-filter: blur(8px); | |
| backdrop-filter: blur(8px); | |
| background: rgba(255,255,255,0.03) !important; | |
| border-bottom: 1px solid rgba(255,255,255,0.1); | |
| } | |
| ${CARD} { | |
| position: relative; | |
| border-radius: 10px; | |
| transition: | |
| background 0.25s ease, | |
| transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), | |
| box-shadow 0.25s ease; | |
| } | |
| ${CARD}:hover { | |
| background: color-mix(in srgb, white 7%, transparent); | |
| box-shadow: | |
| inset 0 0 0 1px rgba(255,255,255,0.4), | |
| 0 6px 16px rgba(0,0,0,0.35); | |
| transform: translateX(3px) scale(1.015); | |
| } | |
| ${AVATAR_IMG} { | |
| box-shadow: 0 0 0 1px rgba(255,255,255,0.2); | |
| } | |
| `; | |
| const full = ` | |
| ${SIDE_NAV}::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| background: linear-gradient( | |
| 115deg, | |
| transparent 15%, | |
| rgba(255,255,255,0.07) 38%, | |
| rgba(255,255,255,0.18) 50%, | |
| rgba(255,255,255,0.07) 62%, | |
| transparent 85% | |
| ); | |
| background-size: 250% 250%; | |
| mix-blend-mode: overlay; | |
| animation: lgSheen 7s ease-in-out infinite; | |
| } | |
| @keyframes lgSheen { | |
| 0% { background-position: 0% 0%; } | |
| 50% { background-position: 100% 100%; } | |
| 100% { background-position: 0% 0%; } | |
| } | |
| ${CARD}::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| background: radial-gradient( | |
| 160px 160px at var(--lg-mx, 50%) var(--lg-my, 50%), | |
| rgba(255,255,255,0.3), | |
| transparent 65% | |
| ); | |
| } | |
| ${CARD}:hover::before { | |
| opacity: 1; | |
| } | |
| `; | |
| style.textContent = level === 2 ? base + full : base; | |
| } | |
| let shineHandlerBound = false; | |
| function bindShineHandler() { | |
| if (shineHandlerBound) return; | |
| shineHandlerBound = true; | |
| document.addEventListener('mousemove', (e) => { | |
| if (getLevel() !== 2) return; | |
| const card = e.target.closest && e.target.closest(CARD); | |
| if (!card) return; | |
| const rect = card.getBoundingClientRect(); | |
| const x = ((e.clientX - rect.left) / rect.width) * 100; | |
| const y = ((e.clientY - rect.top) / rect.height) * 100; | |
| card.style.setProperty('--lg-mx', `${x}%`); | |
| card.style.setProperty('--lg-my', `${y}%`); | |
| }, { passive: true }); | |
| } | |
| function updateToggleLabel() { | |
| const btn = document.getElementById(TOGGLE_ID); | |
| if (btn) btn.textContent = `Glass: ${LEVELS[getLevel()]}`; | |
| } | |
| function createToggle() { | |
| if (document.getElementById(TOGGLE_ID)) return; | |
| const btn = document.createElement('button'); | |
| btn.id = TOGGLE_ID; | |
| btn.style.cssText = ` | |
| position: fixed; | |
| bottom: 76px; | |
| right: 20px; | |
| z-index: 999999; | |
| padding: 8px 14px; | |
| background: #9147ff; | |
| color: #fff; | |
| border: none; | |
| border-radius: 9999px; | |
| font-size: 13px; | |
| font-weight: 700; | |
| font-family: Arial, sans-serif; | |
| cursor: pointer; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.35); | |
| `; | |
| btn.onclick = () => setLevel((getLevel() + 1) % LEVELS.length); | |
| document.body.appendChild(btn); | |
| updateToggleLabel(); | |
| } | |
| function tick() { | |
| if (document.querySelector(SIDE_NAV)) { | |
| applyStyle(); | |
| createToggle(); | |
| bindShineHandler(); | |
| } | |
| } | |
| let tickScheduled = false; | |
| function scheduleTick() { | |
| if (tickScheduled) return; | |
| tickScheduled = true; | |
| requestAnimationFrame(() => { | |
| tickScheduled = false; | |
| tick(); | |
| }); | |
| } | |
| const origPushState = history.pushState; | |
| history.pushState = function (...args) { | |
| origPushState.apply(this, args); | |
| scheduleTick(); | |
| }; | |
| window.addEventListener('popstate', scheduleTick); | |
| const observer = new MutationObserver(scheduleTick); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| tick(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment