您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Keep the nice blur background across SPA navigation & new chats by injecting a persistent, Shadow-DOM isolated layer
// ==UserScript== // @name ChatGPT Persistent Background (Always On) // @namespace eli.keep.bg // @version 2.0.0 // @description Keep the nice blur background across SPA navigation & new chats by injecting a persistent, Shadow-DOM isolated layer // @author eli // @license MIT // @match https://chat.openai.com/* // @match https://chatgpt.com/* // @run-at document-start // @noframes // @grant none // ==/UserScript== (() => { "use strict"; if (window.__eliPersistentBg) return; // singleton window.__eliPersistentBg = true; const ID = "eli-persistent-bg"; const FALLBACK_SRCSET = "https://persistent.oaistatic.com/burrito-nux/640.webp 640w, https://persistent.oaistatic.com/burrito-nux/1280.webp 1280w, https://persistent.oaistatic.com/burrito-nux/1920.webp 1920w"; const FALLBACK_IMG = "https://persistent.oaistatic.com/burrito-nux/1920.webp"; let root, imgEl, sourceEl; let hooked = false; // Minimal debounce to survive DOM churn without thrashing const debounce = (fn, ms = 100) => { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; }; function ensureLayer() { if (document.getElementById(ID)) return; const host = document.createElement("div"); host.id = ID; Object.assign(host.style, { position: "fixed", inset: "0", zIndex: "0", pointerEvents: "none", contain: "strict", }); document.documentElement.appendChild(host); // Shadow DOM to avoid site CSS conflicts const shadow = host.attachShadow({ mode: "open" }); shadow.innerHTML = ` <style> :host, .wrap { position: fixed; inset: 0; } picture, img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; } img.blur { transform: scale(1.02); filter: blur(20px); opacity: .5; } .grad { position:absolute; inset:0; pointer-events:none; background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, var(--eli-bg-end, #000) 100%); mix-blend-mode: normal; } @media (prefers-color-scheme: light) {.grad{ --eli-bg-end: #fff; }} @media (prefers-color-scheme: dark) {.grad{ --eli-bg-end: #000; }} </style> <div class="wrap" part="wrap" aria-hidden="true"> <picture> <source type="image/webp" id="eli-bg-srcset"> <img id="eli-bg-img" class="blur" alt=""> </picture> <div class="grad"></div> </div> `; root = host; imgEl = shadow.getElementById("eli-bg-img"); sourceEl = shadow.getElementById("eli-bg-srcset"); // Keep the app transparent so our layer shows const css = document.createElement("style"); css.textContent = ` html, body { background: transparent !important; } `; document.documentElement.appendChild(css); } function pickAppBackgroundPicture() { // Try common patterns first, then a broader heuristic const candidates = [ // Known nux background ...document.querySelectorAll('picture img[src*="burrito-nux"]'), ...document.querySelectorAll('picture source[srcset*="burrito-nux"]'), // Any oaistatic hero-ish picture ...document.querySelectorAll('picture img[src*="oaistatic.com"]'), ...document.querySelectorAll('picture source[srcset*="oaistatic.com"]'), ]; const pic = candidates.find(Boolean)?.closest("picture"); return pic || null; } function syncFromApp() { ensureLayer(); const appPic = pickAppBackgroundPicture(); if (appPic) { const appImg = appPic.querySelector("img"); const appSrc = appPic.querySelector('source[type="image/webp"]'); if (appImg?.src) imgEl.src = appImg.src; if (appImg?.getAttribute("srcset")) imgEl.setAttribute("srcset", appImg.getAttribute("srcset")); if (appSrc?.getAttribute("srcset")) { sourceEl.setAttribute("srcset", appSrc.getAttribute("srcset")); } else { sourceEl.removeAttribute("srcset"); } imgEl.setAttribute("sizes", "100vw"); return; } // Fallback sourceEl.setAttribute("srcset", FALLBACK_SRCSET); imgEl.src = FALLBACK_IMG; imgEl.setAttribute("srcset", FALLBACK_SRCSET); imgEl.setAttribute("sizes", "100vw"); } const safeSync = debounce(() => { try { syncFromApp(); } catch (e) { /* silent */ } }, 120); function hookNavOnce() { if (hooked) return; hooked = true; const wrap = (obj, key) => { const orig = obj[key]; if (!orig || orig.__eliWrapped) return; const fn = function () { const r = orig.apply(this, arguments); queueMicrotask(safeSync); return r; }; fn.__eliWrapped = true; try { obj[key] = fn; } catch {} }; wrap(history, "pushState"); wrap(history, "replaceState"); window.addEventListener("popstate", safeSync, true); window.addEventListener("load", safeSync, true); document.addEventListener("visibilitychange", safeSync, true); // Lean observer + debounced handler (no attribute spam watching) new MutationObserver(() => safeSync()).observe(document.documentElement, { childList: true, subtree: true, }); // Resize can swap backgrounds on some layouts window.addEventListener("resize", safeSync, { passive: true }); } // Boot ensureLayer(); hookNavOnce(); syncFromApp(); setTimeout(syncFromApp, 250); })();