# Card Layout 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:** Wrap each old Reddit post in a shadcn-styled card with configurable gap, page background, and independent toggle from badges. **Architecture:** Add `rt-card` class to each `div.thing.link` via JS, apply card styling via scoped CSS. Convert internal float layout to flexbox. Shared dark mode detection in `settings.js`. Unified MutationObserver in `main.js` for both badge and card processing. **Tech Stack:** Vanilla JS (IIFE modules on `window.RedditTweaks`), plain CSS, Firefox WebExtension manifest v2. ## Global Constraints - old.reddit.com only — `www.reddit.com` is out of scope - No build tools, no frameworks, no npm dependencies - All CSS values for the card must match shadcn/ui's default neutral theme exactly (see spec `docs/superpowers/specs/2026-08-26-card-layout-design.md` for token table) - Modules use IIFE pattern and expose API on `window.RedditTweaks` - Imports at top of file, no inline imports --- ### Task 1: Extract shared utilities to `settings.js` Move `isDarkMode()` and `getColorPrefix()` from `badges.js` into `settings.js` as shared infrastructure. Add new setting defaults. Update all consumers. **Files:** - Modify: `lib/settings.js` - Modify: `content/tweaks/badges.js` - Modify: `content/tweaks/color-popover.js` **Interfaces:** - Produces: `window.RedditTweaks.isDarkMode(settings)` → `boolean` - Produces: `window.RedditTweaks.getColorPrefix(settings)` → `"light" | "dark"` - Produces: New DEFAULTS keys: `badgeLayout` (true), `cardLayout` (true), `cardBackground` ("reddit"), `cardGap` (16) - [ ] **Step 1: Add dark mode functions and new defaults to `settings.js`** Add `isDarkMode`, `getColorPrefix`, and new setting keys to `lib/settings.js`. Insert after the `resetSettings` function, before the `window.RedditTweaks` assignment: ```js function isDarkMode(settings) { if (settings.darkMode === "light") return false; if (settings.darkMode === "dark") return true; return document.body.classList.contains("res-nightmode"); } function getColorPrefix(settings) { return isDarkMode(settings) ? "dark" : "light"; } ``` Add to `DEFAULTS`: ```js badgeLayout: true, cardLayout: true, cardBackground: "reddit", cardGap: 16, ``` Add to the `window.RedditTweaks` assignment: ```js window.RedditTweaks.isDarkMode = isDarkMode; window.RedditTweaks.getColorPrefix = getColorPrefix; ``` - [ ] **Step 2: Update `badges.js` to use shared functions** In `content/tweaks/badges.js`: Remove the `isDarkMode` and `getColorPrefix` function definitions (lines 8-17). Replace all internal calls to `isDarkMode(settings)` with `window.RedditTweaks.isDarkMode(settings)` and `getColorPrefix(settings)` with `window.RedditTweaks.getColorPrefix(settings)`. Update the exported API — keep `isDarkMode` and `getColorPrefix` as aliases for backward compatibility during this transition, but point them at the shared versions: ```js window.RedditTweaks.badgeTweak = { apply: apply, updateColors: updateColors, isDarkMode: window.RedditTweaks.isDarkMode, getColorPrefix: window.RedditTweaks.getColorPrefix, }; ``` - [ ] **Step 3: Update `color-popover.js` to use shared functions** In `content/tweaks/color-popover.js`, change the `getHelpers()` function (lines 16-21): ```js function getHelpers() { return { isDarkMode: window.RedditTweaks.isDarkMode, getColorPrefix: window.RedditTweaks.getColorPrefix, }; } ``` - [ ] **Step 4: Verify extension still works** Load the extension in Firefox (`about:debugging` → Load Temporary Add-on). Navigate to `old.reddit.com`. Verify: - Badges render with correct colors - Dark mode detection works (if RES is installed, toggle night mode) - Color popover opens and displays correct mode - No console errors - [ ] **Step 5: Commit** ```bash git add lib/settings.js content/tweaks/badges.js content/tweaks/color-popover.js git commit -m "refactor: extract isDarkMode/getColorPrefix to settings.js as shared utilities" ``` --- ### Task 2: Badge layout toggle + FOUC CSS rename Add `badgeLayout` toggle to the popup and gate badge processing behind it. Rename the FOUC CSS class from `rt-enabled` to `rt-badges-enabled`. **Files:** - Modify: `content/styles/badges.css` - Modify: `content/main.js` - Modify: `popup/popup.html` - Modify: `popup/popup.js` **Interfaces:** - Consumes: `settings.badgeLayout` (boolean, from Task 1 DEFAULTS) - Produces: `html.rt-badges-enabled` class on `` (replaces `html.rt-enabled`) - [ ] **Step 1: Rename FOUC selectors in `badges.css`** In `content/styles/badges.css`, change the FOUC prevention selectors (lines 1-5): From: ```css html.rt-enabled .thing.link .tagline .subreddit, html.rt-enabled .thing.link .flat-list li:has(> .comments) { display: none !important; } ``` To: ```css html.rt-badges-enabled .thing.link .tagline .subreddit, html.rt-badges-enabled .thing.link .flat-list li:has(> .comments) { display: none !important; } ``` - [ ] **Step 2: Update `main.js` to gate badges behind `badgeLayout`** Replace the `init()` function in `content/main.js`: ```js async function init() { const settings = await window.RedditTweaks.loadSettings(); if (!settings.enabled) return; if (settings.badgeLayout) { document.documentElement.classList.add("rt-badges-enabled"); window.RedditTweaks.badgeTweak.apply(settings); } browser.storage.onChanged.addListener(function (changes) { if ( (changes.enabled && changes.enabled.newValue !== changes.enabled.oldValue) || (changes.badgeLayout && changes.badgeLayout.newValue !== changes.badgeLayout.oldValue) ) { location.reload(); return; } window.RedditTweaks.loadSettings().then(function (updated) { if (settings.badgeLayout) { window.RedditTweaks.badgeTweak.updateColors(updated); } }); }); } ``` - [ ] **Step 3: Add badge toggle to popup HTML** In `popup/popup.html`, add a badge toggle after the existing enabled checkbox. Change the first section: ```html
``` - [ ] **Step 4: Wire badge toggle in `popup.js`** In `popup/popup.js`, add `badgeLayout` to the `populateControls` function: ```js document.getElementById("badgeLayout").checked = settings.badgeLayout; ``` Add change listener in `init()`, next to the existing `enabled` listener: ```js document.getElementById("badgeLayout").addEventListener("change", saveFromControls); ``` Add to `saveFromControls()` — in the settings object construction, add: ```js badgeLayout: document.getElementById("badgeLayout").checked, ``` - [ ] **Step 5: Verify badge toggle works** Reload extension. Open popup: - Badge layout checkbox is checked by default - Unchecking it and reloading old.reddit.com: original subreddit/comment links are visible, no badges - Checking it back: badges return after reload - No console errors - [ ] **Step 6: Commit** ```bash git add content/styles/badges.css content/main.js popup/popup.html popup/popup.js git commit -m "feat: add badgeLayout toggle, rename FOUC class to rt-badges-enabled" ``` --- ### Task 3: Create card CSS Create `content/styles/cards.css` with all card styling per the spec. Add it to the manifest. **Files:** - Create: `content/styles/cards.css` - Modify: `manifest.json` **Interfaces:** - Produces: CSS rules scoped under `.rt-card`, `html.rt-card-enabled`, `html.rt-card-bg-shadcn` - [ ] **Step 1: Create `content/styles/cards.css`** ```css /* --- Post list container --- */ html.rt-card-enabled #siteTable { display: flex; flex-direction: column; align-items: flex-start; gap: var(--rt-card-gap, 1rem); } html.rt-card-enabled #siteTable > .clearleft { display: none; } /* --- Card container --- */ .rt-card { background: #ffffff; color: #0a0a0a; border: 1px solid #e5e5e5; border-radius: 0.875rem; padding: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); transition: border-color 0.15s; display: flex; align-items: flex-start; gap: 0.75rem; width: 100%; overflow: hidden; } .rt-card:hover { border-color: #d4d4d4; } /* --- Card internal layout overrides --- */ .rt-card .midcol, .rt-card .thumbnail { float: none; flex-shrink: 0; } .rt-card .entry { flex: 1; min-width: 0; } .rt-card .clearleft { display: none; } /* --- Card dark mode --- */ .rt-dark .rt-card { background: #171717; color: #fafafa; border-color: rgba(255, 255, 255, 0.1); } .rt-dark .rt-card:hover { border-color: rgba(255, 255, 255, 0.18); } /* --- Page background override --- */ html.rt-card-bg-shadcn .content[role="main"] { background: #f5f5f5; } html.rt-card-bg-shadcn.rt-dark .content[role="main"] { background: #262626; } ``` - [ ] **Step 2: Add `cards.css` to `manifest.json`** In the `content_scripts[0].css` array, add `content/styles/cards.css` after `badges.css`: ```json "css": ["content/styles/badges.css", "content/styles/cards.css"], ``` - [ ] **Step 3: Verify no parse errors** Reload extension. Open `old.reddit.com`. No console CSS parse errors. Cards don't render yet (no JS adding the `rt-card` class), but the CSS is loaded. - [ ] **Step 4: Commit** ```bash git add content/styles/cards.css manifest.json git commit -m "feat: add card CSS with shadcn design tokens" ``` --- ### Task 4: Create card JS + unified observer Create the card tweak module. Refactor `main.js` to use a single shared MutationObserver that dispatches to both badge and card processors. Remove the duplicate observer from `badges.js`. **Files:** - Create: `content/tweaks/cards.js` - Modify: `content/tweaks/badges.js` - Modify: `content/main.js` - Modify: `manifest.json` **Interfaces:** - Consumes: `window.RedditTweaks.isDarkMode(settings)` (from Task 1) - Consumes: `settings.cardLayout`, `settings.cardBackground`, `settings.cardGap` (from Task 1) - Produces: `window.RedditTweaks.cardTweak.apply(settings)` — processes posts + applies card classes - Produces: `window.RedditTweaks.cardTweak.updateSettings(settings)` — live-update gap/background - Produces: `window.RedditTweaks.cardTweak.processNewPosts(settings)` — process unprocessed posts (called by shared observer) - Modifies: `window.RedditTweaks.badgeTweak` — adds `processNewPosts(settings)` for shared observer, removes internal observer - [ ] **Step 1: Create `content/tweaks/cards.js`** ```js (function () { "use strict"; function processPost(post) { if (post.classList.contains("rt-card")) return; post.classList.add("rt-card"); } function processPosts() { var posts = document.querySelectorAll("div.thing.link:not(.rt-card)"); for (var i = 0; i < posts.length; i++) { processPost(posts[i]); } } function applyDarkMode(settings) { if (window.RedditTweaks.isDarkMode(settings)) { document.documentElement.classList.add("rt-dark"); } else { document.documentElement.classList.remove("rt-dark"); } } function applyBackground(settings) { if (settings.cardBackground === "shadcn") { document.documentElement.classList.add("rt-card-bg-shadcn"); } else { document.documentElement.classList.remove("rt-card-bg-shadcn"); } } function applyGap(settings) { var siteTable = document.getElementById("siteTable"); if (siteTable) { siteTable.style.setProperty("--rt-card-gap", settings.cardGap + "px"); } } function apply(settings) { document.documentElement.classList.add("rt-card-enabled"); applyDarkMode(settings); applyBackground(settings); applyGap(settings); processPosts(); } function updateSettings(settings) { applyDarkMode(settings); applyBackground(settings); applyGap(settings); } window.RedditTweaks = window.RedditTweaks || {}; window.RedditTweaks.cardTweak = { apply: apply, updateSettings: updateSettings, processNewPosts: processPosts, }; })(); ``` - [ ] **Step 2: Expose `processNewPosts` from `badges.js`, remove both internal observers** In `content/tweaks/badges.js`: Remove the `contentObserver` variable declaration (line 4: `let contentObserver = null;`). Remove the `darkModeObserver` variable declaration (line 5: `let darkModeObserver = null;`). Remove the `getObserverTarget()` function (lines 106-112). Remove the `startContentObserver(settings)` function (lines 114-135). Remove the `startDarkModeObserver()` function (lines 137-149). Remove both observer calls from the `apply()` function: `startContentObserver(settings)` (line 168) and `startDarkModeObserver()` (line 169). Add `processNewPosts` to the exported API: ```js window.RedditTweaks.badgeTweak = { apply: apply, updateColors: updateColors, isDarkMode: window.RedditTweaks.isDarkMode, getColorPrefix: window.RedditTweaks.getColorPrefix, processNewPosts: processPosts, }; ``` - [ ] **Step 3: Add unified observer to `main.js`** In `content/main.js`, add shared observer functions before `init()`. First, the dark mode observer (moved from `badges.js`). This watches for RES night mode toggles and dispatches to both modules: ```js function startDarkModeObserver(settings) { var observer = new MutationObserver(function () { if (settings.darkMode === "auto") { window.RedditTweaks.loadSettings().then(function (updated) { if (settings.badgeLayout) { window.RedditTweaks.badgeTweak.updateColors(updated); } if (settings.cardLayout) { window.RedditTweaks.cardTweak.updateSettings(updated); } }); } }); observer.observe(document.body, { attributes: true, attributeFilter: ["class"], }); } ``` Then the content observer for new posts: ```js function startPostObserver(settings) { var target = document.getElementById("siteTable") || document.querySelector('.content[role="main"]') || document.body; var observer = new MutationObserver(function (mutations) { var hasNewPosts = false; for (var i = 0; i < mutations.length; i++) { var added = mutations[i].addedNodes; for (var j = 0; j < added.length; j++) { var node = added[j]; if (node.nodeType !== Node.ELEMENT_NODE) continue; if ((node.matches && node.matches("div.thing.link")) || (node.querySelector && node.querySelector("div.thing.link"))) { hasNewPosts = true; break; } } if (hasNewPosts) break; } if (hasNewPosts) { window.RedditTweaks.loadSettings().then(function (updated) { if (settings.badgeLayout) { window.RedditTweaks.badgeTweak.processNewPosts(updated); } if (settings.cardLayout) { window.RedditTweaks.cardTweak.processNewPosts(updated); } }); } }); observer.observe(target, { childList: true, subtree: true }); } ``` Update `init()` to call `cardTweak.apply`, start the shared observer, and observe dark mode changes for cards: ```js async function init() { const settings = await window.RedditTweaks.loadSettings(); if (!settings.enabled) return; if (settings.badgeLayout) { document.documentElement.classList.add("rt-badges-enabled"); window.RedditTweaks.badgeTweak.apply(settings); } if (settings.cardLayout) { window.RedditTweaks.cardTweak.apply(settings); } if (settings.badgeLayout || settings.cardLayout) { startPostObserver(settings); startDarkModeObserver(settings); } browser.storage.onChanged.addListener(function (changes) { if ( (changes.enabled && changes.enabled.newValue !== changes.enabled.oldValue) || (changes.badgeLayout && changes.badgeLayout.newValue !== changes.badgeLayout.oldValue) || (changes.cardLayout && changes.cardLayout.newValue !== changes.cardLayout.oldValue) ) { location.reload(); return; } window.RedditTweaks.loadSettings().then(function (updated) { if (settings.badgeLayout) { window.RedditTweaks.badgeTweak.updateColors(updated); } if (settings.cardLayout) { window.RedditTweaks.cardTweak.updateSettings(updated); } }); }); } ``` - [ ] **Step 4: Add `cards.js` to `manifest.json`** In the `content_scripts[0].js` array, add `content/tweaks/cards.js` 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/main.js" ], ``` Also remove `"*://www.reddit.com/*"` from the `matches` array (out of scope per spec): ```json "matches": ["*://old.reddit.com/*"], ``` - [ ] **Step 5: Verify cards render** Reload extension. Navigate to `old.reddit.com`: - Each post is wrapped in a card: white background, 1px `#e5e5e5` border, 14px rounded corners, subtle shadow - Posts are separated by 16px gaps - Vote arrows, thumbnail, and entry are horizontally aligned (no floats) - Hovering a card darkens the border to `#d4d4d4` - Badges still render inside cards (if badge layout is enabled) - No console errors - [ ] **Step 6: Commit** ```bash git add content/tweaks/cards.js content/tweaks/badges.js content/main.js manifest.json git commit -m "feat: add card layout tweak with unified post observer" ``` --- ### Task 5: Popup controls for card settings Add card layout controls to the extension popup: enable toggle, background style dropdown, card spacing dropdown. **Files:** - Modify: `popup/popup.html` - Modify: `popup/popup.js` **Interfaces:** - Consumes: `settings.cardLayout`, `settings.cardBackground`, `settings.cardGap` (from Task 1 DEFAULTS) - [ ] **Step 1: Add card layout section to popup HTML** In `popup/popup.html`, add this section after the Dark Mode section and before the Light Mode Colors section: ```html

