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>
This commit is contained in:
463
docs/superpowers/plans/2026-08-27-score-ratio-badge.md
Normal file
463
docs/superpowers/plans/2026-08-27-score-ratio-badge.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Score Ratio Badge Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a temperature-coded badge to each post showing the score-per-comment ratio, using Font Awesome's fire icon and an HSL color scale from blue (cold) to red (hot).
|
||||
|
||||
**Architecture:** New `ratio.js` IIFE module on `window.RedditTweaks.ratioTweak` with the standard tweak interface. Score and comment count are parsed from the DOM per post. HSL hue is computed from the ratio and set as CSS custom properties on each badge element. CSS handles light/dark mode lightness via selectors. Font Awesome solid subset is bundled with scoped CSS to prevent global pollution.
|
||||
|
||||
**Tech Stack:** Vanilla JS (IIFE modules on `window.RedditTweaks`), plain CSS with custom properties, Font Awesome 6 Free (solid subset), Firefox WebExtension manifest v2.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- old.reddit.com only
|
||||
- No build tools, no frameworks, no npm runtime dependencies
|
||||
- Modules use IIFE pattern and expose API on `window.RedditTweaks`
|
||||
- Imports at top of file, no inline imports
|
||||
- Score ratio requires `badgeLayout` or `cardLayout` to be enabled (accepted dependency)
|
||||
- In `main.js`, badges must always process before ratio (hard call-order requirement)
|
||||
- FA CSS must be scoped under `.rt-badge--ratio` — no global FA selectors
|
||||
- Design spec: `docs/superpowers/specs/2026-08-27-vote-ratio-badge-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Settings infrastructure + popup toggle
|
||||
|
||||
Add the `scoreRatio` setting and wire a toggle in the popup's Card Layout section.
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/settings.js`
|
||||
- Modify: `popup/popup.html`
|
||||
- Modify: `popup/popup.js`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `settings.scoreRatio` (boolean, default `true`)
|
||||
|
||||
- [ ] **Step 1: Add `scoreRatio` to DEFAULTS in `lib/settings.js`**
|
||||
|
||||
In `lib/settings.js`, add `scoreRatio: true` to the `DEFAULTS` object, after `cardGap: 16`:
|
||||
|
||||
```js
|
||||
cardGap: 16,
|
||||
scoreRatio: true,
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add toggle to `popup/popup.html`**
|
||||
|
||||
In `popup/popup.html`, inside the Card Layout section, add the score ratio toggle after the "Enable card layout" toggle (after line 40: `</label>`):
|
||||
|
||||
```html
|
||||
<label class="toggle-row">
|
||||
<span>Score ratio badge</span>
|
||||
<input type="checkbox" id="scoreRatio">
|
||||
</label>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Wire toggle in `popup/popup.js`**
|
||||
|
||||
In `popup/popup.js`, add to `populateControls(settings)`, after the `cardLayout` checkbox line:
|
||||
|
||||
```js
|
||||
document.getElementById("scoreRatio").checked = settings.scoreRatio;
|
||||
```
|
||||
|
||||
In `saveFromControls()`, add to the settings object, after the `cardLayout` line:
|
||||
|
||||
```js
|
||||
scoreRatio: document.getElementById("scoreRatio").checked,
|
||||
```
|
||||
|
||||
In `init()`, add a change listener, after the `cardLayout` listener:
|
||||
|
||||
```js
|
||||
document.getElementById("scoreRatio").addEventListener("change", saveFromControls);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify toggle works**
|
||||
|
||||
Load the extension in Firefox (`about:debugging` → Load Temporary Add-on). Open popup:
|
||||
- "Score ratio badge" checkbox appears in the Card Layout section, checked by default
|
||||
- Unchecking and reopening popup: stays unchecked (persisted)
|
||||
- Re-checking: persists
|
||||
- No console errors
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/settings.js popup/popup.html popup/popup.js
|
||||
git commit -m "feat: add scoreRatio setting and popup toggle"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Font Awesome solid subset + ratio badge CSS
|
||||
|
||||
Download the FA solid woff2 font, create scoped FA CSS and the ratio badge stylesheet, and register both in the manifest.
|
||||
|
||||
**Files:**
|
||||
- Create: `content/fonts/fontawesome/fa-solid-900.woff2` (downloaded)
|
||||
- Create: `content/styles/fontawesome-scoped.css`
|
||||
- Create: `content/styles/ratio.css`
|
||||
- Modify: `manifest.json`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `@font-face` for "Font Awesome 6 Free" (solid, weight 900)
|
||||
- Produces: `.rt-badge--ratio .fa-solid` and `.rt-badge--ratio .fa-fire::before` selectors
|
||||
- Produces: `.rt-badge--ratio` and `.rt-badge--ratio-neutral` CSS classes consuming `--rt-ratio-hue` and `--rt-ratio-sat` custom properties
|
||||
|
||||
- [ ] **Step 1: Download FA 6.7.2 solid woff2 (pinned version)**
|
||||
|
||||
```bash
|
||||
mkdir -p content/fonts/fontawesome
|
||||
curl -f -L -o content/fonts/fontawesome/fa-solid-900.woff2 \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/webfonts/fa-solid-900.woff2"
|
||||
```
|
||||
|
||||
Verify integrity — must be 158KB with sha256 `aa75998623a391e61c6901794ace832e3ecdd288b56d608f21bea0411acc0b8e`:
|
||||
|
||||
```bash
|
||||
ls -lh content/fonts/fontawesome/fa-solid-900.woff2
|
||||
shasum -a 256 content/fonts/fontawesome/fa-solid-900.woff2
|
||||
```
|
||||
|
||||
If the hash doesn't match or the download fails (curl `-f` flag fails on HTTP errors), stop and investigate — the CDN URL may have changed.
|
||||
|
||||
- [ ] **Step 2: Create `content/styles/fontawesome-scoped.css`**
|
||||
|
||||
This defines the @font-face and scopes all FA selectors under `.rt-badge--ratio` to prevent global CSS pollution:
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("../fonts/fontawesome/fa-solid-900.woff2") format("woff2");
|
||||
}
|
||||
|
||||
.rt-badge--ratio .fa-solid,
|
||||
.rt-badge--ratio .fas {
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: 1;
|
||||
text-rendering: auto;
|
||||
}
|
||||
|
||||
.rt-badge--ratio .fa-fire::before {
|
||||
content: "\f06d";
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create `content/styles/ratio.css`**
|
||||
|
||||
This depends on `.rt-badge` base styles from `badges.css` (always loaded via manifest). It uses CSS custom properties `--rt-ratio-hue` and `--rt-ratio-sat` set inline by JS to derive all colors. Dark mode changes lightness via the `.rt-dark` selector — no JS DOM walk needed.
|
||||
|
||||
```css
|
||||
/* --- Score ratio badge: temperature-colored --- */
|
||||
.rt-badge--ratio {
|
||||
color: hsl(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 45%);
|
||||
background: hsla(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 45%, 0.1);
|
||||
border-color: hsla(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 45%, 0.25);
|
||||
cursor: default;
|
||||
gap: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rt-badge--ratio .fa-solid {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* --- Dark mode: bump lightness from 45% to 55% --- */
|
||||
html.rt-dark .rt-badge--ratio,
|
||||
.rt-dark .rt-badge--ratio {
|
||||
color: hsl(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 55%);
|
||||
background: hsla(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 55%, 0.1);
|
||||
border-color: hsla(var(--rt-ratio-hue, 0), var(--rt-ratio-sat, 80%), 55%, 0.25);
|
||||
}
|
||||
|
||||
/* --- Neutral state for incalculable ratios (uses rt-badge base only, not rt-badge--ratio) --- */
|
||||
.rt-badge--ratio-neutral {
|
||||
color: #737373;
|
||||
background: rgba(115, 115, 115, 0.1);
|
||||
border-color: rgba(115, 115, 115, 0.25);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
html.rt-dark .rt-badge--ratio-neutral,
|
||||
.rt-dark .rt-badge--ratio-neutral {
|
||||
color: #a3a3a3;
|
||||
background: rgba(163, 163, 163, 0.1);
|
||||
border-color: rgba(163, 163, 163, 0.25);
|
||||
}
|
||||
|
||||
/* --- Disable hover effect on non-interactive ratio badges --- */
|
||||
.rt-badge--ratio:hover,
|
||||
.rt-badge--ratio-neutral:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add CSS files to `manifest.json`**
|
||||
|
||||
In the `content_scripts[0].css` array, add both new CSS files after `fonts.css`:
|
||||
|
||||
```json
|
||||
"css": [
|
||||
"content/styles/badges.css",
|
||||
"content/styles/cards.css",
|
||||
"content/styles/fonts.css",
|
||||
"content/styles/fontawesome-scoped.css",
|
||||
"content/styles/ratio.css"
|
||||
],
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify CSS loads without errors**
|
||||
|
||||
Reload extension. Navigate to `old.reddit.com`. Open the browser console:
|
||||
- No CSS parse errors
|
||||
- No 404 errors for the woff2 font file
|
||||
- No visible changes yet (no JS creating badge elements)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add content/fonts/fontawesome/fa-solid-900.woff2 content/styles/fontawesome-scoped.css content/styles/ratio.css manifest.json
|
||||
git commit -m "feat: add FA solid subset and score ratio badge CSS"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Core ratio module + main.js integration
|
||||
|
||||
Create `ratio.js` with score parsing, comment count parsing, ratio calculation, HSL hue computation, and badge DOM injection. Wire it into `main.js` with enforced call order (after badges) and add to the manifest.
|
||||
|
||||
**Files:**
|
||||
- Create: `content/tweaks/ratio.js`
|
||||
- Modify: `content/main.js`
|
||||
- Modify: `manifest.json`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `settings.scoreRatio` (boolean, from Task 1)
|
||||
- Consumes: `.rt-badge` base styles from `badges.css`
|
||||
- Consumes: `.rt-badge--ratio` styles from `ratio.css` (Task 2)
|
||||
- Consumes: FA `fa-solid fa-fire` classes from `fontawesome-scoped.css` (Task 2)
|
||||
- Produces: `window.RedditTweaks.ratioTweak.apply(settings)`
|
||||
- Produces: `window.RedditTweaks.ratioTweak.processNewPosts(settings)`
|
||||
- Produces: `window.RedditTweaks.ratioTweak.updateSettings(settings)` (no-op)
|
||||
|
||||
- [ ] **Step 1: Create `content/tweaks/ratio.js`**
|
||||
|
||||
```js
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var SCALE_MAX = 100;
|
||||
|
||||
function parseScore(post) {
|
||||
var scoreEl = post.querySelector(".midcol .score.unvoted");
|
||||
if (!scoreEl) return null;
|
||||
|
||||
var raw = scoreEl.getAttribute("title");
|
||||
if (raw && raw.indexOf("\u2022") === -1) {
|
||||
var parsed = parseInt(raw, 10);
|
||||
if (!isNaN(parsed)) return parsed;
|
||||
}
|
||||
|
||||
var text = scoreEl.textContent.trim();
|
||||
if (!text || text.indexOf("\u2022") !== -1) return null;
|
||||
|
||||
var num = parseInt(text, 10);
|
||||
if (!isNaN(num)) return num;
|
||||
|
||||
var match = text.match(/^(-?[\d.]+)\s*([km])/i);
|
||||
if (match) {
|
||||
var val = parseFloat(match[1]);
|
||||
var suffix = match[2].toLowerCase();
|
||||
if (suffix === "k") return Math.round(val * 1000);
|
||||
if (suffix === "m") return Math.round(val * 1000000);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseCommentCount(post) {
|
||||
var commentsLink = post.querySelector(".flat-list .comments");
|
||||
if (!commentsLink) return null;
|
||||
|
||||
var text = commentsLink.textContent.trim();
|
||||
if (text === "comment") return 0;
|
||||
|
||||
var match = text.match(/^(\d+)/);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
}
|
||||
|
||||
function computeRatio(score, comments) {
|
||||
if (score === null || comments === null || comments === 0) return null;
|
||||
return score / comments;
|
||||
}
|
||||
|
||||
function ratioToHue(ratio) {
|
||||
var clamped = Math.max(0, Math.min(ratio, SCALE_MAX));
|
||||
return Math.round(240 - (clamped / SCALE_MAX) * 240);
|
||||
}
|
||||
|
||||
function processPost(post) {
|
||||
if (post.hasAttribute("data-rt-ratio-processed")) return;
|
||||
post.setAttribute("data-rt-ratio-processed", "true");
|
||||
|
||||
var score = parseScore(post);
|
||||
var comments = parseCommentCount(post);
|
||||
var ratio = computeRatio(score, comments);
|
||||
|
||||
var badge = document.createElement("span");
|
||||
|
||||
if (ratio !== null) {
|
||||
badge.className = "rt-badge rt-badge--ratio";
|
||||
badge.style.setProperty("--rt-ratio-hue", String(ratioToHue(ratio)));
|
||||
badge.style.setProperty("--rt-ratio-sat", "80%");
|
||||
|
||||
var text = document.createTextNode(ratio.toFixed(1) + " ");
|
||||
var icon = document.createElement("i");
|
||||
icon.className = "fa-solid fa-fire";
|
||||
badge.appendChild(text);
|
||||
badge.appendChild(icon);
|
||||
} else {
|
||||
badge.className = "rt-badge rt-badge--ratio-neutral";
|
||||
badge.textContent = "\u2014";
|
||||
}
|
||||
|
||||
var container = post.querySelector(".reddit-tweaks-badges");
|
||||
if (container) {
|
||||
container.appendChild(badge);
|
||||
} else {
|
||||
var entry = post.querySelector(".entry");
|
||||
if (!entry) return;
|
||||
var tagline = entry.querySelector(".tagline");
|
||||
if (tagline && tagline.nextSibling) {
|
||||
entry.insertBefore(badge, tagline.nextSibling);
|
||||
} else if (tagline) {
|
||||
tagline.parentNode.appendChild(badge);
|
||||
} else {
|
||||
entry.insertBefore(badge, entry.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processPosts() {
|
||||
var posts = document.querySelectorAll(
|
||||
"div.thing.link:not([data-rt-ratio-processed])"
|
||||
);
|
||||
for (var i = 0; i < posts.length; i++) {
|
||||
processPost(posts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function apply() {
|
||||
processPosts();
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
// No-op: dark mode lightness is handled via CSS selectors on .rt-dark
|
||||
}
|
||||
|
||||
window.RedditTweaks = window.RedditTweaks || {};
|
||||
window.RedditTweaks.ratioTweak = {
|
||||
apply: apply,
|
||||
processNewPosts: processPosts,
|
||||
updateSettings: updateSettings,
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `ratio.js` to `manifest.json`**
|
||||
|
||||
In the `content_scripts[0].js` array, add `content/tweaks/ratio.js` after `cards.js` and before `main.js`:
|
||||
|
||||
```json
|
||||
"js": [
|
||||
"lib/settings.js",
|
||||
"content/tweaks/badges.js",
|
||||
"content/tweaks/color-picker.js",
|
||||
"content/tweaks/color-popover.js",
|
||||
"content/tweaks/cards.js",
|
||||
"content/tweaks/ratio.js",
|
||||
"content/main.js"
|
||||
],
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Wire ratio into `main.js` init**
|
||||
|
||||
In `content/main.js`, inside the `init()` function, add the ratio block **after** the `cardLayout` block (after line 76: `}`) and **before** the observer start (line 78: `if (settings.badgeLayout || settings.cardLayout)`):
|
||||
|
||||
```js
|
||||
if (settings.scoreRatio) {
|
||||
window.RedditTweaks.ratioTweak.apply(settings);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Wire ratio into post observer**
|
||||
|
||||
In `content/main.js`, inside the `startPostObserver` function, in the `if (hasNewPosts)` callback, add ratio processing **after** the `cardTweak.processNewPosts` call (after line 52: `}`):
|
||||
|
||||
```js
|
||||
if (settings.scoreRatio) {
|
||||
window.RedditTweaks.ratioTweak.processNewPosts(updated);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Wire ratio into dark mode observer**
|
||||
|
||||
In `content/main.js`, inside the `startDarkModeObserver` function, in the `if (updated.darkMode === "auto")` callback, add ratio update **after** the `cardTweak.updateSettings` call (after line 13: `}`):
|
||||
|
||||
```js
|
||||
if (updated.scoreRatio) {
|
||||
window.RedditTweaks.ratioTweak.updateSettings(updated);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add `scoreRatio` to storage change reload triggers**
|
||||
|
||||
In `content/main.js`, in the `browser.storage.onChanged.addListener` callback, add `scoreRatio` to the reload condition (inside the `if` that checks for changes requiring reload, after the `cardLayout` check):
|
||||
|
||||
```js
|
||||
(changes.scoreRatio && changes.scoreRatio.newValue !== changes.scoreRatio.oldValue)
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Wire ratio into settings update listener**
|
||||
|
||||
In `content/main.js`, in the `browser.storage.onChanged.addListener` callback, in the `.then(function (updated))` block that runs when no reload is needed, add ratio update **after** the `cardTweak.updateSettings` call:
|
||||
|
||||
```js
|
||||
if (updated.scoreRatio) {
|
||||
window.RedditTweaks.ratioTweak.updateSettings(updated);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Verify end-to-end**
|
||||
|
||||
Reload extension. Navigate to `old.reddit.com`. Verify:
|
||||
- Each post shows a colored ratio badge after the subreddit/comments badges
|
||||
- Badge text is a number with one decimal followed by a fire icon (e.g., "28.7 🔥")
|
||||
- Posts with high ratios (100+) show red badges
|
||||
- Posts with low ratios show blue badges
|
||||
- Posts with mid-range ratios show green/yellow badges
|
||||
- Posts with hidden scores (• in the vote column) show "—" in gray
|
||||
- Posts with 0 comments show "—" in gray
|
||||
- Dark mode (toggle RES night mode or force dark in popup): badge colors shift slightly brighter, fire icon follows
|
||||
- Infinite scroll (RES Never Ending Reddit): new posts get ratio badges
|
||||
- Disabling "Score ratio badge" in popup and reloading: no ratio badges appear
|
||||
- Re-enabling: badges return
|
||||
- No console errors
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add content/tweaks/ratio.js content/main.js manifest.json
|
||||
git commit -m "feat: add score ratio badge with temperature color scale"
|
||||
```
|
||||
157
docs/superpowers/specs/2026-08-27-vote-ratio-badge-design.md
Normal file
157
docs/superpowers/specs/2026-08-27-vote-ratio-badge-design.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user