QQ/SB/SV Mobile readability

Standardize font size to 15px and revert justified text; avoid text that is too small or too big. Replace fonts with sans-serif/serif/monospace at calibrated sizes (avoid unsupported/mismatched desktop fonts). Dodge colored text from the background and lightly pastelize to improve contrast.

当前为 2025-05-23 提交的版本,查看 最新版本

// ==UserScript==
// @name        QQ/SB/SV Mobile readability
// @description Standardize font size to 15px and revert justified text; avoid text that is too small or too big. Replace fonts with sans-serif/serif/monospace at calibrated sizes (avoid unsupported/mismatched desktop fonts). Dodge colored text from the background and lightly pastelize to improve contrast. 
// @author      C89sd
// @version     1.7
// @match       https://questionablequesting.com/*
// @match       https://forum.questionablequesting.com/*
// @match       https://forums.spacebattles.com/*
// @match       https://forums.sufficientvelocity.com/*
// @grant       GM_addStyle
// @namespace   https://greasyfork.org/users/1376767
// @noframes
// ==/UserScript==

'use strict';

const IS_THREAD = document.URL.includes('/threads/');
if (!IS_THREAD) return;

const colorCache = new Map();
const adjustTextColor = (textRGBStr) => {
    if (colorCache.has(textRGBStr)) {
        return colorCache.get(textRGBStr);
    }

    function toRGB(str) { // can return up to 3 values [] to [r,g,b]
        const nums = str.match(/\d+/g);
        return nums ? nums.map(Number).slice(0, 3) : [];
    }

    let fg = toRGB(textRGBStr);
    const bg = toRGB("rgb(39, 39, 39)");

    if (fg.length !== 3) { return textRGBStr }

    const clamp = v => Math.max(0, Math.min(255, v));
    const pastelize = ([r, g, b]) => {
        const neutral = 128;
        const whiteBoost = Math.pow((r + g + b) / (3 * 255), 10);
        const factor = 0.8 + 0.1*whiteBoost;
        return [
            neutral + factor * (r - neutral),
            neutral + factor * (g - neutral),
            neutral + factor * (b - neutral)
        ].map(clamp);
    };

    fg = pastelize(fg);

    let positiveDiff = 0;
    positiveDiff += Math.max(0, fg[0] - 39);
    positiveDiff += Math.max(0, fg[1] - 39);
    positiveDiff += Math.max(0, fg[2] - 39);

    const threshold = 180;
    if (positiveDiff < threshold) {
      fg[0] = Math.max(fg[0], 39);
      fg[1] = Math.max(fg[1], 39);
      fg[2] = Math.max(fg[2], 39);

      const avg = (fg[0]+fg[1]+fg[2])/3;
      const boost = Math.min(1.0, (Math.abs(fg[0]-avg)+Math.abs(fg[1]-avg)+Math.abs(fg[2]-avg))/255/0.2); // grays = 0, colors >= 1

      let correction = Math.round((threshold - positiveDiff) / 3 + boost*40);
      fg = fg.map(c => Math.min(c + correction, 255));
    }

    const result = `rgb(${fg[0]}, ${fg[1]}, ${fg[2]})`;
    colorCache.set(textRGBStr, result);

    return result;
};


const SANS_SERIF = /arial|tahoma|trebuchet ms|verdana/;
const MONOSPACE = /courier new/;
const SERIF = /times new roman|georgia|book antiqua/;

const wrappers = document.querySelectorAll('div.bbWrapper');
for (let wrapper of wrappers) {
  wrapper.style.fontSize = '15px';

  const children = wrapper.querySelectorAll('*');
  for (let child of children) {
    const style = child.style;

    if (style.fontSize) {
      style.fontSize = '';
    }

    if (style.fontFamily) {
      const font = style.fontFamily;
      if (SANS_SERIF.test(font)) {
        style.fontFamily = 'sans-serif';
      } else if (MONOSPACE.test(font)) {
        style.fontFamily = 'monospace';
        style.fontSize = '13.5px';
      } else if (SERIF.test(font)) {
        style.fontFamily = 'serif';
      }
    }

    if (style.textAlign) {
      if (style.textAlign !== 'center' && style.textAlign !== 'right') {
        style.textAlign = '';
      }
    }

    if (style.color && style.color.startsWith('rgb')) {
      style.color = adjustTextColor(style.color);
    }
  }
}

// Smaller tables
GM_addStyle(`
.bbTable {
  font-size: 13px;
  overflow: visible;
  table-layout: auto;
}
.bbTable td {
  vertical-align: top;
  white-space: normal;
  word-wrap: normal;
  word-break: break-word;
}
`);

// SB Weekly stats thread
if (document.URL.includes('.1140820/')) {
  // Make tables with 2/3 cols fixed 50% 33.33%
  [...document.querySelectorAll('.bbTable table')]
    .filter(t=>[2,3].includes(t.rows[0]?.cells.length))
    .forEach(t=>{
      t.style.cssText += 'table-layout:fixed;width:100%';
      let w = (100/t.rows[0].cells.length).toFixed(2) + '%';
      for(let c of t.rows[0].cells) c.width = w;
    });
  // Standardize padding around the "New Top 10" bubble
  GM_addStyle(`
    .bbTable td b:has( code.bbCodeInline) {
      display: inline-block !important;
      padding: 2px 0 !important;
    }
  `);
}