Files
reddit-tweaks-ffe/docs/superpowers/specs/2026-08-27-vote-ratio-badge-design.md
cottongin c0a0c157f5 chore: add LICENSE, README, VERSION, .editorconfig, expand .gitignore
Add project housekeeping files and sync manifest version to 0.1.0.
Also stages previously untracked chat summaries and score-ratio docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 00:27:54 -04:00

7.2 KiB
Raw Blame History

Score-per-Comment Ratio Badge

A temperature-coded badge on each post card showing the ratio of score to comments. High ratios (lots of score, few comments) appear hot/red; low ratios appear cold/blue.

Data Extraction

Score Parsing

Old Reddit renders three .score spans inside .midcol: .score.unvoted, .score.likes, .score.dislikes. Only one is visible at a time. When hide_score is true (per-subreddit config or new posts), all three show "•".

Parsing strategy: target .score.unvoted specifically (the neutral/true score). Read its title attribute first (raw integer), fall back to text content. The text fallback must handle abbreviated suffixes (k, m).

DOM State .score.unvoted text title attr Parse result
Normal "23809" or "23809 points" "23809" 23809
Abbreviated "23.8k" or "1.2m" "23800" (if present) 23800 / 1200000
Hidden "•" or "• points" "•" or absent null
Zero net votes "0" "0" 0
Negative "-5" "-5" -5
No .score element (promoted/ad) n/a n/a null

Text fallback parser: if parseInt fails, try matching /^(-?[\d.]+)\s*([km])/i and multiply by 1000 or 1000000 respectively. If that also fails, return null.

Comment Count Parsing

Parse integer from .flat-list .comments text content. The ratio module does its own parsing (independent of badges.js).

DOM text Parse result
"830 comments" 830
"1 comment" 1
"comment" (old Reddit's 0 form) 0
No .comments element null

Ratio Calculation

Formula: score / comments, displayed to 1 decimal place.

Score Comments Ratio Badge text Color
null (hidden) any — "—" Neutral gray
any null (missing) — "—" Neutral gray
any 0 Division by zero "—" Neutral gray
0 > 0 0.0 "0.0 🔥" Cold blue
< 0 > 0 negative e.g. "-1.7 🔥" Cold blue (clamp at 0)
420 33 12.7 "12.7 🔥" Mapped via HSL
100000 5 20000.0 "20000.0 🔥" Max red

The 🔥 above represents the Font Awesome fa-fire glyph (solid), not the Unicode emoji. The icon inherits the temperature color.

Temperature Color Scale

HSL hue rotation maps the ratio to a color on the blue → cyan → green → yellow → red spectrum.

  • Input: ratio clamped to [0, 100] for color mapping
  • Hue formula: hue = 240 - (clampedRatio / 100) * 240
  • Saturation: 80%
  • Lightness: controlled via CSS, not inline — 45% in light mode, 55% when .rt-dark is active

JS sets two CSS custom properties on each badge element:

  • --rt-ratio-hue: the computed hue (0–240)
  • --rt-ratio-sat: 80% (fixed, but exposed as a variable for future tuning)

CSS uses these to derive all colors:

  • Text/icon color: hsl(var(--rt-ratio-hue), var(--rt-ratio-sat), 45%) (light), 55% (dark)
  • Background: same HSL at 10% alpha
  • Border: same HSL at 25% alpha

This means dark mode toggling requires no DOM walk — the .rt-dark CSS selector changes the lightness value automatically.

Ratio Hue Color
0 240° Blue (cold)
12.5 210° Cyan-blue
25 180° Cyan
50 120° Green
75 60° Yellow
100+ 0° Red (hot)

Neutral state ("—" badges): #737373 light, #a3a3a3 dark. These use a separate CSS class (.rt-badge--ratio-neutral) rather than the custom property approach.

Module Architecture

New file: content/tweaks/ratio.js

Exposes window.RedditTweaks.ratioTweak with the standard tweak interface:

  • apply(settings) — process all existing posts, add ratio badges
  • processNewPosts(settings) — called by mutation observer for infinite scroll
  • updateSettings(settings) — no-op for now (dark mode handled via CSS), kept for interface consistency

Processing flow per post

  1. Guard: skip if data-rt-ratio-processed attribute present
  2. Parse score from .midcol .score.unvoted (title attr → text fallback with k/m parsing)
  3. Parse comment count from .flat-list .comments text
  4. Calculate ratio (or null for edge cases)
  5. Compute hue from ratio (0–240)
  6. Create badge: <span class="rt-badge rt-badge--ratio" style="--rt-ratio-hue:120; --rt-ratio-sat:80%">12.7 <i class="fa-solid fa-fire"></i></span>
  7. Insert into .reddit-tweaks-badges container if present, otherwise after .tagline inside .entry

New file: content/styles/ratio.css

Depends on .rt-badge base styles from badges.css (always loaded via manifest). Adds:

  • .rt-badge--ratio styling: color, background, and border derived from --rt-ratio-hue and --rt-ratio-sat custom properties
  • .rt-dark .rt-badge--ratio overrides: lightness changes from 45% to 55%
  • .rt-badge--ratio-neutral for "—" badges: static gray colors, no custom properties

Font Awesome integration

  • Bundle FA Free solid subset only (~150KB woff2 + minimal CSS) in content/fonts/fontawesome/
  • Scope all FA CSS selectors under .rt-badge--ratio to prevent global style pollution (e.g., .rt-badge--ratio .fa-solid instead of .fa-solid)
  • Add scoped FA CSS to manifest.json content_scripts CSS array

Integration into main.js

Hard requirement: call order. In both init() and the post observer, badges must process before ratio. This prevents the ratio badge from being orphaned in .entry while .reddit-tweaks-badges is created later.

  • Check settings.scoreRatio toggle
  • Call ratioTweak.apply(settings) during init — after badgeTweak.apply()
  • Call ratioTweak.processNewPosts(settings) in post observer — after badgeTweak.processNewPosts()
  • Call ratioTweak.updateSettings(settings) on settings changes (currently a no-op)
  • Add scoreRatio to storage change listener for reload triggers

Dependency: the score ratio feature requires badgeLayout or cardLayout to be enabled. The mutation observer only starts when one of those is active. If both are off, ratio badges only appear on the initial page load (no infinite scroll processing). This is an accepted trade-off; the toggle lives under "Card Layout" in the popup to reflect this coupling.

Settings

  • New default: scoreRatio: true in lib/settings.js DEFAULTS
  • Color scale max hardcoded at 100 (tunable later)

Popup UI

New toggle in the Card Layout section of popup.html, after "Enable card layout":

<label class="toggle-row">
  <span>Score ratio badge</span>
  <input type="checkbox" id="scoreRatio">
</label>

Wired into populateControls and saveFromControls in popup.js. No additional configuration UI for the scale max (hardcoded at 100 for now).

Files Changed

File Change
content/tweaks/ratio.js New — ratio calculation, badge injection, color math
content/styles/ratio.css New — badge styling, dark mode, neutral state
content/fonts/fontawesome/ New — FA solid subset (woff2 + scoped CSS)
lib/settings.js Add scoreRatio: true to DEFAULTS
content/main.js Wire up ratioTweak lifecycle (after badges, always)
popup/popup.html Add score ratio toggle
popup/popup.js Wire toggle to save/load
manifest.json Add ratio.js, ratio.css, scoped FA CSS to content_scripts