Add design spec for card layout: shadcn-style post cards

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 20:22:24 -04:00
parent 37c142f6e6
commit 6c017f688d

View File

@@ -0,0 +1,314 @@
# Card Layout: shadcn-Style Post Cards — Design Spec
**Date:** 2026-08-26
**Status:** Draft
## Overview
Replace old Reddit's flat post list with individual cards styled to match shadcn/ui's Card component. Each `div.thing.link` gets card treatment — background, border, radius, shadow, and internal flexbox layout — using the exact design tokens from shadcn's default neutral theme.
Cards are a separate toggle from badges. Either can be enabled independently.
## Approach
Class injection + scoped CSS overrides. Each `.thing.link` receives an `rt-card` class via JS. All card styling is scoped under `.rt-card` in CSS. The existing DOM tree stays intact — no element relocation or wrapper injection. The internal float-based layout is overridden to flexbox within the `.rt-card` scope.
## shadcn Reference Values
All values sourced from the [shadcn Card registry](https://ui.shadcn.com/r/styles/new-york-v4/card.json) and [theming docs](https://ui.shadcn.com/docs/theming). The Card component classes are:
```
flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm
```
### Token-to-CSS Mapping (Default Neutral Theme)
| Token | Light | Dark |
|---|---|---|
| `--card` | `#ffffff` (oklch 1 0 0) | `#171717` (oklch 0.205 0 0) |
| `--card-foreground` | `#0a0a0a` (oklch 0.145 0 0) | `#fafafa` (oklch 0.985 0 0) |
| `--border` | `#e5e5e5` (oklch 0.922 0 0) | `rgba(255,255,255,0.1)` (oklch 1 0 0 / 10%) |
| `--muted-foreground` | `#737373` (oklch 0.556 0 0) | `#a3a3a3` (oklch 0.708 0 0) |
| `--muted` | `#f5f5f5` (oklch 0.97 0 0) | `#262626` (oklch 0.269 0 0) |
| `--radius` | `0.625rem` (10px) | same |
### Computed CSS Values
| Property | Tailwind class | Value |
|---|---|---|
| border-radius | `rounded-xl` | `0.875rem` (14px) — `--radius` × 1.4 |
| vertical padding | `py-6` | `1.5rem` (24px) |
| horizontal padding | `px-6` (CardContent) | `1.5rem` (24px) |
| section gap | `gap-6` | `1.5rem` (24px) |
| shadow | `shadow-sm` | `0 1px 2px 0 rgb(0 0 0 / 0.05)` |
---
## Card Container Styling
shadcn's Card root has `py-6` (vertical only); horizontal padding lives on CardContent/CardHeader/CardFooter (`px-6`). Since we're styling a flat `.thing.link` without shadcn's section anatomy, we apply `padding: 1.5rem` on all sides — equivalent to both `py-6` and `px-6` combined.
### Light Mode
```css
.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;
}
```
### Dark Mode
```css
.rt-dark .rt-card {
background: #171717;
color: #fafafa;
border-color: rgba(255, 255, 255, 0.1);
}
```
### Hover Effect
Border darkens one step on hover:
```css
.rt-card:hover {
border-color: #d4d4d4; /* neutral-300 */
}
.rt-dark .rt-card:hover {
border-color: rgba(255, 255, 255, 0.18);
}
```
Shadow does not change on hover — consistent with shadcn's static card.
---
## Internal Layout
Old Reddit uses float-based layout inside `.thing.link`:
```
[.rank] [.midcol (votes)] [.thumbnail] [.entry (title + tagline + buttons)]
```
All children float left; `.entry` flows after the floats. A `.clearleft` div clears at the end.
The card overrides this to flexbox:
```css
.rt-card {
display: flex;
align-items: flex-start;
gap: 0.75rem;
overflow: hidden;
}
.rt-card .midcol,
.rt-card .thumbnail {
float: none;
flex-shrink: 0;
}
.rt-card .entry {
flex: 1;
min-width: 0;
}
.rt-card .clearleft {
display: none;
}
```
The `.rank` element (post number, only visible when enabled in Reddit preferences) participates in the flex flow naturally.
The badges container (`.reddit-tweaks-badges`) stays at the top of `.entry` where it's already injected by the badge tweak. No change to badge positioning — badges sit inside the card when both features are enabled.
---
## Settings
Four new settings:
| Key | Type | Default | Description |
|---|---|---|---|
| `badgeLayout` | `boolean` | `true` | Enable/disable badge replacements (new toggle for existing feature) |
| `cardLayout` | `boolean` | `true` | Enable/disable card layout |
| `cardBackground` | `"reddit" \| "shadcn"` | `"reddit"` | Page background style |
| `cardGap` | `number` | `16` | Pixel gap between cards |
`badgeLayout` is a new toggle that controls the existing badge replacement feature. Previously, badges always ran when `enabled` was on. Now `enabled` remains the master switch, and `badgeLayout`/`cardLayout` are independent sub-toggles beneath it.
The FOUC CSS that hides original subreddit/comment elements is scoped to `html.rt-badges-enabled` (renamed from `html.rt-enabled`) so it only applies when badges are active. `main.js` adds `rt-badges-enabled` to `<html>` only when `settings.badgeLayout` is true.
### Page Background Options
- `"reddit"` — no background override. Reddit's native background (`#f6f7f8` light, `#1a1a1b` dark) provides natural contrast with the white/dark card surfaces.
- `"shadcn"` — override the content area background to shadcn's `--muted` token (`#f5f5f5` light / `#262626` dark). Gives cards slightly more visual lift.
### Card Gap
The gap between cards is controlled by a CSS custom property `--rt-card-gap` set on `#siteTable`. The popup offers preset values: 8px, 12px, 16px (default), 24px, 32px.
### Popup UI
A badge toggle is added to the existing "Enabled" section, and a new "Card Layout" section appears between "Dark Mode" and "Light Mode Colors":
```
┌─────────────────────────────┐
│ [✓] Enabled │
│ [✓] Badge layout │
└─────────────────────────────┘
┌─────────────────────────────┐
│ Card Layout │
│ │
│ [✓] Enable card layout │
│ │
│ Background Style │
│ [Reddit Default ▼] │
│ │
│ Card Spacing │
│ [16px ▼] │
└─────────────────────────────┘
```
---
## Post List Container
The `#siteTable` (parent of all `.thing.link` elements) is converted to a flex column so the gap property works:
```css
html.rt-card-enabled #siteTable {
display: flex;
flex-direction: column;
gap: var(--rt-card-gap, 1rem);
}
```
Non-post children of `#siteTable` (ads, "next page" links, clearfix divs) participate in the flex flow. Ads and clearfix divs that add unwanted gaps are hidden:
```css
html.rt-card-enabled #siteTable > .clearleft {
display: none;
}
```
Page background override:
```css
html.rt-card-bg-shadcn .content[role="main"] {
background: #f5f5f5;
}
html.rt-card-bg-shadcn.rt-dark .content[role="main"] {
background: #262626;
}
```
---
## Architecture
### New Files
| File | Purpose |
|---|---|
| `content/tweaks/cards.js` | Card tweak module |
| `content/styles/cards.css` | Card CSS |
### Modified Files
| File | Change |
|---|---|
| `manifest.json` | Add `content/styles/cards.css` to CSS array, add `content/tweaks/cards.js` to JS array (before `main.js`) |
| `lib/settings.js` | Add `badgeLayout`, `cardLayout`, `cardBackground`, `cardGap` to `DEFAULTS` |
| `content/main.js` | Gate badge tweak behind `settings.badgeLayout`. Call `cardTweak.apply(settings)` when `settings.cardLayout` is true. Add `rt-badges-enabled` class only when badges are on. Wire `storage.onChanged` to update card settings live. |
| `content/styles/badges.css` | Change FOUC selectors from `html.rt-enabled` to `html.rt-badges-enabled` |
| `popup/popup.html` | Add "Card Layout" section with toggle, background dropdown, spacing dropdown. Add badge toggle to existing section. |
| `popup/popup.js` | Wire the new controls to save/load |
### Module API
`content/tweaks/cards.js` exposes:
```js
window.RedditTweaks.cardTweak = {
apply(settings), // process existing posts + start observer
updateSettings(settings), // live-update gap/background without reload
};
```
### `apply(settings)` Responsibilities
1. Add `rt-card-enabled` class to `<html>`.
2. Query all `div.thing.link`, add `rt-card` class to each.
3. Set `--rt-card-gap` custom property on `#siteTable` from `settings.cardGap`.
4. Apply background class (`rt-card-bg-shadcn`) to `<html>` if `settings.cardBackground === "shadcn"`.
5. Apply dark mode class (`rt-dark`) based on current dark mode state.
6. Start MutationObserver on `#siteTable` (or fallback container) to add `rt-card` to dynamically loaded posts (RES infinite scroll, etc.).
### `updateSettings(settings)` Responsibilities
1. Update `--rt-card-gap` custom property.
2. Toggle `rt-card-bg-shadcn` class on `<html>`.
3. No need to reprocess individual posts — the CSS handles the rest via the scoped classes.
### Interaction with Badges
Cards and badges are fully independent:
- Both enabled: badges sit inside the card (in `.entry`) as they do today.
- Cards only: post renders as a card without badge replacements.
- Badges only: existing behavior unchanged, no card styling.
- Neither (but `enabled` is on): no tweaks are applied, original Reddit layout is preserved.
### Dark Mode
The card tweak respects the same dark mode setting (`auto`/`light`/`dark`) as badges. The `rt-dark` class on the card container (already managed by `badges.js` for badge containers) is extended to also apply to the `<html>` element so card CSS can reference it. The `cards.js` module reads the dark mode state from the shared `badgeTweak.isDarkMode()` utility.
---
## Edge Cases
- **RES infinite scroll:** The MutationObserver catches new `.thing.link` elements and adds `rt-card`. Same pattern as the badge tweak's observer.
- **RES night mode toggle:** Dark mode observer (already in `badges.js`) triggers `updateSettings()` to refresh card dark mode.
- **Promoted posts / ads:** Reddit's promoted posts use `.thing.promoted.link`. They match `div.thing.link` and get card treatment. If this is undesirable, they can be excluded via `.thing.promoted` selector, but for now they're included for visual consistency.
- **Self-text expanders:** Clicking "expand" on a self-post adds content inside `.entry`. Since `.entry` has `flex: 1`, the card grows to accommodate the expanded content naturally.
- **Thumbnails missing:** Some posts have no thumbnail (text posts, or when thumbnails are disabled). The flex layout handles this — `.entry` fills the available space regardless of whether `.thumbnail` is present.
- **Non-post elements in `#siteTable`:** The flex column + gap on `#siteTable` affects all children, including the "next page" nav links. These continue to render normally in the flex flow. Clearfix divs (`.clearleft`) are hidden.
- **Cards disabled mid-session:** Toggling `cardLayout` off requires a page reload (same pattern as the existing `enabled` toggle). The `storage.onChanged` listener detects the change and reloads.
- **Cards enabled but badges disabled:** The `.reddit-tweaks-badges` container is not injected. The card renders the post without badges, showing the original subreddit link and comments link in their default positions (not hidden, since the badge tweak's FOUC CSS is scoped to `html.rt-badges-enabled`).
- **Popover positioning:** The color popover uses `position: absolute` relative to the badge. Inside a card, the badge's offset parent is still `.entry` (or the card itself). The popover's existing viewport-aware positioning logic handles this.
## Testing
Manual testing checklist:
1. Enable card layout — each post renders as a card with white background, border, rounded corners, and subtle shadow.
2. Hover a card — border darkens subtly, no shadow change.
3. Cards have proper internal layout: vote arrows, thumbnail, and entry are horizontally aligned with 12px gaps.
4. Long post titles wrap within the card without overflowing.
5. Posts without thumbnails render correctly (entry fills the card width).
6. Self-text expansion works — card grows to fit expanded content.
7. Card gap setting: changing from 16px to 8px/24px/32px updates spacing between cards live.
8. Background setting "shadcn" changes the page background to `#f5f5f5`.
9. Background setting "Reddit Default" uses Reddit's native background.
10. Dark mode: cards switch to dark surface (`#171717`), dark border, light text.
11. Dark mode: shadcn background switches to `#262626`.
12. Dark mode hover: border lightens subtly.
13. RES infinite scroll: new posts loaded via infinite scroll get card treatment.
14. RES night mode toggle: cards respond to night mode changes.
15. Cards + badges both enabled: badges appear inside the card at the top of `.entry`.
16. Cards enabled, badges disabled: posts render as cards without badge replacements.
17. Cards disabled, badges enabled: existing badge behavior unchanged, no card styling.
18. Disable card layout toggle — page reloads and posts return to flat list.
19. Card spacing dropdown offers 8px, 12px, 16px, 24px, 32px.
20. Settings persist across browser restart.