meta.ai | Performance Optimizer

Fixes sluggish typing in long Meta AI conversations. Kills transition:all bloat, freezes orphaned avatar canvases, adds CSS containment, and prunes hidden node render cost.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         meta.ai | Performance Optimizer
// @namespace    https://greasyfork.org/en/users/1462137-piknockyou
// @version      1.1
// @author       Piknockyou (vibe-coded)
// @license      AGPL-3.0
// @description  Fixes sluggish typing in long Meta AI conversations. Kills transition:all bloat, freezes orphaned avatar canvases, adds CSS containment, and prunes hidden node render cost.
// @match        https://www.meta.ai/*
// @icon         https://www.meta.ai/favicon.ico
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(() => {
	// ═══════════════════════════════════════════════════════════════════
	// CONFIGURATION
	// Change these defaults here. They are also toggleable via the toolbar.
	// ═══════════════════════════════════════════════════════════════════
const DEFAULT_CONFIG = {
		// MODULE A — CSS Surgeon
		// Replaces transition:all on every element with a safe subset.
		// "full"    → kill transition:all globally, allow only specific properties
		// "minimal" → only kill transitions inside the chat scroller
		transitionSurgery: true,
		transitionSurgeryScope: "full", // 'full' | 'minimal'

		// MODULE B — Canvas Freezer
		// Detaches GPU compositor layers for orphaned avatar canvases.
		// "aggressive" → also freeze canvases not drawn to in >30s
		// "conservative" → only freeze canvases inside .invisible parents
		canvasFreezer: true,
		canvasFreezerMode: "aggressive", // 'aggressive' | 'conservative'
		canvasIdleThresholdMs: 30000, // ms of inactivity before aggressive freeze

		// MODULE C — DOM Containment
		// Adds CSS contain: properties to stable subtrees so the browser
		// can skip them during style recalculation.
		domContainment: true,

		// MODULE D — Long Task Monitor
		// Observes PerformanceObserver longtask events.
		// When a long task is running, our own cleanup is suspended
		// so we don't pile onto an already-stressed frame.
		longTaskMonitor: true,

		// MODULE F — Periodic Janitor
		// How often (ms) the janitor runs canvas freeze + shadow prune passes.
		janitorIntervalMs: 10000,

		// MODULE G — Toolbar UI
		showToolbar: true,
	};

	// ═══════════════════════════════════════════════════════════════════
	// INTERNAL STATE  (do not edit)
	// ═══════════════════════════════════════════════════════════════════
	let config = loadConfig();
	let systemBusy = false; // set true during detected long tasks
	let ltObserver = null; // PerformanceObserver for long tasks
	let mutationObs = null; // MutationObserver on chat container
	let janitorTimer = null;
	let originalGetContext = null; // stored so we can restore if needed
	const stats = {
		canvasesFound: 0,
		canvasFrozen: 0,
		longTaskCount: 0,
		longTaskMaxMs: 0,
		longTaskTotalMs: 0,
	};

	// Per-canvas metadata for aggressive mode idle tracking
	// WeakMap so GC can still collect dead canvases
	const canvasLastDraw = new WeakMap();

	// ═══════════════════════════════════════════════════════════════════
	// CONFIG PERSISTENCE
	// ═══════════════════════════════════════════════════════════════════
	function loadConfig() {
		const stored = GM_getValue("metaAIPerfConfig", null);
		if (stored) {
			try {
				return { ...DEFAULT_CONFIG, ...JSON.parse(stored) };
			} catch (_e) {
				/* fall through */
			}
		}
		return { ...DEFAULT_CONFIG };
	}

	function saveConfig() {
		GM_setValue("metaAIPerfConfig", JSON.stringify(config));
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE A — CSS SURGEON
	// ═══════════════════════════════════════════════════════════════════
	const CSS_SURGEON_ID = "meta-perf-css-surgeon";

	function buildSurgeonCSS() {
		if (config.transitionSurgeryScope === "full") {
			return `
                /* ── Meta AI Perf: CSS Surgeon (full scope) ── */

                /* 1. Kill transition:all globally — it forces recalc of every
                      CSS property on every element on every state change.
                      Replace with a safe explicit subset. */
                *, *::before, *::after {
                    transition-property:
                        color,
                        background-color,
                        border-color,
                        outline-color,
                        opacity !important;
                    transition-duration: 150ms !important;
                    transition-timing-function: ease !important;
                    transition-delay: 0s !important;
                }

                /* 2. Interactive elements may also animate transform safely */
                button, [role="button"], a, [role="tab"],
                [role="menuitem"], [role="option"],
                input, textarea, select {
                    transition-property:
                        color,
                        background-color,
                        border-color,
                        outline-color,
                        opacity,
                        transform !important;
                    transition-duration: 150ms !important;
                }

                /* 3. Kill smooth scroll — with 8000+ nodes it causes jank
                      on every programmatic scroll the app triggers */
                .group\\/scroller,
                [class*="scroller"],
                [class*="overflow-y-auto"],
                [class*="overflow-auto"] {
                    scroll-behavior: auto !important;
                }

                /* 4. Avatar/icon wrappers — no transitions needed at all */
                .relative.inline-block.overflow-hidden *,
                [class*="absolute"][class*="start-0"][class*="top-0"] * {
                    transition: none !important;
                }
            `;
		}

		// minimal scope — only inside the chat scroller
		return `
            /* ── Meta AI Perf: CSS Surgeon (minimal scope) ── */
            .group\\/scroller *,
            .group\\/scroller *::before,
            .group\\/scroller *::after {
                transition-property:
                    color, background-color, border-color, opacity !important;
                transition-duration: 150ms !important;
                transition-delay: 0s !important;
            }
            .group\\/scroller {
                scroll-behavior: auto !important;
            }
        `;
	}

	function injectSurgeonCSS() {
		if (document.getElementById(CSS_SURGEON_ID)) return;
		const s = document.createElement("style");
		s.id = CSS_SURGEON_ID;
		s.textContent = buildSurgeonCSS();
		// inject into <head> if available, else <html>
		(document.head || document.documentElement).appendChild(s);
	}

	function removeSurgeonCSS() {
		const el = document.getElementById(CSS_SURGEON_ID);
		if (el) el.remove();
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE C — DOM CONTAINMENT
	// ═══════════════════════════════════════════════════════════════════
	const CSS_CONTAIN_ID = "meta-perf-containment";

	function injectContainmentCSS() {
		if (document.getElementById(CSS_CONTAIN_ID)) return;
		const s = document.createElement("style");
		s.id = CSS_CONTAIN_ID;
		s.textContent = `
            /* ── Meta AI Perf: DOM Containment ── */

            /* Chat scroll container: its internal layout does not
               affect anything outside it */
            .group\\/scroller {
                contain: layout style;
            }

            /* Each direct child of the scroller (a message group)
               is fully independent */
            .group\\/scroller > div {
                contain: content;
            }

            /* Code blocks — syntax-highlighted spans are expensive.
               Each block is visually self-contained. */
            [class*="language-"],
            [class*="shiki"],
            pre {
                contain: content;
            }

            /* Avatar / icon wrapper — tiny 20×20 canvas containers.
               The most numerous repeated element in the DOM. */
            .relative.inline-block.overflow-hidden {
                contain: layout style size;
            }

            /* Invisible avatar ghost layers */
            [class*="absolute"][class*="start-0"][class*="top-0"] {
                contain: strict;
            }

            /* Markdown output areas */
            [class*="markdown"],
            [class*="prose"] {
                contain: content;
            }

            /* Tooltip/popover hidden layers */
            [data-radix-popper-content-wrapper],
            [data-radix-tooltip-content],
            [role="tooltip"] {
                contain: content;
            }
        `;
		(document.head || document.documentElement).appendChild(s);
	}

	function removeContainmentCSS() {
		const el = document.getElementById(CSS_CONTAIN_ID);
		if (el) el.remove();
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE B — CANVAS FREEZER
	// ═══════════════════════════════════════════════════════════════════

	// Returns true if the canvas is inside an "invisible" / hidden ancestor.
	function isOrphanCanvas(canvas) {
		let el = canvas.parentElement;
		let depth = 0;
		while (el && depth < 8) {
			const cls = el.className;
			if (typeof cls === "string") {
				// Meta uses Tailwind "invisible" utility directly
				if (cls.includes("invisible")) return true;
			}
			// Also check computed visibility
			if (window.getComputedStyle(el).visibility === "hidden") return true;
			el = el.parentElement;
			depth++;
		}
		return false;
	}

	// Freeze a single canvas — release GPU memory by zeroing dimensions,
	// and replace its context with a no-op stub.
	function freezeCanvas(canvas) {
		if (canvas.dataset.perfFrozen === "1") return;
		canvas.dataset.perfFrozen = "1";

		// Zero out dimensions → browser releases backing store / GPU texture
		canvas.width = 0;
		canvas.height = 0;

		stats.canvasFrozen++;
		updateToolbarStats();
	}

	// Patch getContext so future canvases inside invisible parents
	// never acquire a real GPU context in the first place.
	function patchGetContext() {
		if (originalGetContext) return; // already patched
		originalGetContext = HTMLCanvasElement.prototype.getContext;

		HTMLCanvasElement.prototype.getContext = function (type, options) {
			// Always record the timestamp so aggressive mode can track idle time
			canvasLastDraw.set(this, performance.now());

			if (config.canvasFreezer && isOrphanCanvas(this)) {
				// Return a no-op stub that satisfies callers without
				// allocating any GPU resource.
				return buildNoOpContext(type);
			}
			return originalGetContext.call(this, type, options);
		};
	}

	function unpatchGetContext() {
		if (!originalGetContext) return;
		HTMLCanvasElement.prototype.getContext = originalGetContext;
		originalGetContext = null;
	}

	// Minimal no-op context stub — covers the 2D API surface Meta uses
	// for avatar rendering (drawImage, fillRect, clearRect, etc.)
	function buildNoOpContext(type) {
		if (type?.includes("webgl")) {
			// WebGL stub — just needs to not throw
			return {
				getExtension: () => null,
				getParameter: () => null,
				enable: () => {},
				disable: () => {},
				viewport: () => {},
				clear: () => {},
				useProgram: () => {},
				drawArrays: () => {},
				drawElements: () => {},
			};
		}
		// 2D stub
		const noop = () => {};
		const noopReturn = (v) => () => v;
		return {
			canvas: null,
			save: noop,
			restore: noop,
			scale: noop,
			rotate: noop,
			translate: noop,
			transform: noop,
			setTransform: noop,
			resetTransform: noop,
			createLinearGradient: noopReturn({ addColorStop: noop }),
			createRadialGradient: noopReturn({ addColorStop: noop }),
			createPattern: noopReturn(null),
			clearRect: noop,
			fillRect: noop,
			strokeRect: noop,
			beginPath: noop,
			closePath: noop,
			moveTo: noop,
			lineTo: noop,
			bezierCurveTo: noop,
			quadraticCurveTo: noop,
			arc: noop,
			arcTo: noop,
			ellipse: noop,
			rect: noop,
			fill: noop,
			stroke: noop,
			clip: noop,
			drawImage: noop,
			createImageData: noopReturn({
				data: new Uint8ClampedArray(0),
				width: 0,
				height: 0,
			}),
			getImageData: noopReturn({
				data: new Uint8ClampedArray(0),
				width: 0,
				height: 0,
			}),
			putImageData: noop,
			measureText: noopReturn({ width: 0 }),
			fillText: noop,
			strokeText: noop,
			setLineDash: noop,
			getLineDash: noopReturn([]),
			drawFocusIfNeeded: noop,
			// Properties that might be read
			globalAlpha: 1,
			globalCompositeOperation: "source-over",
			fillStyle: "#000",
			strokeStyle: "#000",
			lineWidth: 1,
			lineCap: "butt",
			lineJoin: "miter",
			miterLimit: 10,
			shadowBlur: 0,
			shadowColor: "rgba(0,0,0,0)",
			shadowOffsetX: 0,
			shadowOffsetY: 0,
			font: "10px sans-serif",
			textAlign: "start",
			textBaseline: "alphabetic",
			direction: "ltr",
			imageSmoothingEnabled: true,
		};
	}

	// Scan all current canvases and freeze eligible ones
	function scanAndFreezeCanvases() {
		if (!config.canvasFreezer) return;

		const canvases = document.querySelectorAll("canvas");
		stats.canvasesFound = canvases.length;

		for (const canvas of canvases) {
			if (canvas.dataset.perfFrozen === "1") continue;

			// Conservative mode: only freeze if inside invisible ancestor
			if (isOrphanCanvas(canvas)) {
				freezeCanvas(canvas);
				continue;
			}

			// Aggressive mode: also freeze if not drawn to recently
			if (config.canvasFreezerMode === "aggressive") {
				const lastDraw = canvasLastDraw.get(canvas);
				const age = lastDraw ? performance.now() - lastDraw : performance.now(); // never drawn → treat as old

				if (age > config.canvasIdleThresholdMs) {
					// Double-check it's not a visible important canvas
					// by checking if it has real dimensions and is in viewport
					if (
						!isInViewport(canvas) ||
						canvas.width === 0 ||
						canvas.height === 0
					) {
						freezeCanvas(canvas);
					}
				}
			}
		}

		updateToolbarStats();
	}

	function isInViewport(el) {
		const r = el.getBoundingClientRect();
		return (
			r.top < window.innerHeight &&
			r.bottom > 0 &&
			r.left < window.innerWidth &&
			r.right > 0
		);
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE D — LONG TASK MONITOR
	// ═══════════════════════════════════════════════════════════════════
	function startLongTaskMonitor() {
		if (!config.longTaskMonitor) return;
		if (typeof PerformanceObserver === "undefined") return;

		try {
			ltObserver = new PerformanceObserver((list) => {
				for (const entry of list.getEntries()) {
					stats.longTaskCount++;
					stats.longTaskTotalMs += entry.duration;
					if (entry.duration > stats.longTaskMaxMs) {
						stats.longTaskMaxMs = entry.duration;
					}

					// Signal that system is busy — Janitor will back off
					systemBusy = true;
					// Clear the busy flag after the task plus a small buffer
					setTimeout(() => {
						systemBusy = false;
					}, entry.duration + 100);
				}
				updateToolbarStats();
			});
			ltObserver.observe({ type: "longtask", buffered: true });
		} catch (e) {
			console.warn(
				"[MetaPerfOpt] Long task observer not supported:",
				e.message,
			);
		}
	}

	function stopLongTaskMonitor() {
		if (ltObserver) {
			ltObserver.disconnect();
			ltObserver = null;
		}
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE F — PERIODIC JANITOR
	// ═══════════════════════════════════════════════════════════════════
	function runJanitorCycle() {
		// Back off if system is mid-freeze
		if (systemBusy) return;

		// Back off if tab is hidden
		if (document.hidden) return;

		scanAndFreezeCanvases();
	}

	function startJanitor() {
		if (janitorTimer) clearInterval(janitorTimer);
		janitorTimer = setInterval(runJanitorCycle, config.janitorIntervalMs);
	}

	function stopJanitor() {
		if (janitorTimer) {
			clearInterval(janitorTimer);
			janitorTimer = null;
		}
	}

	// SPA-aware MutationObserver
	let mutationDebounce = null;
	let navigationDebounce = null;
	let currentChatContainer = null;
	let lastUrl = location.href;

	function getChatContainer() {
		const candidates = [...document.querySelectorAll("*")].filter((el) => {
			try {
				const s = getComputedStyle(el);
				return (
					(s.overflowY === "auto" || s.overflowY === "scroll") &&
					el.scrollHeight > 2000
				);
			} catch {
				return false;
			}
		});

		candidates.sort((a, b) => b.scrollHeight - a.scrollHeight);
		return candidates[0] || null;
	}

	function resetStatsForChat() {
		stats.canvasesFound = 0;
		stats.canvasFrozen = 0;
		updateToolbarStats();
	}

	function handleSPANavigation() {
		clearTimeout(navigationDebounce);

		navigationDebounce = setTimeout(() => {
			const newContainer = getChatContainer();

			if (newContainer && newContainer !== currentChatContainer) {
				currentChatContainer = newContainer;

				console.log("[MetaPerfOpt] Chat container replaced — rebinding");

				resetStatsForChat();

				// Wait for React flood-render to settle
				setTimeout(() => {
					scanAndFreezeCanvases();
					updateToolbarStats();
				}, 1500);
			}
		}, 1200);
	}

	function startMutationObserver() {
		if (mutationObs) {
			mutationObs.disconnect();
			mutationObs = null;
		}

		currentChatContainer = getChatContainer();

		mutationObs = new MutationObserver((mutations) => {
			// SPA URL change
			if (location.href !== lastUrl) {
				lastUrl = location.href;

				console.log("[MetaPerfOpt] URL changed:", lastUrl);

				handleSPANavigation();
			}

			let significantMutation = false;

			for (const m of mutations) {
				if (m.addedNodes.length > 20 || m.removedNodes.length > 20) {
					significantMutation = true;
					break;
				}
			}

			// Heavy React flood-render
			if (significantMutation) {
				handleSPANavigation();
				return;
			}

			clearTimeout(mutationDebounce);

			mutationDebounce = setTimeout(() => {
				if (systemBusy || document.hidden) return;

				scanAndFreezeCanvases();
				updateToolbarStats();
			}, 500);
		});

		mutationObs.observe(document.body, {
			childList: true,
			subtree: true,
		});
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE G — TOOLBAR UI
	// ═══════════════════════════════════════════════════════════════════
	let toolbar = null;
	let statsEl = null;
	let _settingsPopover = null;

	const TOOLBAR_ID = "meta-perf-toolbar";
	const POPOVER_ID = "meta-perf-settings-popover";

	GM_addStyle(`
        #${TOOLBAR_ID} {
            all: initial;
            position: fixed;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            z-index: 2147483647;
            background: #0a0a0a;
            color: #4ade80;
            padding: 0 6px;
            border-radius: 8px 8px 0 0;
            font: 11px/1.4 system-ui, -apple-system, sans-serif;
            display: flex;
            align-items: center;
            gap: 2px;
            user-select: none;
            box-shadow: 0 -2px 12px rgba(0,0,0,0.4);
        }
        #${TOOLBAR_ID} button {
            all: unset;
            cursor: pointer;
            padding: 3px 8px;
            border-radius: 4px;
            font: inherit;
            color: #cdd6f4;
            white-space: nowrap;
        }
        #${TOOLBAR_ID} button:hover { background: rgba(255,255,255,0.12); }
        #${TOOLBAR_ID} .perf-stats {
            padding: 3px 6px;
            pointer-events: none;
            white-space: nowrap;
        }
        #${POPOVER_ID} {
            all: initial;
            position: fixed;
            bottom: 26px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 2147483646;
            background: #1e1e2e;
            color: #cdd6f4;
            border-radius: 10px;
            padding: 14px 18px;
            font: 13px/1.5 system-ui, -apple-system, sans-serif;
            box-shadow: 0 -4px 24px rgba(0,0,0,0.55);
            display: flex;
            flex-direction: column;
            gap: 6px;
            min-width: 300px;
        }
        #${POPOVER_ID} .perf-row {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 4px 6px;
            border-radius: 6px;
            cursor: pointer;
        }
        #${POPOVER_ID} .perf-row:hover { background: rgba(255,255,255,0.06); }
        #${POPOVER_ID} .perf-on  { color: #a6e3a1; font-weight: 700; }
        #${POPOVER_ID} .perf-off { color: #6c7086; font-weight: 700; }
        #${POPOVER_ID} .perf-sep { border-top: 1px solid #313244; margin: 4px 0; }
        #${POPOVER_ID} .perf-head {
            font-weight: 700;
            font-size: 14px;
            margin-bottom: 4px;
            color: #cdd6f4;
        }
        #${POPOVER_ID} .perf-stat-block {
            background: #181825;
            border-radius: 6px;
            padding: 8px 10px;
            font-size: 11px;
            color: #a6adc8;
            line-height: 1.7;
        }
        #${POPOVER_ID} .perf-subrow {
            display: flex;
            gap: 6px;
            margin-left: 28px;
            flex-wrap: wrap;
        }
        #${POPOVER_ID} .perf-chip {
            padding: 1px 8px;
            border-radius: 10px;
            font-size: 11px;
            cursor: pointer;
            border: 1px solid #45475a;
            color: #cdd6f4;
        }
        #${POPOVER_ID} .perf-chip.active {
            background: #313244;
            border-color: #89b4fa;
            color: #89b4fa;
        }
    `);

	function createToolbar() {
		if (!config.showToolbar) return;
		if (document.getElementById(TOOLBAR_ID)) return;

		toolbar = document.createElement("div");
		toolbar.id = TOOLBAR_ID;

		// Force-clean button
		const cleanBtn = document.createElement("button");
		cleanBtn.textContent = "🧹";
		cleanBtn.title = "Force full cleanup now";
		cleanBtn.addEventListener("click", () => {
			runJanitorCycle();
		});

		// Stats text
		statsEl = document.createElement("span");
		statsEl.className = "perf-stats";

		// Settings button
		const settingsBtn = document.createElement("button");
		settingsBtn.textContent = "⚙️";
		settingsBtn.title = "Settings";
		settingsBtn.addEventListener("click", toggleSettingsPopover);

		toolbar.appendChild(cleanBtn);
		toolbar.appendChild(statsEl);
		toolbar.appendChild(settingsBtn);

		document.body.appendChild(toolbar);
		updateToolbarStats();
	}

	function updateToolbarStats() {
		if (!statsEl) return;

		const ltMs =
			stats.longTaskMaxMs > 0
				? ` · freeze ${stats.longTaskMaxMs.toFixed(0)}ms`
				: "";

		const busy = systemBusy ? " · 🔴" : "";

		statsEl.textContent = `canvas ${stats.canvasFrozen}/${stats.canvasesFound} frozen${ltMs}${busy}`;

		// Color coding
		if (systemBusy) {
			statsEl.style.color = "#f38ba8";
		} else if (stats.longTaskMaxMs > 1000) {
			statsEl.style.color = "#fab387";
		} else {
			statsEl.style.color = "#a6e3a1";
		}
	}

	function toggleSettingsPopover() {
		const existing = document.getElementById(POPOVER_ID);
		if (existing) {
			existing.remove();
			_settingsPopover = null;
			return;
		}
		openSettingsPopover();
	}

	function openSettingsPopover() {
		if (document.getElementById(POPOVER_ID)) return;

		const panel = document.createElement("div");
		panel.id = POPOVER_ID;

		// ── Header ──
		const head = document.createElement("div");
		head.className = "perf-head";
		head.textContent = "⚡ Meta AI Performance Optimizer";
		panel.appendChild(head);

		// ── Live stats block ──
		const statBlock = document.createElement("div");
		statBlock.className = "perf-stat-block";
		function refreshStatBlock() {
			statBlock.innerHTML =
				`Canvas: <b>${stats.canvasesFound}</b> found · <b>${stats.canvasFrozen}</b> frozen<br>` +
				`Long tasks: <b>${stats.longTaskCount}</b> · max <b>${stats.longTaskMaxMs.toFixed(0)}ms</b> · total <b>${(stats.longTaskTotalMs / 1000).toFixed(1)}s</b>`;
		}
		refreshStatBlock();
		panel.appendChild(statBlock);

		// Refresh stats every second while popover is open
		const statRefreshTimer = setInterval(refreshStatBlock, 1000);

		const sep0 = document.createElement("div");
		sep0.className = "perf-sep";
		panel.appendChild(sep0);

		// ── Toggle rows ──
		function addToggle(icon, label, getVal, onToggle, subContent) {
			const row = document.createElement("div");
			row.className = "perf-row";

			const renderRow = () => {
				const on = getVal();
				row.innerHTML =
					`<span style="width:20px;text-align:center">${icon}</span>` +
					`<span style="flex:1">${label}</span>` +
					`<span class="${on ? "perf-on" : "perf-off"}">${on ? "ON" : "OFF"}</span>`;
			};
			renderRow();

			row.addEventListener("click", () => {
				onToggle();
				renderRow();
				if (subContent) subContent.style.display = getVal() ? "flex" : "none";
				saveConfig();
				updateToolbarStats();
			});
			panel.appendChild(row);

			if (subContent) {
				subContent.style.display = getVal() ? "flex" : "none";
				panel.appendChild(subContent);
			}
		}

		// ── Sub-option: transition scope chips ──
		const transSubRow = document.createElement("div");
		transSubRow.className = "perf-subrow";
		for (const scope of ["full", "minimal"]) {
			const chip = document.createElement("span");
			chip.className =
				"perf-chip" +
				(config.transitionSurgeryScope === scope ? " active" : "");
			chip.textContent = scope;
			chip.title =
				scope === "full"
					? "Kill transition:all globally across the entire page"
					: "Only kill transitions inside the chat scroller";
			chip.addEventListener("click", (e) => {
				e.stopPropagation(); // don't toggle parent
				config.transitionSurgeryScope = scope;
				saveConfig();
				// Re-inject with new scope
				if (config.transitionSurgery) {
					removeSurgeonCSS();
					injectSurgeonCSS();
				}
				transSubRow
					.querySelectorAll(".perf-chip")
					// biome-ignore lint/suspicious/useIterableCallbackReturn: intentional side effect call
					.forEach((c) => c.classList.remove("active"));
				chip.classList.add("active");
			});
			transSubRow.appendChild(chip);
		}

		addToggle(
			"⚡",
			"Transition surgery",
			() => config.transitionSurgery,
			() => {
				config.transitionSurgery = !config.transitionSurgery;
				if (config.transitionSurgery) injectSurgeonCSS();
				else removeSurgeonCSS();
			},
			transSubRow,
		);

		// ── Sub-option: canvas freezer mode chips ──
		const canvasSubRow = document.createElement("div");
		canvasSubRow.className = "perf-subrow";
		for (const mode of ["aggressive", "conservative"]) {
			const chip = document.createElement("span");
			chip.className = `perf-chip${config.canvasFreezerMode === mode ? " active" : ""}`;
			chip.textContent = mode;
			chip.title =
				mode === "aggressive"
					? "Also freeze canvases not drawn to in 30s"
					: "Only freeze canvases inside invisible parents";
			chip.addEventListener("click", (e) => {
				e.stopPropagation();
				config.canvasFreezerMode = mode;
				saveConfig();
				canvasSubRow
					.querySelectorAll(".perf-chip")
					// biome-ignore lint/suspicious/useIterableCallbackReturn: intentional side effect call
					.forEach((c) => c.classList.remove("active"));
				chip.classList.add("active");
			});
			canvasSubRow.appendChild(chip);
		}

		addToggle(
			"🖼️",
			"Canvas freezer",
			() => config.canvasFreezer,
			() => {
				config.canvasFreezer = !config.canvasFreezer;
				if (!config.canvasFreezer && originalGetContext) unpatchGetContext();
				if (config.canvasFreezer) {
					patchGetContext();
					scanAndFreezeCanvases();
				}
			},
			canvasSubRow,
		);

		addToggle(
			"📦",
			"DOM containment",
			() => config.domContainment,
			() => {
				config.domContainment = !config.domContainment;
				if (config.domContainment) injectContainmentCSS();
				else removeContainmentCSS();
			},
		);

		addToggle(
			"📡",
			"Long task monitor",
			() => config.longTaskMonitor,
			() => {
				config.longTaskMonitor = !config.longTaskMonitor;
				if (config.longTaskMonitor) startLongTaskMonitor();
				else stopLongTaskMonitor();
			},
		);

		// ── Separator ──
		const sep1 = document.createElement("div");
		sep1.className = "perf-sep";
		panel.appendChild(sep1);

		// ── Action rows ──
		function addAction(icon, label, fn) {
			const row = document.createElement("div");
			row.className = "perf-row";
			row.innerHTML = `<span style="width:20px;text-align:center">${icon}</span><span>${label}</span>`;
			row.addEventListener("click", () => {
				fn();
				panel.remove();
				_settingsPopover = null;
				clearInterval(statRefreshTimer);
			});
			panel.appendChild(row);
		}

		addAction("🧹", "Force full cleanup now", () => {
			runJanitorCycle();
			updateToolbarStats();
		});

		addAction("♻️", "Reset stats counters", () => {
			stats.canvasFrozen = 0;
			stats.longTaskCount = 0;
			stats.longTaskMaxMs = 0;
			stats.longTaskTotalMs = 0;
			updateToolbarStats();
		});

		addAction("🔄", "Reset all settings to defaults", () => {
			config = { ...DEFAULT_CONFIG };
			saveConfig();
			reapplyAllModules();
		});

		document.body.appendChild(panel);
		_settingsPopover = panel;

		// Close on outside click
		setTimeout(() => {
			function onOutside(e) {
				if (!panel.contains(e.target) && !toolbar.contains(e.target)) {
					panel.remove();
					_settingsPopover = null;
					clearInterval(statRefreshTimer);
					document.removeEventListener("mousedown", onOutside);
				}
			}
			document.addEventListener("mousedown", onOutside);
		}, 0);

		// Close on Escape
		function onKey(e) {
			if (e.key === "Escape") {
				panel.remove();
				_settingsPopover = null;
				clearInterval(statRefreshTimer);
				document.removeEventListener("keydown", onKey);
			}
		}
		document.addEventListener("keydown", onKey);
	}

	// ═══════════════════════════════════════════════════════════════════
	// ORCHESTRATION
	// ═══════════════════════════════════════════════════════════════════

	// Re-apply all active modules after a settings reset
	function reapplyAllModules() {
		removeSurgeonCSS();
		removeContainmentCSS();
		unpatchGetContext();
		stopLongTaskMonitor();
		stopJanitor();

		if (config.transitionSurgery) injectSurgeonCSS();
		if (config.domContainment) injectContainmentCSS();
		if (config.canvasFreezer) {
			patchGetContext();
			scanAndFreezeCanvases();
		}
		if (config.longTaskMonitor) startLongTaskMonitor();

		startMutationObserver();
		startJanitor();
		updateToolbarStats();
	}

	// Phase 1: CSS injections — run at document-start before any paint
	// (Modules A and C are pure CSS — safe to inject into <html> immediately)
	function phase1() {
		if (config.transitionSurgery) injectSurgeonCSS();
		if (config.domContainment) injectContainmentCSS();
	}

	// Phase 2: Everything that needs document.body
	function phase2() {
		// Canvas patch — needs to be in place before Meta renders canvases
		if (config.canvasFreezer) {
			patchGetContext();
		}

		// Toolbar
		if (config.showToolbar) {
			createToolbar();
		}

		// Long task monitor
		if (config.longTaskMonitor) {
			startLongTaskMonitor();
		}

		// Initial cleanup pass
		runJanitorCycle();

		// Ongoing: mutation observer + interval janitor
		startMutationObserver();
		startJanitor();

		// Second pass after a delay — catches late-rendered content
		setTimeout(runJanitorCycle, 3000);
		setTimeout(runJanitorCycle, 8000);

		console.log(
			"[MetaPerfOpt] Meta AI Performance Optimizer active.\n" +
				`  Transition surgery : ${config.transitionSurgery} (${config.transitionSurgeryScope})\n` +
				`  Canvas freezer     : ${config.canvasFreezer} (${config.canvasFreezerMode})\n` +
				`  DOM containment    : ${config.domContainment}\n` +
				`  Long task monitor  : ${config.longTaskMonitor}`,
		);
	}

	// ── Boot sequence ──
	phase1(); // runs immediately at document-start

	if (document.body) {
		phase2();
	} else {
		// document-start: body may not exist yet
		const bodyWatcher = new MutationObserver(() => {
			if (document.body) {
				bodyWatcher.disconnect();
				phase2();
			}
		});
		bodyWatcher.observe(document.documentElement, { childList: true });
	}
})();