Card Layout

``` - [ ] **Step 2: Wire card controls in `popup.js`** In `populateControls(settings)`, add: ```js document.getElementById("cardLayout").checked = settings.cardLayout; document.getElementById("cardBackground").value = settings.cardBackground; document.getElementById("cardGap").value = String(settings.cardGap); ``` In `saveFromControls()`, add to the settings object: ```js cardLayout: document.getElementById("cardLayout").checked, cardBackground: document.getElementById("cardBackground").value, cardGap: parseInt(document.getElementById("cardGap").value, 10), ``` In `init()`, add change listeners: ```js document.getElementById("cardLayout").addEventListener("change", saveFromControls); document.getElementById("cardBackground").addEventListener("change", saveFromControls); document.getElementById("cardGap").addEventListener("change", saveFromControls); ``` - [ ] **Step 3: Verify settings work end-to-end** Reload extension. Open popup: - Card layout checkbox is checked by default - Background dropdown shows "Reddit Default" - Card spacing shows "16px" - Changing card spacing to "24px": cards on old.reddit.com update gap live (no reload) - Changing background to "shadcn Style": page background becomes `#f5f5f5` (live) - Unchecking card layout: page reloads, cards disappear, posts show flat list - Settings persist after closing and reopening popup - Settings persist after browser restart - [ ] **Step 4: Commit** ```bash git add popup/popup.html popup/popup.js git commit -m "feat: add card layout controls to extension popup" ```