Skip to content

Instantly share code, notes, and snippets.

@glektarssza
Last active April 24, 2025 21:54
Show Gist options
  • Save glektarssza/4ad84d315260adae6649d46ebbcd7852 to your computer and use it in GitHub Desktop.
Save glektarssza/4ad84d315260adae6649d46ebbcd7852 to your computer and use it in GitHub Desktop.
YouTube Anti-20th Birthday Changes Greasemonkey Script
// ==UserScript==
// @name YouTube Anti-20th Birthday Limited Item Per Row Script
// @version 0.0.3
// @description Increase the number of videos per row on the YouTube home page in opposition to their 20th birthday changes.
// @author G'lek Tarssza
// @copyright Copyright (c) 2025 G'lek Tarssza
// @namespace https://glektarssza.com/
// @source https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852
// @updateURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852/raw/anti-youtube-birthday-gm.js
// @downloadURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852/raw/anti-youtube-birthday-gm.js
// @supportURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=16&domain=youtube.com
// @icon64 https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant unsafeWindow
// @noframes
// ==/UserScript==
/********************
* SCRIPT CONSTANTS *
********************/
/**
* A list of DOM selectors to operate on and the operation(s) to perform on them.
*/
const HTML_NODE_ADJUSTMENTS = {
'ytd-rich-grid-renderer': (elem, itemCount) => {
elem.style.setProperty('--ytd-rich-grid-items-per-row', itemCount);
elem.style.setProperty('--ytd-rich-grid-posts-per-row', itemCount);
}
};
/**
* The time to wait before handling a update events.
*/
const DOM_UPDATE_EVENT_THROTTLE = 2000;
/**
* The time to wait before handling a resize events.
*/
const RESIZE_EVENT_THROTTLE = 1000;
/**
* Mapping of screen widths to number of items per row.
*
* Mappings are `[Minimum Width, Maximum Width]`.
*/
const SIZE_MAPPINGS = new Map([
[[992, Infinity], '4'],
[[480, 768], '3'],
[[0, 480], '2']
]);
/**
* A simple routine that returns a wrapper function that can be used to rate
* limit how often the passed in function is called.
*
* @param {Function} func The function to rate limit the calling of.
* @param {number} timeout The duration to wait before calling the provided
* function. Additional calls during this timeout will reset the timeout.
*
* @returns {Function} A function that can be used to initiate the timeout.
* Accepts the same inputs as the provided function.
*/
function debounce(func, timeout) {
const inputBuffer = [];
let timer = null;
return (...inputs) => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
inputBuffer[inputBuffer.length] = inputs;
timer = setTimeout(() => {
while (inputBuffer.length > 0) {
func.apply(this, inputBuffer.shift());
}
}, timeout);
};
}
/**
* Update the items per row property of the YouTube DOM.
*
* @param {HTMLElement} htmlNode The HTML element to update.
* @param {Function} action The action to apply to the node.
*/
function updateHtmlNode(htmlNode, action) {
const sizeEntries = Array.from(SIZE_MAPPINGS.entries());
sizeEntries.sort((a, b) => b[0][0] - a[0][0]);
for (const [[minSize, maxSize], count] of sizeEntries) {
if (
unsafeWindow.innerWidth >= minSize &&
unsafeWindow.innerWidth < maxSize
) {
action(htmlNode, count);
break;
}
}
}
(function () {
'use strict';
//-- DOM update handling
const updateHandler = debounce(
(mutations) =>
Object.keys(HTML_NODE_ADJUSTMENTS).forEach((selector) =>
Array.from(document.querySelectorAll(selector))
.filter((htmlNode) =>
mutations.some((mutation) =>
mutation.target.contains(htmlNode)
)
)
.forEach((htmlNode) =>
updateHtmlNode(
htmlNode,
HTML_NODE_ADJUSTMENTS[selector]
)
)
),
DOM_UPDATE_EVENT_THROTTLE
);
const observer = new MutationObserver(updateHandler);
observer.observe(document.body, {childList: true, subtree: true});
//-- Window resize handling
const resizeHandler = debounce(
() =>
Object.keys(HTML_NODE_ADJUSTMENTS).forEach((selector) =>
document
.querySelectorAll(selector)
.forEach((htmlNode) =>
updateHtmlNode(
htmlNode,
HTML_NODE_ADJUSTMENTS[selector]
)
)
),
RESIZE_EVENT_THROTTLE
);
window.addEventListener('resize', resizeHandler);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment