Hides tagged images in the "All" section of your saved imagine creations.
// ==UserScript==
// @name Grok TidyFavs
// @namespace https://github.com/Zhiro90
// @version 1.3.2
// @description Hides tagged images in the "All" section of your saved imagine creations.
// @author Zhiro90
// @match *://grok.com/*
// @icon https://grok.com/images/favicon.ico
// @homepageURL https://github.com/Zhiro90/grok-tidyfavs
// @supportURL https://github.com/Zhiro90/grok-tidyfavs/issues
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// 🎛️ CONFIGURACIÓN DE DESARROLLO
// ==========================================
const DEBUG_MODE = false;
let savedMemory = JSON.parse(localStorage.getItem('grok_tagged_memory') || '{}');
let hideTagged = localStorage.getItem('grok_hide_tagged') !== 'false';
let currentFolderState = undefined;
let zoomTimeout = null;
let isScrolling = false;
let scrollTimeout = null;
let pendingLayoutFix = false;
function logDebug(type, message, color = "#bdc3c7") {
if (!DEBUG_MODE) return;
console.log(`%c[DEBUG ${type}] ${message}`, `color: ${color}; font-size: 11px;`);
}
console.log("%c🚀 GROK TIDYFAVS V1.3.2 (INVISIBLE VOLUME HACK) LOADED", "color: #00ff00; font-weight: bold;");
// ==========================================
// 🎨 CSS INJECTIONS (Global Scroll Lock)
// ==========================================
if (!document.getElementById('grok-tidy-css')) {
const style = document.createElement('style');
style.id = 'grok-tidy-css';
style.textContent = `
body.tidy-scroll-lock {
overflow: hidden !important;
overscroll-behavior: none !important;
}
`;
document.head.appendChild(style);
}
// ==========================================
// 🕹️ 1. SCROLL GUARD
// ==========================================
window.addEventListener('scroll', () => {
isScrolling = true;
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
isScrolling = false;
if (pendingLayoutFix) {
logDebug("SCROLL", "Scroll stopped. Triggering sync.", "#2ecc71");
pendingLayoutFix = false;
requestLayoutSync();
}
}, 500);
}, { capture: true, passive: true });
// ==========================================
// 🖥️ 2. DOM MANAGER & HACKS
// ==========================================
const SYSTEM_TAB_NAMES = new Set([
'All', 'Todas', 'Todos', 'Tout', 'Tous', 'Alle', 'Tutti', 'すべて', '全て', '全部',
'Favorites', 'Favourites', 'Guardados', 'Favoritos', 'Favoris', 'Favoriten', 'Preferiti', 'Saved',
]);
function getMediaId(node) {
if (node.dataset.grokMediaId) return node.dataset.grokMediaId;
try {
const key = Object.keys(node).find(k => k.startsWith('__reactFiber$'));
if (!key) return null;
let curr = node[key];
for (let i = 0; i < 25; i++) {
if (!curr) break;
const props = curr.memoizedProps;
const data = props?.data || props?.post || props?.item;
if (data?.id && typeof data.id === 'string' && data.id.length > 20) {
node.dataset.grokMediaId = data.id;
return data.id;
}
curr = curr.return;
}
} catch (e) {}
return null;
}
function getActiveFolderName() {
const activeBtn = document.querySelector('button.bg-primary');
if (!activeBtn) return null;
const name = activeBtn.textContent.trim();
return SYSTEM_TAB_NAMES.has(name) ? null : (name || null);
}
function showToast(message, type = 'info') {
const existingToasts = document.querySelectorAll('.grok-tidyfavs-toast');
existingToasts.forEach(t => t.remove());
const toast = document.createElement('div');
toast.className = 'grok-tidyfavs-toast';
toast.textContent = message;
let bgColor = 'rgba(0, 0, 0, 0.9)';
let borderColor = '#444';
if (type === 'success') {
bgColor = 'rgba(20, 80, 40, 0.95)';
borderColor = '#2ecc71';
} else if (type === 'warning') {
bgColor = 'rgba(100, 30, 30, 0.95)';
borderColor = '#ff4d4d';
}
Object.assign(toast.style, {
position: 'fixed', bottom: '85px', right: '20px', zIndex: '9999999',
backgroundColor: bgColor, color: '#fff',
padding: '12px 18px', borderRadius: '8px', border: `1px solid ${borderColor}`,
fontSize: '14px', fontFamily: 'sans-serif', fontWeight: '500',
boxShadow: '0 4px 12px rgba(0,0,0,0.5)', pointerEvents: 'none',
transition: 'opacity 0.3s ease-in-out', opacity: '0'
});
document.body.appendChild(toast);
requestAnimationFrame(() => { toast.style.opacity = '1'; });
setTimeout(() => { toast.style.opacity = '0'; }, 2000);
setTimeout(() => { toast.remove(); }, 2300);
}
function triggerZoom() {
const originalZoom = document.body.style.zoom || '';
document.body.style.zoom = '0.999';
setTimeout(() => {
document.body.style.zoom = originalZoom;
window.dispatchEvent(new Event('resize'));
}, 50);
}
function requestLayoutSync() {
if (isScrolling) {
pendingLayoutFix = true;
return;
}
if (zoomTimeout) clearTimeout(zoomTimeout);
// Grok recomendó subir esto a 300ms para darle respiro al Virtualizer
zoomTimeout = setTimeout(triggerZoom, 300);
}
function fireTripleTapSequence() {
setTimeout(triggerZoom, 100);
setTimeout(triggerZoom, 500);
setTimeout(triggerZoom, 1200);
}
// 🔥 EL HACK DE VOLUMEN INVISIBLE RECOMENDADO POR GROK
function applyShrinkHide(wrapper) {
if (wrapper.dataset.tidyShrink === 'true') return false;
wrapper.style.removeProperty('display');
// El sweet spot: 10px de altura para calmar a React, pero 100% invisible.
wrapper.style.setProperty('height', '10px', 'important');
wrapper.style.setProperty('min-height', '10px', 'important');
wrapper.style.setProperty('width', '1px', 'important');
wrapper.style.setProperty('overflow', 'hidden', 'important');
wrapper.style.setProperty('opacity', '0', 'important');
wrapper.style.setProperty('margin', '0', 'important');
wrapper.style.setProperty('padding', '0', 'important');
wrapper.style.setProperty('pointer-events', 'none', 'important');
wrapper.style.setProperty('border', 'none', 'important');
wrapper.dataset.tidyShrink = 'true';
return true;
}
function removeShrinkHide(wrapper) {
if (wrapper.dataset.tidyShrink !== 'true') return false;
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('min-height');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('opacity');
wrapper.style.removeProperty('margin');
wrapper.style.removeProperty('padding');
wrapper.style.removeProperty('pointer-events');
wrapper.style.removeProperty('border');
delete wrapper.dataset.tidyShrink;
return true;
}
// ==========================================
// 👁️ THE MUTATION OBSERVER (Zero-Blink Engine)
// ==========================================
const domObserver = new MutationObserver((mutations) => {
const url = window.location.href;
if (!url.includes('favorites') && !url.includes('collection') && !url.includes('saved') && !url.endsWith('/all')) return;
let needsSync = false;
const isAllView = !getActiveFolderName();
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) {
const cards = node.classList.contains('group/media-post-masonry-card')
? [node]
: node.querySelectorAll('.group\\/media-post-masonry-card');
cards.forEach(card => {
const wrapper = card.parentElement;
const mediaId = getMediaId(card);
if (mediaId && hideTagged && isAllView && savedMemory[mediaId]) {
applyShrinkHide(wrapper);
needsSync = true;
}
});
}
}
}
}
if (needsSync) {
logDebug("OBSERVER", "Squished hidden elements instantly before paint.");
if (window.scrollY === 0 && isAllView) {
window.scrollTo(0, 2);
}
}
});
domObserver.observe(document.body, { childList: true, subtree: true });
// ==========================================
// 🧠 MEMORY MANAGER (Background Sync)
// ==========================================
function updateDOM() {
const folderName = getActiveFolderName();
const cards = Array.from(document.querySelectorAll('.group\\/media-post-masonry-card'));
if (cards.length === 0) return;
let learnedCount = 0;
let maxRelativeBottom = 0;
let needsLayoutFix = false;
const isAllView = !folderName;
const masonryContainer = cards[0].parentElement.parentElement;
const containerRect = masonryContainer ? masonryContainer.getBoundingClientRect() : null;
const newFolderState = isAllView ? 'ALL' : folderName;
if (currentFolderState !== newFolderState) {
currentFolderState = newFolderState;
if (isAllView && hideTagged) {
fireTripleTapSequence();
}
}
cards.forEach(card => {
const wrapper = card.parentElement;
const mediaId = getMediaId(card);
if (!mediaId) return;
if (!isAllView) {
if (removeShrinkHide(wrapper)) needsLayoutFix = true;
if (!savedMemory[mediaId]) {
savedMemory[mediaId] = folderName;
learnedCount++;
}
} else if (hideTagged && savedMemory[mediaId]) {
if (applyShrinkHide(wrapper)) needsLayoutFix = true;
} else {
if (removeShrinkHide(wrapper)) needsLayoutFix = true;
if (hideTagged && isAllView && containerRect && wrapper.dataset.tidyShrink !== 'true') {
const reactTransformY = parseFloat(wrapper.style.translate?.split(' ')[1] || "0");
const relativeBottom = reactTransformY + wrapper.getBoundingClientRect().height;
if (relativeBottom > maxRelativeBottom) {
maxRelativeBottom = relativeBottom;
}
}
}
});
if (needsLayoutFix) {
logDebug("RENDER", "Applying Zoom Jiggle to force React measurer update");
requestLayoutSync();
}
let currentTargetHeight = "100vh";
if (isAllView && hideTagged && masonryContainer) {
if (maxRelativeBottom > 0) {
let proposedHeight = Math.ceil(maxRelativeBottom) + 800;
let currentHeightVal = parseFloat(masonryContainer.dataset.tidyHeight || "0");
if (Math.abs(proposedHeight - currentHeightVal) > 15) {
currentTargetHeight = `${proposedHeight}`;
masonryContainer.dataset.tidyHeight = currentTargetHeight;
masonryContainer.style.setProperty('height', `${currentTargetHeight}px`, 'important');
masonryContainer.dataset.tidyBrake = 'true';
}
} else {
masonryContainer.style.setProperty('height', '100vh', 'important');
masonryContainer.dataset.tidyHeight = "0";
}
} else if (masonryContainer && masonryContainer.dataset.tidyBrake) {
masonryContainer.style.removeProperty('height');
delete masonryContainer.dataset.tidyBrake;
delete masonryContainer.dataset.tidyHeight;
}
if (learnedCount > 0) {
localStorage.setItem('grok_tagged_memory', JSON.stringify(savedMemory));
showToast(`✅ ${learnedCount} new images added to memory`, 'success');
}
}
// ==========================================
// 🎛️ 3. USER INTERFACE
// ==========================================
function createUI() {
if (document.getElementById('grok-filter-container')) return;
const containerUI = document.createElement('div');
containerUI.id = 'grok-filter-container';
Object.assign(containerUI.style, {
position: 'fixed', bottom: '20px', right: '80px', zIndex: '999999',
display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'center'
});
const resetBtn = document.createElement('button');
resetBtn.title = "Clear memory (Already refreshed? Reload to show hidden)";
Object.assign(resetBtn.style, {
width: '38px', height: '38px', borderRadius: '50%',
backgroundColor: 'rgba(20,20,20,0.9)', border: '1px solid #555',
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#aaa', fontSize: '18px', boxShadow: '0 2px 6px rgba(0,0,0,0.4)', transition: 'all 0.2s'
});
resetBtn.innerHTML = `
<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
`;
resetBtn.onmouseover = () => { resetBtn.style.color = '#ff4d4d'; resetBtn.style.borderColor = '#ff4d4d'; };
resetBtn.onmouseout = () => { resetBtn.style.color = '#aaa'; resetBtn.style.borderColor = '#555'; };
resetBtn.onclick = () => {
savedMemory = {};
localStorage.setItem('grok_tagged_memory', '{}');
hideTagged = false;
localStorage.setItem('grok_hide_tagged', false);
const tb = document.getElementById('grok-toggle-btn');
if(tb) {
tb.style.color = '#fff';
tb.innerHTML = '👁️';
tb.title = 'Hide organized images';
}
showToast("Memory wiped. (Reload if images are missing)", 'warning');
fireTripleTapSequence();
};
const toggleBtn = document.createElement('button');
toggleBtn.id = 'grok-toggle-btn';
Object.assign(toggleBtn.style, {
width: '50px', height: '50px', borderRadius: '50%',
backgroundColor: 'rgba(0,0,0,0.9)', border: '2px solid #444',
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
color: hideTagged ? '#ff4d4d' : '#fff', fontSize: '24px', boxShadow: '0 4px 10px rgba(0,0,0,0.5)', transition: 'color 0.2s'
});
toggleBtn.innerHTML = hideTagged ? '👁️🗨️' : '👁️';
toggleBtn.title = hideTagged ? 'Show organized images' : 'Hide organized images';
toggleBtn.onclick = () => {
hideTagged = !hideTagged;
localStorage.setItem('grok_hide_tagged', hideTagged);
toggleBtn.style.color = hideTagged ? '#ff4d4d' : '#fff';
toggleBtn.innerHTML = hideTagged ? '👁️🗨️' : '👁️';
toggleBtn.title = hideTagged ? 'Show organized images' : 'Hide organized images';
showToast(hideTagged ? "Applying Filter..." : "Disabling Filter...", "info");
fireTripleTapSequence();
};
containerUI.appendChild(resetBtn);
containerUI.appendChild(toggleBtn);
document.body.appendChild(containerUI);
}
// ==========================================
// ⏰ MASTER LOOP
// ==========================================
setInterval(() => {
const url = window.location.href;
const isTargetPage = url.includes('favorites') || url.includes('collection') || url.includes('saved') || url.endsWith('/all');
let uiContainer = document.getElementById('grok-filter-container');
if (!isTargetPage) {
if (uiContainer) uiContainer.style.display = 'none';
document.body.classList.remove('tidy-scroll-lock');
return;
} else {
if (uiContainer) {
uiContainer.style.display = 'flex';
} else {
createUI();
}
document.body.classList.add('tidy-scroll-lock');
updateDOM();
}
}, 150);
})();