Files
reddit-tweaks-ffe/docs/superpowers/specs/2026-08-27-vote-ratio-badge-design.md

158 lines
7.2 KiB
Markdown
Raw Permalink Normal View 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":
```html
<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 |