Raw Source
XxUnkn0wnxX / Reddit Fluid Width

// ==UserScript==
// @name         Reddit Fluid Width
// @namespace    https://github.com/XxUnkn0wnxX/Scripts
// @version      1.0.0
// @description  Widens Reddit post/comment pages only while preserving native feed and landing layouts. Vibe coded with OpenAI.
// @homepageURL  https://github.com/XxUnkn0wnxX/Scripts
// @supportURL   https://discord.gg/slayersicerealm
// @author       XxUnkn0wnxX
// @license      AGPL-3.0-or-later
// @updateURL    https://raw.githubusercontent.com/XxUnkn0wnxX/Scripts/master/userscripts/reddit-fluid-width.user.js
// @downloadURL  https://raw.githubusercontent.com/XxUnkn0wnxX/Scripts/master/userscripts/reddit-fluid-width.user.js
// @match        https://www.reddit.com/r/*
// @run-at       document-start
// @grant        none
// @noframes
// ==/UserScript==

(() => {
  'use strict';

  const STYLE_ID = 'reddit-fluid-width-style';
  const ROOT_ATTR = 'data-reddit-fluid-width';
  const HISTORY_PATCH_FLAG = '__redditFluidWidthHistoryPatched';
  const ROUTE_RE = /^\/r\/[^/]+\/comments\/[^/]+(?:\/|$)/i;

  /*** CONFIG ***/
  // Set 85-95 here for narrower/wider post containers.
  // true (default) pins the right edge at 100%/0px; false centers the whole grid.
  const CONFIG = Object.freeze({
    contentWidthPercent: 95,
    minGutterPx: 32,
    pinRightSidebar: true,
  });

  const config = normalizeConfig(CONFIG);
  const style = ensureStyle();
  const html = document.documentElement;
  style.textContent = buildStyles();

  syncRouteState();
  installRouteSyncHooks();

  function normalizeConfig(source) {
    const percentRaw = Number(source?.contentWidthPercent);
    const gutterRaw = Number(source?.minGutterPx);
    const pinRightSidebar = source?.pinRightSidebar;

    const contentWidthPercent = Number.isFinite(percentRaw)
      ? Math.max(1, Math.min(100, percentRaw))
      : 95;

    const minGutterPx = Number.isFinite(gutterRaw)
      ? Math.max(16, Math.min(128, Math.round(gutterRaw)))
      : 32;

    return Object.freeze({
      contentWidthPercent,
      minGutterPx,
      pinRightSidebar: typeof pinRightSidebar === 'boolean' ? pinRightSidebar : true,
    });
  }

  function syncRouteState() {
    const isPostRoute = isRedditPostCommentsRoute(location.pathname);
    if (isPostRoute) {
      html.setAttribute(ROOT_ATTR, '');
      return;
    }

    html.removeAttribute(ROOT_ATTR);
  }

  function isRedditPostCommentsRoute(pathname) {
    return ROUTE_RE.test(String(pathname || '').trim());
  }

  function buildStyles() {
    const isPinned = config.pinRightSidebar;
    const horizontalModeStyles = isPinned
      ? `
    margin-inline-start: auto !important;
    margin-inline-end: 0px !important;
    `
      : `
    margin-inline: auto !important;
    `;
    const gutteredWidth = isPinned
      ? `calc(100% - var(--reddit-fluid-gutter))`
      : `calc(100% - var(--reddit-fluid-gutter) - var(--reddit-fluid-gutter))`;

    return `
@media (min-width: 1472px) {
  html[${ROOT_ATTR}] #subgrid-container {
    --reddit-fluid-width: ${config.contentWidthPercent}%;
    --reddit-fluid-gutter: ${config.minGutterPx}px;
    width: max(
      1120px,
      min(var(--reddit-fluid-width), ${gutteredWidth})
    ) !important;
    box-sizing: border-box;
${horizontalModeStyles}
  }

  html[${ROOT_ATTR}] #subgrid-container > .main-container.fixed-sidebar {
    grid-template-columns: minmax(0, 1fr) minmax(0, 316px) !important;
  }
}
`;
  }

  function ensureStyle() {
    let styleNode = document.getElementById(STYLE_ID);
    if (!styleNode) {
      styleNode = document.createElement('style');
      styleNode.id = STYLE_ID;
      (document.head || document.documentElement).appendChild(styleNode);
    }
    return styleNode;
  }

  function installRouteSyncHooks() {
    if (!history[HISTORY_PATCH_FLAG]) {
      const originalPushState = history.pushState;
      const originalReplaceState = history.replaceState;

      history.pushState = function (...args) {
        const result = originalPushState.apply(this, args);
        syncRouteState();
        return result;
      };

      history.replaceState = function (...args) {
        const result = originalReplaceState.apply(this, args);
        syncRouteState();
        return result;
      };

      history[HISTORY_PATCH_FLAG] = true;
    }

    window.addEventListener('popstate', syncRouteState, true);
    window.addEventListener('pageshow', syncRouteState, true);
  }
})();