// UI element hiding for Instagram web. // // SCROLL LOCK WARNING: // MutationObserver callbacks with {childList:true, subtree:true} fire hundreds // of times/sec on Instagram's infinite scroll feed. Expensive querySelectorAll // calls inside these callbacks block the main thread = scroll jank. // All JS hiders below use a 300ms debounce so they run only after mutations settle. // ─── CSS-based ──────────────────────────────────────────────────────────────── // FIX: Like count CSS. // Instagram's like BUTTON has aria-label="Like" (the verb) — NOT the count. // [role="button"][aria-label$=" likes"] never matches anything. // The COUNT lives in a[href*="/liked_by/"] (e.g. "1,234 likes" link). // We hide that link. The JS hider below catches React-rendered span variants. const String kHideLikeCountsCSS = ''' a[href*="/liked_by/"], section a[href*="/liked_by/"] { display: none !important; } '''; const String kHideFollowerCountsCSS = ''' a[href*="/followers/"] span, a[href*="/following/"] span { opacity: 0 !important; pointer-events: none !important; } '''; // Stories bar CSS — multiple selectors for different Instagram DOM versions. // :has() is supported in WebKit (Instagram's engine). Targets the container, // not individual story items which is what [aria-label*="Stories"] matches. const String kHideStoriesBarCSS = ''' [aria-label*="Stories"], [aria-label*="stories"], [role="list"]:has([aria-label*="tory"]), [role="listbox"]:has([aria-label*="tory"]), div[style*="overflow"][style*="scroll"]:has(canvas), section > div > div[style*="overflow-x"] { display: none !important; } '''; const String kHideExploreTabCSS = ''' a[href="/explore/"], a[href="/explore"] { display: none !important; } '''; const String kHideReelsTabCSS = ''' a[href="/reels/"], a[href="/reels"] { display: none !important; } '''; const String kHideShopTabCSS = ''' a[href*="/shop"], a[href*="/shopping"] { display: none !important; } '''; // ─── JS-based ───────────────────────────────────────────────────────────────── // Like counts — JS fallback for React-rendered count spans not caught by CSS. // Scans for text matching "1,234 likes" / "12.3K views" patterns. const String kHideLikeCountsJS = r''' (function() { function hideLikeCounts() { try { // Hide liked_by links and their immediate parent wrapper document.querySelectorAll('a[href*="/liked_by/"]').forEach(function(el) { try { el.style.setProperty('display', 'none', 'important'); // Also hide the parent span/div that wraps the count text if (el.parentElement) { el.parentElement.style.setProperty('display', 'none', 'important'); } } catch(_) {} }); // Scan spans for numeric like/view count text patterns document.querySelectorAll('span').forEach(function(el) { try { const text = el.textContent.trim(); // Matches: "1,234 likes", "12.3K views", "1 like", "45 views", etc. if (/^[\d,.]+[KkMm]?\s+(like|likes|view|views)$/.test(text)) { el.style.setProperty('display', 'none', 'important'); } } catch(_) {} }); } catch(_) {} } hideLikeCounts(); if (!window.__fgLikeCountObserver) { let _t = null; window.__fgLikeCountObserver = new MutationObserver(() => { clearTimeout(_t); _t = setTimeout(hideLikeCounts, 300); }); window.__fgLikeCountObserver.observe( document.documentElement, { childList: true, subtree: true } ); } })(); '''; // Stories bar JS — structural detection when CSS selectors don't match. // Two strategies: // 1. aria-label scan on role=list/listbox elements // 2. BoundingClientRect check: story circles are square, narrow (<120px), appear in a row const String kHideStoriesBarJS = r''' (function() { function hideStories() { try { // Strategy 1: aria-label on list containers document.querySelectorAll('[role="list"], [role="listbox"]').forEach(function(el) { try { const label = (el.getAttribute('aria-label') || '').toLowerCase(); if (label.includes('stor')) { el.style.setProperty('display', 'none', 'important'); } } catch(_) {} }); // Strategy 2: BoundingClientRect — story circles are narrow square items in a row. // Look for a