Add design spec for badge polish: gear animation + custom color picker
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
210
docs/superpowers/specs/2026-08-26-badge-polish-design.md
Normal file
210
docs/superpowers/specs/2026-08-26-badge-polish-design.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Badge Polish: Gear Animation + Custom Color Picker — Design Spec
|
||||
|
||||
**Date:** 2026-08-26
|
||||
**Status:** Draft
|
||||
**Parent spec:** `2026-08-26-inline-color-popover-design.md`
|
||||
|
||||
## Overview
|
||||
|
||||
Two improvements to the inline color popover and badge system:
|
||||
|
||||
1. **Gear icon animation** — remove the permanent padding gap on subreddit badges. The badge grows on hover to reveal the gear, then shrinks back.
|
||||
2. **Custom color picker** — replace all native `<input type="color">` elements (which delegate to the macOS system picker) with an inline SV+Hue picker widget built in vanilla JS/canvas.
|
||||
|
||||
## Improvement 1: Gear Icon Animation
|
||||
|
||||
### Current Behavior
|
||||
|
||||
`.rt-badge--subreddit` has `padding-right: 18px` at all times, reserving space for the gear icon even when it's hidden. This wastes ~12px on every subreddit badge.
|
||||
|
||||
### New Behavior
|
||||
|
||||
The subreddit badge uses `padding-right: 6px` at rest (matching the left padding). On hover, `padding-right` transitions to `18px` over 150ms, smoothly expanding the badge to reveal the gear icon. The gear's opacity transition (0 → 0.6, 150ms) runs in parallel. When the mouse leaves, both transitions reverse — the gear fades out and the badge shrinks.
|
||||
|
||||
The adjacent comments badge shifts right during the expansion. This is natural inline-block flow and at 150ms feels intentional rather than jittery.
|
||||
|
||||
### CSS Changes
|
||||
|
||||
```css
|
||||
.rt-badge--subreddit {
|
||||
position: relative;
|
||||
padding-right: 6px;
|
||||
transition: padding-right 0.15s;
|
||||
/* background, border-color, color unchanged */
|
||||
}
|
||||
|
||||
.rt-badge--subreddit:hover {
|
||||
padding-right: 18px;
|
||||
}
|
||||
```
|
||||
|
||||
The gear icon CSS (`.rt-badge-gear`) is unchanged — it remains `position: absolute; right: 3px` and animates opacity on parent hover.
|
||||
|
||||
### File Changes
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `content/styles/badges.css` | Change `.rt-badge--subreddit` `padding-right` from `18px` to `6px`, add `transition: padding-right 0.15s`, add `.rt-badge--subreddit:hover { padding-right: 18px }`. |
|
||||
|
||||
No JS changes required.
|
||||
|
||||
---
|
||||
|
||||
## Improvement 2: Custom Color Picker Widget
|
||||
|
||||
### Problem
|
||||
|
||||
The native `<input type="color">` delegates to the OS-level color picker. On macOS, this opens a separate floating panel that is detached from the popover context, has limited precision, and feels foreign to the extension's UI.
|
||||
|
||||
### Solution
|
||||
|
||||
Replace all `<input type="color">` elements — in both the inline popover and the extension popup — with a custom SV+Hue picker widget. The picker renders inline within the containing UI (popover or popup), provides full color control, and stays visually consistent with the extension's design.
|
||||
|
||||
### Picker Anatomy
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ │ │ ← SV square (saturation-value)
|
||||
│ │ [cursor ●] │ │ Click/drag to pick S + V
|
||||
│ │ │ │ Canvas-rendered for current hue
|
||||
│ └──────────────────────┘ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ ← hue slider → │ │ ← Hue bar (horizontal rainbow)
|
||||
│ └──────────────────────────┘ │ Click/drag to change hue
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
### Color Model
|
||||
|
||||
The picker uses HSV internally (hue, saturation, value). HSV maps naturally to the SV square (X = saturation, Y = value) and hue bar (X = hue). Colors are converted to/from hex (`#rrggbb`) for storage, display, and sync with the hex text input.
|
||||
|
||||
### SV Square
|
||||
|
||||
A `<canvas>` element, redrawn whenever the hue changes. The canvas renders a two-dimensional gradient:
|
||||
|
||||
- Horizontal axis: saturation (0% at left → 100% at right)
|
||||
- Vertical axis: value/brightness (100% at top → 0% at bottom)
|
||||
|
||||
The gradient is drawn by filling the canvas with the current hue at full saturation, then overlaying a white-to-transparent horizontal gradient and a transparent-to-black vertical gradient. A small circular cursor indicates the current position.
|
||||
|
||||
**Size:** The SV square fills the available width minus padding. In the 260px popover, this is ~230px. In the 320px popup, ~290px. Height is fixed at 140px regardless of width — this keeps the popover from growing too tall while still providing a usable picking area. The result is a landscape rectangle, not a true square.
|
||||
|
||||
### Hue Bar
|
||||
|
||||
A horizontal strip below the SV square. The background is a static CSS `linear-gradient` cycling through the hue spectrum (red → yellow → green → cyan → blue → magenta → red). A thin vertical indicator shows the current hue position. Click or drag to change hue, which redraws the SV square.
|
||||
|
||||
**Height:** 14px. Full width of the picker.
|
||||
|
||||
### Interaction
|
||||
|
||||
- **Click** on the SV square sets saturation and value immediately.
|
||||
- **Drag** on the SV square continuously updates saturation and value (tracks `mousemove` after `mousedown`, stops on `mouseup` or mouse leaving the document).
|
||||
- **Click/drag** on the hue bar changes hue and redraws the SV square.
|
||||
- The hex text input stays bidirectionally synced: valid hex input updates the picker cursor and hue position; picker changes update the hex input.
|
||||
|
||||
### Integration with Popover
|
||||
|
||||
Each color row in the popover currently has:
|
||||
|
||||
```
|
||||
Label [<input type="color">] [<input type="text" hex>]
|
||||
```
|
||||
|
||||
This changes to:
|
||||
|
||||
```
|
||||
Label [<div swatch ■>] [<input type="text" hex>]
|
||||
```
|
||||
|
||||
The swatch `<div>` is a small colored rectangle (same size as the old color input: 28×20px) showing the current color. Clicking the swatch opens the picker inline, appearing below the clicked row within the popover. The picker slides into view — it's part of the popover DOM, so the popover grows vertically to accommodate it.
|
||||
|
||||
Only one picker is open at a time within the popover. Clicking a different row's swatch closes the current picker and opens the new one. Clicking the same swatch while its picker is already open toggles the picker closed.
|
||||
|
||||
When the picker is open, changes are reflected live in:
|
||||
- The swatch `<div>` for that row
|
||||
- The hex text input for that row
|
||||
- The popover's live preview badge
|
||||
|
||||
### Integration with Popup
|
||||
|
||||
The popup uses the same picker widget. Each `<input type="color">` in the popup (global colors + per-subreddit overrides) is replaced with a clickable swatch `<div>`. Clicking opens the picker inline below the control, within the popup's scrollable area.
|
||||
|
||||
The popup loads the picker module via a `<script>` tag in `popup.html` (the content script version is loaded via the manifest for the page context).
|
||||
|
||||
### Picker API
|
||||
|
||||
The picker module exposes `window.RedditTweaks.colorPicker`:
|
||||
|
||||
- `create(containerEl)` — creates a picker instance and appends it to the container. Returns a picker object.
|
||||
- Picker object:
|
||||
- `open(anchorEl, currentHex, onChange)` — shows the picker below `anchorEl`, sets initial color from hex, calls `onChange(hex)` on every change.
|
||||
- `close()` — hides the picker.
|
||||
- `setColor(hex)` — programmatically update the picker's color (for external sync).
|
||||
- `isOpen()` — returns whether the picker is currently visible.
|
||||
- `destroy()` — removes DOM elements and event listeners.
|
||||
|
||||
### Styling
|
||||
|
||||
**Light mode:**
|
||||
- Picker background: matches parent (transparent, inherits popover/popup background)
|
||||
- SV square border: `1px solid #ccc`, `border-radius: 3px`
|
||||
- Hue bar border: `1px solid #ccc`, `border-radius: 3px`
|
||||
- SV cursor: 10px circle, `2px solid white`, `box-shadow: 0 0 2px rgba(0,0,0,0.5)` for visibility against any background
|
||||
- Hue indicator: 2px wide vertical line, white with dark shadow
|
||||
|
||||
**Dark mode (`.rt-dark` context):**
|
||||
- SV square border: `1px solid #555`
|
||||
- Hue bar border: `1px solid #555`
|
||||
- Cursor and indicator styles unchanged (they're designed to be visible on any background)
|
||||
|
||||
**Spacing:** 6px margin between SV square and hue bar. 8px margin above the picker (separating it from the swatch row). 8px margin below (before the next row or preview section).
|
||||
|
||||
### Popover Repositioning
|
||||
|
||||
When the picker opens inside the popover, the popover grows taller. If this would push the popover below the viewport bottom, the popover's existing positioning logic handles the flip (it already accounts for viewport overflow). The popover doesn't reposition dynamically while open — the picker height is fixed, so the size change happens once on open.
|
||||
|
||||
### File Changes
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `content/tweaks/color-picker.js` | **New file.** HSV↔hex conversion, canvas SV square rendering, hue bar, mouse tracking, create/open/close/destroy API. Exposes `window.RedditTweaks.colorPicker`. |
|
||||
| `content/styles/badges.css` | Add CSS for `.rt-color-picker`, `.rt-color-picker-sv` (canvas), `.rt-color-picker-hue` (bar), `.rt-color-picker-sv-cursor`, `.rt-color-picker-hue-indicator`. Light and dark mode variants. |
|
||||
| `content/tweaks/color-popover.js` | Replace `<input type="color" class="rt-color-swatch">` with `<div class="rt-color-swatch">`. On swatch click, open picker. Wire picker's `onChange` to update hex input, swatch color, and live preview. Close picker on mode toggle. |
|
||||
| `popup/popup.html` | Add `<script src="../content/tweaks/color-picker.js"></script>` before `popup.js`. |
|
||||
| `popup/popup.js` | Replace `<input type="color">` creation with swatch `<div>` + picker integration for global colors and per-subreddit overrides. |
|
||||
| `popup/popup.css` | Add picker styles for the popup context (same classes, scoped by parent). |
|
||||
| `manifest.json` | Add `content/tweaks/color-picker.js` to content scripts array (before `color-popover.js`). |
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Picker open + popover dismiss:** If the picker is open when the popover is dismissed (Cancel, Escape, click-outside), the popover hides and the picker is hidden with it. No orphaned state.
|
||||
- **Picker open + mode toggle:** Switching light/dark mode in the popover closes any open picker and loads the new mode's values into the swatches.
|
||||
- **Popup scroll:** The picker is inline in the DOM flow, so it scrolls naturally with the popup's controls.
|
||||
- **Multiple pickers:** Only one picker open at a time in any context (popover or popup). Opening a new one closes the previous.
|
||||
- **Hex input while picker is open:** Typing a valid hex into the text input updates the picker's cursor and hue position in real time.
|
||||
- **Invalid hex while picker is open:** The hex input shows the error border (existing behavior). The picker position is not updated until a valid hex is entered.
|
||||
- **Very small viewport:** The SV square has a minimum size of 100×100px. If the container is too narrow, the picker still works but may feel cramped.
|
||||
- **Color precision:** HSV→hex conversion rounds to the nearest integer per channel. This can cause minor rounding differences when round-tripping hex→HSV→hex, but the difference is imperceptible (at most ±1 per channel).
|
||||
|
||||
## Testing
|
||||
|
||||
Manual testing additions (extends the existing 20-item checklist):
|
||||
|
||||
1. Hovering over a subreddit badge smoothly expands the badge to reveal the gear (no gap at rest).
|
||||
2. The comments badge shifts right during the expansion and back when hover ends.
|
||||
3. The animation is ~150ms and feels snappy, not laggy.
|
||||
4. Clicking a color swatch in the popover opens the SV+Hue picker inline.
|
||||
5. Dragging on the SV square updates the hex input and live preview in real time.
|
||||
6. Dragging on the hue bar changes the SV square's base color.
|
||||
7. Only one picker is open at a time — clicking another swatch closes the first.
|
||||
8. Typing a valid hex updates the picker cursor position.
|
||||
9. Typing an invalid hex shows the error border but doesn't move the picker.
|
||||
10. Closing the popover while the picker is open works cleanly.
|
||||
11. Mode toggle closes any open picker.
|
||||
12. In the popup, all global color pickers use the custom picker.
|
||||
13. In the popup, per-subreddit color pickers use the custom picker.
|
||||
14. The picker renders correctly in dark mode.
|
||||
15. Color values round-trip correctly (pick → save → reopen → same position).
|
||||
Reference in New Issue
Block a user