// ==UserScript==
// @name X/Twitter - xGrid Media Timeline
// @namespace https://github.com/BD9Max/userscripts
// @version 2.5.1.2
// @description Profile media timeline grid filters: Display images, videos or both. Grid size (1-5) changer. Media deduplication - removes duplicate media from grid. Blocks GIFs. Uncrops thumbnails. Control Panel for settings/save.
// @icon https://raw.githubusercontent.com/BD9Max/userscripts/refs/heads/main/media/icons/X_Twitter_xGrid_Media_Timeline_Icon_64.png
// @author krbd9max
// @include https://x.com/*
// @include https://twitter.com/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect raw.githubusercontent.com
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration
const HAMMING_THRESHOLD = 3; // Bit difference tolerance for image similarities
// --- BK-TREE DATA STRUCTURE FOR FAST HAMMING DISTANCE LOOKUPS ---
class BKNode {
constructor(hash, cellId) {
this.hash = hash;
this.cellIds = [cellId];
this.children = {}; // distance -> BKNode
}
}
class BKTree {
constructor() {
this.root = null;
}
static hammingDistance(h1, h2) {
let xor = h1 ^ h2;
let count = 0;
while (xor > 0n) {
if (xor & 1n) count++;
xor >>= 1n;
}
return count;
}
add(hash, cellId) {
if (!this.root) {
this.root = new BKNode(hash, cellId);
return null;
}
let curr = this.root;
while (true) {
const dist = BKTree.hammingDistance(curr.hash, hash);
if (dist === 0) {
curr.cellIds.push(cellId);
return curr.cellIds[0];
}
if (curr.children[dist]) {
curr = curr.children[dist];
} else {
curr.children[dist] = new BKNode(hash, cellId);
return null;
}
}
}
search(hash, maxDist, node = this.root, results = []) {
if (!node) return results;
const dist = BKTree.hammingDistance(node.hash, hash);
if (dist <= maxDist) results.push({ node, dist });
const minDist = dist - maxDist;
const highDist = dist + maxDist;
for (let d in node.children) {
const numericD = parseInt(d, 10);
if (numericD >= minDist && numericD <= highDist) {
this.search(hash, maxDist, node.children[numericD], results);
}
}
return results;
}
}
// --- DHASH ALGORITHM ---
function computeDHash(imgSrc) {
return new Promise((resolve) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = imgSrc;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 9;
canvas.height = 8;
ctx.drawImage(img, 0, 0, 9, 8);
let imgData;
try {
imgData = ctx.getImageData(0, 0, 9, 8).data;
} catch (e) {
resolve(null);
return;
}
const gray = new Uint8Array(72);
for (let i = 0; i < 72; i++) {
gray[i] = 0.299 * imgData[i * 4] + 0.587 * imgData[i * 4 + 1] + 0.114 * imgData[i * 4 + 2];
}
let hash = 0n;
let bitPosition = 0n;
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
if (gray[y * 9 + x] > gray[y * 9 + (x + 1)]) hash |= (1n << bitPosition);
bitPosition++;
}
}
resolve(hash);
};
img.onerror = () => resolve(null);
});
}
// --- TRACKING GLOBALS ---
const imageTree = new BKTree();
const cellRegistry = new Map();
let deduplicatorMode = "SCAN";
const knownDuplicates = new Set();
const knownUniques = new Set();
// --- ROUTE & CONTEXT CHECKER WITH STATE RETENTION ---
function updatePageContext() {
const isMediaTab = /\/[^/]+\/media\/?$/.test(window.location.pathname);
if (isMediaTab) {
document.body.setAttribute('data-xgrid-active', 'true');
} else {
if (!document.querySelector('[role="dialog"]') && !document.querySelector('[data-testid="layerViews"]')) {
document.body.removeAttribute('data-xgrid-active');
document.body.removeAttribute('filter-by');
}
}
}
// --- GLOBAL PERSISTENCE STORAGE CONTROLLER ENGINE ---
function saveAllSettings() {
if (document.body.getAttribute('data-save-settings') === "ON") {
const filterBy = document.body.getAttribute('filter-by');
if (filterBy) localStorage.setItem('xgrid-filter-by', filterBy);
else localStorage.removeItem('xgrid-filter-by');
localStorage.setItem('xgrid-column-count', document.body.getAttribute('column-count') || "3");
localStorage.setItem('xgrid-dedup-mode', document.body.getAttribute('dedup-mode') || "SCAN");
localStorage.setItem('xgrid-crop-mode', document.body.getAttribute('data-crop-mode') || "ON");
} else {
localStorage.removeItem('xgrid-filter-by');
localStorage.removeItem('xgrid-column-count');
localStorage.removeItem('xgrid-dedup-mode');
localStorage.removeItem('xgrid-crop-mode');
}
}
function loadAllSettings() {
const globalSaveState = localStorage.getItem('xgrid-save-settings') || "OFF";
document.body.setAttribute('data-save-settings', globalSaveState);
if (globalSaveState === "ON") {
const filterBy = localStorage.getItem('xgrid-filter-by');
if (filterBy) document.body.setAttribute('filter-by', filterBy);
else document.body.removeAttribute('filter-by');
const cols = localStorage.getItem('xgrid-column-count');
if (cols) document.body.setAttribute('column-count', cols);
const dedup = localStorage.getItem('xgrid-dedup-mode');
if (dedup) {
document.body.setAttribute('dedup-mode', dedup);
deduplicatorMode = dedup;
}
const crop = localStorage.getItem('xgrid-crop-mode');
if (crop) document.body.setAttribute('data-crop-mode', crop);
}
}
// Control panel UI / logo
let cachedToggleIconDataUri = null;
function loadToggleIcon(imgEl) {
if (!imgEl) return;
if (cachedToggleIconDataUri) {
imgEl.src = cachedToggleIconDataUri;
return;
}
if (typeof GM_xmlhttpRequest === 'undefined') return;
GM_xmlhttpRequest({
method: 'GET',
url: 'https://raw.githubusercontent.com/BD9Max/userscripts/refs/heads/main/media/icons/X_Twitter_xGrid_Media_Timeline_Icon_64.png',
responseType: 'arraybuffer',
onload: (response) => {
const bytes = new Uint8Array(response.response);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
cachedToggleIconDataUri = 'data:image/png;base64,' + btoa(binary);
imgEl.src = cachedToggleIconDataUri;
}
});
}
function createFilterButtons() {
if (!document.body.hasAttribute('data-xgrid-active')) {
const existing = document.getElementById("media-filter-controls");
if (existing) existing.style.display = "none";
return;
}
if (document.getElementById("media-filter-controls")) {
document.getElementById("media-filter-controls").style.display = "flex";
return;
}
// --- Main Floating Container Panel
const xgridCP = document.createElement("div");
xgridCP.id = "media-filter-controls";
xgridCP.style.cssText = `
display: flex;
position: fixed;
top: 130px;
right: 40px;
z-index: 9999;
border-radius: 15px;
padding: 8px;
gap: 10px;
flex-direction: column;
align-items: stretch;
background: rgba(0, 0, 0, 0.85);
border: 1px solid #333;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
backdrop-filter: blur(30px);
width: 140px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`;
// --- Minimize / Expand Toggle Button
const toggleBtn = document.createElement("button");
toggleBtn.id = "media-filter-toggle-btn";
toggleBtn.innerHTML = `
`;
loadToggleIcon(toggleBtn.querySelector("#media-filter-toggle-icon"));
toggleBtn.title = "xGrid Media Timeline";
toggleBtn.style.cssText = `
position: absolute;
left: -32px;
top: 10px;
width: 34px;
height: 42px;
border-radius: 6px 0 0 6px;
border: 1px solid #333;
border-right: none;
background: rgba(0, 0, 0, 0.85);
color: #e7e9ea;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(30px);
transition: transform 0.3s ease, color 0.15s ease, background-color 0.15s ease;
padding: 0;
box-shadow: -2px 0 8px rgba(0,0,0,0.3);
`;
// --- Vertical Label
const closedTextLabel = document.createElement("div");
closedTextLabel.id = "media-filter-closed-label";
closedTextLabel.textContent = "xGrid Media Timeline";
closedTextLabel.style.cssText = `
position: absolute;
left: -26px;
top: 45px;
color: #71767b;
background: rgba(0, 0, 0, 0.85);
font-size: 9px;
font-weight: 800;
letter-spacing: 1px;
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotate(180deg);
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease, color 0.15s ease;
`;
xgridCP.appendChild(closedTextLabel);
// Toggle button click logic with persistence
let isOpen = localStorage.getItem('xgrid-cp-open') !== 'false';
// Initial state logic
if (isOpen) {
xgridCP.style.right = "40px";
toggleBtn.style.transform = "rotate(0deg)";
closedTextLabel.style.opacity = "0";
} else {
xgridCP.style.right = "-153px";
toggleBtn.style.transform = "rotate(180deg)";
closedTextLabel.style.opacity = "1";
}
toggleBtn.onclick = (e) => {
e.stopPropagation();
isOpen = !isOpen;
localStorage.setItem('xgrid-cp-open', isOpen);
if (isOpen) {
xgridCP.style.right = "40px";
toggleBtn.style.transform = "rotate(0deg)";
closedTextLabel.style.opacity = "0";
} else {
xgridCP.style.right = "-153px";
toggleBtn.style.transform = "rotate(180deg)";
closedTextLabel.style.opacity = "1";
}
};
xgridCP.appendChild(toggleBtn);
// --- Filter Section Header
const titleFilters = document.createElement("div");
titleFilters.textContent = "xGrid MEDIA FILTERS:";
titleFilters.style.cssText = `color: #e7e9ea; font-size: 12px; font-weight: 750; letter-spacing: 0.5px; padding: 0 2px;`;
xgridCP.appendChild(titleFilters);
// Images / Videos / All filter buttons
const filterGroup = document.createElement("div");
filterGroup.style.cssText = `display: flex; flex-direction: row; gap: 4px; width: 100%; justify-content: space-between;`;
const allBtn = document.createElement("button");
allBtn.id = "all";
allBtn.textContent = "OFF";
allBtn.onclick = () => { document.body.removeAttribute("filter-by"); saveAllSettings(); };
const imagesBtn = document.createElement("button");
imagesBtn.id = "images";
imagesBtn.textContent = "IMG";
imagesBtn.onclick = () => { document.body.setAttribute("filter-by", "images"); saveAllSettings(); };
const videosBtn = document.createElement("button");
videosBtn.id = "videos";
videosBtn.textContent = "VID";
videosBtn.onclick = () => { document.body.setAttribute("filter-by", "videos"); saveAllSettings(); };
filterGroup.appendChild(allBtn);
filterGroup.appendChild(imagesBtn);
filterGroup.appendChild(videosBtn);
xgridCP.appendChild(filterGroup);
// --- Grid Scaler Header
const titleColumns = document.createElement("div");
titleColumns.textContent = "GRID SCALE:";
titleColumns.style.cssText = `color: #e7e9ea; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; padding: 0 2px; margin-top: 6px;`;
xgridCP.appendChild(titleColumns);
// Grid width adjustments
const scalerGroup = document.createElement("div");
scalerGroup.style.cssText = `display: flex; gap: 4px; align-items: center; justify-content: space-between; background: #000; border: 1px solid #3d4144; border-radius: 20px; padding: 2px 4px;`;
if (!document.body.hasAttribute("column-count")) {
document.body.setAttribute("column-count", "3");
}
const valueDisplay = document.createElement("span");
valueDisplay.style.cssText = `background-color: rgb(26, 77, 216); color: #fff; font-size: 11px; font-weight: bold; width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; text-align: center;`;
valueDisplay.textContent = document.body.getAttribute("column-count");
const updateCols = (delta) => {
let current = parseInt(document.body.getAttribute("column-count") || "3", 10);
let next = current + delta;
if (next >= 1 && next <= 5) {
document.body.setAttribute("column-count", next.toString());
valueDisplay.textContent = next.toString();
saveAllSettings();
}
};
const minusBtn = document.createElement("button");
minusBtn.textContent = "−";
minusBtn.style.cssText = `background: none; border: none; color: #71767b; cursor: pointer; font-size: 14px; font-weight: bold; width: 24px; height: 24px; padding: 0; transition: color 0.1s ease;`;
minusBtn.onclick = () => updateCols(-1);
minusBtn.onmouseover = () => minusBtn.style.color = "rgb(26, 77, 216)";
minusBtn.onmouseout = () => minusBtn.style.color = "#71767b";
const plusBtn = document.createElement("button");
plusBtn.textContent = "+";
plusBtn.style.cssText = `background: none; border: none; color: #71767b; cursor: pointer; font-size: 14px; font-weight: bold; width: 24px; height: 24px; padding: 0; transition: color 0.1s ease;`;
plusBtn.onclick = () => updateCols(1);
plusBtn.onmouseover = () => plusBtn.style.color = "rgb(26, 77, 216)";
plusBtn.onmouseout = () => plusBtn.style.color = "#71767b";
scalerGroup.appendChild(minusBtn);
scalerGroup.appendChild(valueDisplay);
scalerGroup.appendChild(plusBtn);
xgridCP.appendChild(scalerGroup);
// DEDUPLICATOR ENGINE CONTROLS
const titleDedup = document.createElement("div");
titleDedup.textContent = "DEDUPLICATE:";
titleDedup.style.cssText = `color: #e7e9ea; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; padding: 0 2px; margin-top: 6px;`;
xgridCP.appendChild(titleDedup);
const dedupRow = document.createElement("div");
dedupRow.style.cssText = `display: flex; gap: 4px; align-items: center; width: 100%; justify-content: space-between;`;
const dedupGroup = document.createElement("div");
dedupGroup.style.cssText = `display: flex; gap: 2px; background: #000; border: 1px solid #3d4144; border-radius: 20px; padding: 2px; flex: 1; justify-content: space-between;`;
if (!document.body.hasAttribute("dedup-mode")) document.body.setAttribute("dedup-mode", "SCAN");
["OFF", "SCAN", "ON"].forEach(mode => {
const btn = document.createElement("button");
btn.className = "dedup-btn";
btn.textContent = mode;
btn.style.cssText = `
background: none; border: none; color: ${deduplicatorMode === mode ? '#fff' : '#71767b'};
cursor: pointer; font-size: 9px; font-weight: bold; padding: 4px 4px; border-radius: 12px;
transition: all 0.1s ease; flex: 1; text-align: center;
background-color: ${deduplicatorMode === mode ? 'rgb(26, 77, 216)' : 'transparent'};
`;
btn.onclick = () => {
deduplicatorMode = mode;
document.body.setAttribute("dedup-mode", mode);
dedupGroup.querySelectorAll('.dedup-btn').forEach(b => {
b.style.color = '#71767b'; b.style.backgroundColor = 'transparent';
});
btn.style.color = '#fff'; btn.style.backgroundColor = 'rgb(26, 77, 216)';
saveAllSettings();
};
dedupGroup.appendChild(btn);
});
const dedupIndicator = document.createElement("span");
dedupIndicator.id = "xgrid-dedup-indicator";
dedupIndicator.textContent = "D";
dedupIndicator.style.cssText = `
font-size: 8px; font-weight: 700; color: #71767b; background: transparent;
border: 1px solid #3d4144; border-radius: 50%; width: 14px; height: 14px;
display: flex; align-items: center; justify-content: center;
text-align: center; transition: all 0.2s ease; box-sizing: border-box; margin-right: 2px;
`;
dedupRow.appendChild(dedupGroup);
dedupRow.appendChild(dedupIndicator);
xgridCP.appendChild(dedupRow);
// --- CROP THUMBS CONTROLS
const titleCrop = document.createElement("div");
titleCrop.textContent = "THUMBNAIL CROP:";
titleCrop.style.cssText = `color: #e7e9ea; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; padding: 0 2px; margin-top: 6px;`;
xgridCP.appendChild(titleCrop);
const cropGroup = document.createElement("div");
cropGroup.style.cssText = `display: flex; gap: 2px; background: #000; border: 1px solid #3d4144; border-radius: 20px; padding: 2px; justify-content: space-between;`;
let cropMode = document.body.getAttribute("data-crop-mode") || "ON";
["ON", "OFF"].forEach(mode => {
const btn = document.createElement("button");
btn.className = "crop-btn";
btn.textContent = mode;
btn.style.cssText = `
background: none; border: none; color: ${cropMode === mode ? '#fff' : '#71767b'};
cursor: pointer; font-size: 9px; font-weight: bold; padding: 4px 6px; border-radius: 12px;
transition: all 0.1s ease; flex: 1;
background-color: ${cropMode === mode ? 'rgb(26, 77, 216)' : 'transparent'};
`;
btn.onclick = () => {
cropMode = mode;
document.body.setAttribute("data-crop-mode", mode);
cropGroup.querySelectorAll('.crop-btn').forEach(b => {
b.style.color = '#71767b'; b.style.backgroundColor = 'transparent';
});
btn.style.color = '#fff'; btn.style.backgroundColor = 'rgb(26, 77, 216)';
saveAllSettings();
};
cropGroup.appendChild(btn);
});
xgridCP.appendChild(cropGroup);
// --- GIF Block Status Text Indicator
const titleGifs = document.createElement("div");
titleGifs.innerHTML = 'GIF BLOCK: ACTIVE';
titleGifs.style.cssText = `color: #e7e9ea; font-size: 10px; padding: 0 2px; margin-top: 4px;`;
xgridCP.appendChild(titleGifs);
// --- GLOBAL SAVE CONFIGURATION SECTION
const titleSave = document.createElement("div");
titleSave.textContent = "SAVE SETTINGS:";
titleSave.style.cssText = `color: #e7e9ea; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; padding: 0 2px; margin-top: 6px;`;
xgridCP.appendChild(titleSave);
const saveGroup = document.createElement("div");
saveGroup.style.cssText = `display: flex; gap: 2px; background: #000; border: 1px solid #3d4144; border-radius: 20px; padding: 2px; justify-content: space-between;`;
let currentSaveMode = document.body.getAttribute('data-save-settings') || "OFF";
["ON", "OFF"].forEach(mode => {
const btn = document.createElement("button");
btn.className = "save-settings-btn";
btn.textContent = mode;
const activeColor = mode === "ON" ? "#198532" : "rgb(26, 77, 216)";
btn.style.cssText = `
background: none; border: none; color: ${currentSaveMode === mode ? '#fff' : '#71767b'};
cursor: pointer; font-size: 9px; font-weight: bold; padding: 4px 6px; border-radius: 12px;
transition: all 0.1s ease; flex: 1;
background-color: ${currentSaveMode === mode ? activeColor : 'transparent'};
`;
btn.onclick = () => {
document.body.setAttribute("data-save-settings", mode);
localStorage.setItem('xgrid-save-settings', mode);
saveGroup.querySelectorAll('.save-settings-btn').forEach(b => {
b.style.color = '#71767b'; b.style.backgroundColor = 'transparent';
});
btn.style.color = '#fff'; btn.style.backgroundColor = activeColor;
saveAllSettings();
};
saveGroup.appendChild(btn);
});
xgridCP.appendChild(saveGroup);
document.body.appendChild(xgridCP);
}
GM_addStyle(`
/* Uniform Filter Tab Formatting rules */
#media-filter-controls #all, #media-filter-controls #images, #media-filter-controls #videos {
flex: 1 !important; padding: 6px 2px !important; border: 1px solid #3d4144 !important;
border-radius: 20px !important; background: #000000 !important; color: #71767b !important;
cursor: pointer !important; font-size: 10px !important; font-weight: 600 !important;
transition: all 0.15s ease !important; text-align: center !important; box-sizing: border-box !important;
}
#media-filter-controls #all:hover, #media-filter-controls #images:hover, #media-filter-controls #videos:hover {
background: #15181c !important;
}
/* Highlight Rules for Selected Active State */
body:not([filter-by]) #media-filter-controls #all,
[filter-by="images"] #media-filter-controls #images,
[filter-by="videos"] #media-filter-controls #videos {
background: rgb(26, 77, 216) !important; scale: 1.02 !important; color: white !important; border-color: transparent !important;
}
#media-filter-toggle-btn:hover { background: #111 !important; }
#media-filter-controls:hover #media-filter-closed-label { color: #e7e9ea !important; }
/* Control Panel Tab Visibility Logic Rules */
#media-filter-controls { display: none !important; }
body:has([data-testid="primaryColumn"] [role="navigation"] [href$="/media"][aria-selected="true"]):not(:has([role="dialog"]), :has([data-testid="layerViews"])) #media-filter-controls {
display: flex !important;
}
/* Rebuild grid after filtering */
[filter-by="videos"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="region"] [role="listitem"]:has(a[href*="/photo/"]) {
display: none !important;
}
[filter-by="images"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="region"] [role="listitem"]:has(a[href*="/video/"]) {
display: none !important;
}
/* Dynamic GIF Blocker Rule */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"]:has(div[style*="tweet_video_thumb"]),
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"]:has(img[src*="tweet_video_thumb"]) {
display: none !important;
}
/* Grid Framework Rebuild Engine */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) section[role="region"]:has(> h1[role="heading"] + div[aria-label^="Timeline:"] > div > div[data-testid="cellInnerDiv"]) > div {
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
gap: 4px !important;
margin: 4px !important;
}
/* Virtualization Row Eraser */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) section[role="region"]:has(> h1[role="heading"] + div[aria-label^="Timeline:"] > div > div[data-testid="cellInnerDiv"]) > div *:not([role="listitem"], [role="listitem"] *) {
display: contents !important;
}
/* Item Core Formatting rules */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"] {
box-sizing: border-box !important;
will-change: width !important;
position: relative !important;
}
/* Secure Visual Image Setup Engine */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"] a {
display: block !important;
width: 100% !important;
aspect-ratio: 1 / 1 !important;
}
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"] img {
aspect-ratio: 1 / 1 !important;
object-fit: cover !important;
width: 100% !important;
height: 100% !important;
opacity: 1 !important;
visibility: visible !important;
}
/* Grid Column Assignment Width Rules */
body:not([column-count]) [data-testid="primaryColumn"] [role="listitem"] { width: calc(33.333% - 2.66px) !important; }
body[column-count="1"] [data-testid="primaryColumn"] [role="listitem"] { width: 100% !important; }
body[column-count="2"] [data-testid="primaryColumn"] [role="listitem"] { width: calc(50% - 2px) !important; }
body[column-count="3"] [data-testid="primaryColumn"] [role="listitem"] { width: calc(33.333% - 2.66px) !important; }
body[column-count="4"] [data-testid="primaryColumn"] [role="listitem"] { width: calc(25% - 3px) !important; }
body[column-count="5"] [data-testid="primaryColumn"] [role="listitem"] { width: calc(20% - 3.2px) !important; }
/* --- UNCROP THUMBS CSS --- */
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/photo/"] *,
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/video/"] * {
object-fit: contain !important;
background-size: contain !important;
background-repeat: no-repeat !important;
background-position: center !important;
}
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/photo/"] img,
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/video/"] img {
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
max-height: 100% !important;
}
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/photo/"],
body[data-crop-mode="OFF"] [data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/video/"] {
background-color: rgba(0, 0, 0, 0) !important;
}
/* DEDUPLICATOR VISUAL OVERLAY MARKER */
body[dedup-mode="SCAN"] [data-is-duplicate="true"]::after {
content: "D" !important; position: absolute !important; bottom: 6px !important; left: 55% !important;
transform: translateX(-50%) !important; background: rgba(0, 0, 0, 0.75) !important; color: #ff3b30 !important;
font-size: 10px !important; font-weight: 700 !important; padding: 1px 4px !important; border-radius: 10px !important;
z-index: 10 !important; pointer-events: none !important; font-family: sans-serif !important;
}
body[dedup-mode="ON"] [data-is-duplicate="true"] { display: none !important; }
/* VIDEO DURATION AND MULTI-MEDIA ICON COLOR OVERRIDES */
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) a[href*="/video/"] span {
color: rgb(60, 162, 240) !important;
}
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"] svg:has(path[d^="M2 8.5"]) {
fill: rgb(60, 162, 240) !important;
color: rgb(60, 162, 240) !important;
}
[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"] svg:has(path[d^="M2 8.5"]) {
fill: rgb(60, 162, 240) !important;
color: rgb(60, 162, 240) !important;
background-color: rgba(15, 15, 20, 0.75) !important;
padding: 1px !important;
border-radius: 35% !important;
display: inline-block !important;
}
`);
// --- DEDUPLICATION LOGIC (DHASH/BK-TREE) ---
function processDeduplication() {
if (deduplicatorMode === "OFF") {
const indicatorEl = document.getElementById('xgrid-dedup-indicator');
if (indicatorEl) { indicatorEl.style.color = "#71767b"; indicatorEl.style.borderColor = "#3d4144"; }
return;
}
const cells = document.querySelectorAll('[data-testid="primaryColumn"]:has([role="navigation"] [href$="/media"][aria-selected="true"]) [role="listitem"]');
if (!cells || cells.length === 0) return;
cells.forEach(cell => {
if (!cell.hasAttribute('data-media-type')) {
const anchor = cell.querySelector('a[href*="/status/"]');
if (anchor) {
const match = anchor.getAttribute('href').match(/\/(photo|video)\//);
if (match) cell.setAttribute('data-media-type', match[1]);
}
}
const imgEl = cell.querySelector('img');
if (!imgEl || !imgEl.src) return;
const imgSrc = imgEl.src;
if (knownDuplicates.has(imgSrc)) {
if (!cell.hasAttribute('data-dedup-id')) cell.setAttribute('data-dedup-id', 'cached_' + Math.random().toString(36).substr(2, 9));
cell.setAttribute('data-is-duplicate', 'true');
return;
}
if (knownUniques.has(imgSrc)) {
if (!cell.hasAttribute('data-dedup-id')) cell.setAttribute('data-dedup-id', 'cached_' + Math.random().toString(36).substr(2, 9));
return;
}
if (cell.hasAttribute('data-dedup-id')) return;
const cellId = 'cell_' + Math.random().toString(36).substr(2, 9);
cell.setAttribute('data-dedup-id', cellId);
cellRegistry.set(cellId, cell);
computeDHash(imgSrc).then(hash => {
if (hash === null) return;
const matches = imageTree.search(hash, HAMMING_THRESHOLD);
if (matches.length > 0) {
let dupCellId = cellId;
let keepCellId = matches[0].node.cellIds[0];
const keepCell = cellRegistry.get(keepCellId);
const currCell = cell;
if (keepCell && currCell) {
const keepHasMulti = keepCell.querySelector('svg:has(path[d^=\"M2 8.5\"])') !== null;
const currHasMulti = currCell.querySelector('svg:has(path[d^=\"M2 8.5\"])') !== null;
// Prefer to keep the post with multiple media (deduplicate/remove the one without)
if (currHasMulti && !keepHasMulti) {
dupCellId = keepCellId;
keepCellId = cellId;
matches[0].node.cellIds[0] = cellId;
currCell.removeAttribute('data-is-duplicate');
}
}
const dupNode = cellRegistry.get(dupCellId);
if (dupNode) {
dupNode.setAttribute('data-is-duplicate', 'true');
const dupImg = dupNode.querySelector('img');
if (dupImg && dupImg.src) knownDuplicates.add(dupImg.src);
}
} else {
imageTree.add(hash, cellId);
knownUniques.add(imgSrc);
}
});
});
// Update real-time UI duplicates panel indicator badge
setTimeout(() => {
const hasDuplicates = document.querySelector('[data-is-duplicate="true"]') !== null;
const indicatorEl = document.getElementById('xgrid-dedup-indicator');
if (indicatorEl) {
if (hasDuplicates) {
indicatorEl.style.color = "#ff6b6b";
indicatorEl.style.borderColor = "#ff6b6b";
} else {
indicatorEl.style.color = "#71767b";
indicatorEl.style.borderColor = "#3d4144";
}
}
}, 250);
}
// --- INITIALIZATION CORE ---
loadAllSettings();
updatePageContext();
createFilterButtons();
const spaObserver = new MutationObserver(() => {
updatePageContext();
createFilterButtons();
if (document.body.hasAttribute('data-xgrid-active')) {
processDeduplication();
}
});
spaObserver.observe(document.documentElement, { childList: true, subtree: true });
// Saves & restore scroll position
let savedGridScrollY = null;
document.addEventListener('click', (e) => {
const link = e.target.closest('[role="listitem"] a[href*="/status/"]');
if (link && document.body.hasAttribute('data-xgrid-active')) {
savedGridScrollY = window.scrollY;
}
}, true);
function restoreGridScroll(targetY, durationMs = 2000) {
const start = performance.now();
const reassert = () => {
if (Math.abs(window.scrollY - targetY) > 1) {
window.scrollTo(0, targetY);
}
if (performance.now() - start < durationMs) {
setTimeout(reassert, 40);
}
};
reassert();
}
let dialogWasOpen = false;
const scrollRestoreObserver = new MutationObserver(() => {
const dialogOpen = !!(document.querySelector('[role="dialog"]') || document.querySelector('[data-testid="layerViews"]'));
if (dialogWasOpen && !dialogOpen && savedGridScrollY !== null) {
restoreGridScroll(savedGridScrollY);
savedGridScrollY = null;
}
dialogWasOpen = dialogOpen;
});
scrollRestoreObserver.observe(document.documentElement, { childList: true, subtree: true });
window.addEventListener('popstate', () => {
const isMediaTab = /\/[^/]+\/media\/?$/.test(window.location.pathname);
if (isMediaTab && savedGridScrollY !== null) {
restoreGridScroll(savedGridScrollY);
savedGridScrollY = null;
}
});
})();