Userscript: removes Amazon's "Alexa for Shopping" (née Rufus) AI sidebar and reclaims the reserved gutter. Survives randomized class names, closed shadow DOM, and page-level layout offsets. Developer Giles Hall released a userscript that removes Amazon's 'Alexa for Shopping' (formerly Rufus) AI sidebar and reclaims the reserved gutter space. The script uses multiple layers of defense, including CSS overrides, script blocking, and a geometry-based hunter that adapts to randomized class names and closed shadow DOM, ensuring the sidebar is hidden across all Amazon domains. | // ==UserScript== | | | // @name Amazon: Nuke Alexa/Rufus Shopping Panel | | | // @description Hides the "Alexa for Shopping" nee Rufus AI sidebar on Amazon and reclaims the reserved gutter, whatever mechanism Amazon uses to create it. | | | // @namespace https://github.com/gileshall | | | // @version 3.0.0 | | | // @license MIT | | | // @match https:// .amazon.com/ | | | // @match https:// .amazon.ca/ | | | // @match https:// .amazon.co.uk/ | | | // @match https:// .amazon.de/ | | | // @match https:// .amazon.fr/ | | | // @match https:// .amazon.it/ | | | // @match https:// .amazon.es/ | | | // @match https:// .amazon.co.jp/ | | | // @match https:// .amazon.in/ | | | // @match https:// .amazon.com.au/ | | | // @match https:// .amazon.com.mx/ | | | // @match https:// .amazon.com.br/ | | | // @run-at document-start | | | // @grant none | | | // @updateURL https://gist.githubusercontent.com/gileshall/0213c0568a16eab0fd6c0a7cdbc7239a/raw/amazon-alexa-nuke.user.js | | | // @downloadURL https://gist.githubusercontent.com/gileshall/0213c0568a16eab0fd6c0a7cdbc7239a/raw/amazon-alexa-nuke.user.js | | | // ==/UserScript== | | | = { | | | 'use strict'; | | | // ------------------------------------------------------------------ | | | // Config | | | // ------------------------------------------------------------------ | | | const DEBUG = false; // true - console.log everything hidden/fixed | | | const BLOCK SCRIPTS = true; // best-effort: neutralize rufus/copilot bootstrap scripts | | | const SCRIPT SRC RE = /rufus|copilot/i; | | | const GUTTER MIN = 50; // content starting past this = a gutter exists | | | const FIX MIN = 40; // individual offsets larger than this get zeroed | | | // Layer-1 fast path: known names as of mid-2026 internally the widget is | | | // still rufus/copilot despite the Alexa rebrand . | | | const SELECTORS = | | | '.rufus-container', | | | '.rufus-panel-container', | | | '.rufus-chat-container', | | | '.rufus-conversation-container', | | | '.rufus-conversation-container-inner', | | | '.rufus-container-peek-view', | | | '.rufus-panel-header-container', | | | '.rufus-view-filler', | | | '.nav-rufus-disco', | | | '.nav-rufus-content', | | | ' nav-flyout-rufus', | | | '.copilot-modal-container', | | | '.copilot-chat-root', | | | 'aside data-copilot-chat-root ', | | | 'div data-copilot-name ', | | | ' class ="rufus-panel" ', | | | ' class ="rufus-chat" ', | | | ; | | | // Things the geometry hunter must NEVER hide. | | | const ROOT IDS = new Set 'a-page', 'pageContent', 'search', 'dp', 'nav-main', 'nav-belt' ; | | | const PROTECT RE = /hmenu|s-refinements|a-popover/i; | | | const PROTECT ANCESTOR = ' nav-main, hmenu-canvas, hmenu-container, s-refinements'; | | | // ------------------------------------------------------------------ | | | // Layer 0: document-start CSS | | | // ------------------------------------------------------------------ | | | const css = | | | ${SELECTORS.join ',\n ' } { | | | display: none important; | | | width: 0 important; | | | min-width: 0 important; | | | visibility: hidden important; | | | pointer-events: none important; | | | } | | | body class ="rufus" , body class ="copilot" { | | | padding-left: 0 important; | | | margin-left: 0 important; | | | } | | | html, body, a-page, pageContent, search, dp, main, role="main" , | | | search div, search div div { | | | margin-left: 0 important; | | | padding-left: 0 important; | | | left: 0 important; | | | transform: none important; | | | } | | | / pseudo-element spacers flagged by the degutter pass / | | | data-alexa-degutter ::before { | | | content: none important; | | | display: none important; | | | width: 0 important; | | | min-width: 0 important; | | | } | | | ; | | | const style = document.createElement 'style' ; | | | style.id = 'nuke-alexa-rufus'; | | | style.textContent = css; | | | document.head || document.documentElement .appendChild style ; | | | // ------------------------------------------------------------------ | | | // Helpers | | | // ------------------------------------------------------------------ | | | const describe = el = { | | | tag: el.tagName, | | | id: el.id || null, | | | cls: String el.className || '' .slice 0, 100 || null, | | | left: Math.round el.getBoundingClientRect .left , | | | width: Math.round el.getBoundingClientRect .width , | | | } ; | | | const isProtected = el = { | | | if el || el instanceof Element return true; | | | if el.tagName === 'HTML' || el.tagName === 'BODY' return true; | | | if el.id && ROOT IDS.has el.id return true; | | | if PROTECT RE.test el.id + ' ' + String el.className || '' return true; | | | if el.closest PROTECT ANCESTOR return true; | | | return false; | | | }; | | | // Hide, don't remove: removal can throw inside Amazon's own code. | | | const hide = el, why = { | | | if isProtected el || el.dataset.alexaNuked === '1' return; | | | el.dataset.alexaNuked = '1'; | | | el.style.setProperty 'display', 'none', 'important' ; | | | el.style.setProperty 'visibility', 'hidden', 'important' ; | | | el.style.setProperty 'pointer-events', 'none', 'important' ; | | | if DEBUG console.log ' alexa-nuke hide:', why, describe el ; | | | }; | | | // ------------------------------------------------------------------ | | | // Layer 1: named-selector reaper + body dock-class stripper | | | // ------------------------------------------------------------------ | | | const COMBINED = SELECTORS.join ',' ; | | | const reapKnown = = { | | | for const el of document.querySelectorAll COMBINED hide el, 'selector' ; | | | const b = document.body; | | | if b return; | | | for const c of Array.from b.classList { | | | const t = c.toLowerCase ; | | | if t.includes 'rufus' || t.includes 'copilot' b.classList.remove c ; | | | } | | | }; | | | // ------------------------------------------------------------------ | | | // Layer 2: geometry-based dock hunter drawn shells, incl. closed | | | // shadow hosts -- elementFromPoint returns the host . | | | // Technique credit: desrod/disable-amazon-rufus-userscript Apache-2.0 ; | | | // independent implementation. | | | // ------------------------------------------------------------------ | | | const scoreDock = el = { | | | if isProtected el return -1; | | | const r = el.getBoundingClientRect ; | | | if r.width <= 0 || r.height <= 0 return -1; | | | if r.left 30 || r.width < 200 || r.width 750 || r.height < 240 return -1; | | | const cs = getComputedStyle el ; | | | let s = 0; | | | if cs.position === 'fixed' || cs.position === 'sticky' s += 3; | | | const z = parseInt cs.zIndex, 10 ; | | | if Number.isFinite z && z = 50 s += 2; | | | if r.height r.width 1.1 s += 1; | | | if r.top < 140 s += 1; | | | if r.width innerWidth 0.9 && r.height innerHeight 0.9 s = -10; | | | return s; | | | }; | | | const looksEmpty = el = | | | el.innerText || '' .trim .length < 5 && | | | el.querySelector 'img, input, select, textarea' ; | | | const effectiveScore = el = { | | | let s = scoreDock el ; | | | if s -1 && s < 3 && looksEmpty el s += 2; | | | return s; | | | }; | | | const bestAncestor = start = { | | | let best = null, bestScore = -1, el = start; | | | for let i = 0; i < 12 && el instanceof Element; i++ { | | | const s = effectiveScore el ; | | | if s bestScore { bestScore = s; best = el; } | | | el = el.parentElement; | | | if el || isProtected el break; | | | } | | | return bestScore = 3 ? { el: best, score: bestScore } : null; | | | }; | | | const huntDock = = { | | | let best = null, bestScore = -1; | | | for const x of 2, 8, 16, 24 { | | | for let y = 80; y < innerHeight - 10; y += 90 { | | | const hit = document.elementFromPoint x, y ; | | | if hit continue; | | | const cand = bestAncestor hit ; | | | if cand && cand.score bestScore { bestScore = cand.score; best = cand.el; } | | | } | | | } | | | if best hide best, dock-hunter score=${bestScore} ; | | | }; | | | // ------------------------------------------------------------------ | | | // Layer 3: degutter -- measurement-driven layout reclaim. | | | // | | | // Symptom: the WHOLE page nav included starts hundreds of px from the | | | // left, with nothing drawn in the gap. That means the offset lives on a | | | // page-level container, produced by any of: margin/padding possibly | | | // inline important , translateX, positioned left, a ::before spacer, | | | // a gutter-occupying sibling, or a grid's first column track. | | | // | | | // Instead of enumerating selectors, walk from a known content anchor up | | | // to