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와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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 });
	}
})();