Compare commits
14 Commits
5521e39a66
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
dc31d21104
|
|||
|
00507fe87c
|
|||
|
80689c6f8d
|
|||
|
e4093935b3
|
|||
|
c0a0c157f5
|
|||
|
a13eca930c
|
|||
|
0eefcfc6cf
|
|||
|
074d234883
|
|||
|
4856520c38
|
|||
|
d7094495d3
|
|||
|
b8ff27ef6e
|
|||
|
1c9d1e2705
|
|||
|
6e2aec39c9
|
|||
|
7b0c80c0a4
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{js,css,json,html}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.superpowers/
|
||||||
|
.DS_Store
|
||||||
|
*.xpi
|
||||||
|
web-ext-artifacts/
|
||||||
|
node_modules/
|
||||||
|
.env*
|
||||||
9
LICENSE
Normal file
9
LICENSE
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 cottongin (cottongin@cottongin.xyz)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Reddit Tweaks Firefox Extension
|
||||||
|
|
||||||
|
> **Note:** This project was built with the aid of [Cursor](https://cursor.com) and Claude.
|
||||||
|
|
||||||
|
This is a personal project, kept public for reference only. It will never be published to AMO (addons.mozilla.org). If you use it, expect to customize things to your own taste — it's opinionated by design.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
A Firefox extension that reskins [old.reddit.com](https://old.reddit.com) with a handful of visual tweaks:
|
||||||
|
|
||||||
|
- **Badge layout** — replaces the default subreddit link and comment count with styled badges
|
||||||
|
- **Card layout** — wraps posts in rounded cards with configurable density, spacing, and background style
|
||||||
|
- **Font override** — swaps in Reddit Sans (and Reddit Mono for code blocks) site-wide
|
||||||
|
- **Color customization** — per-subreddit badge colors via an inline HSV picker
|
||||||
|
- **Theming** — separate light/dark palettes with automatic RES night-mode detection
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> Extension menu (left), standard old.reddit.com (top right), with extension enabled (bottom right).
|
||||||
|
> RES (Reddit Enhancement Suite) was enabled during capture.
|
||||||
|
|
||||||
|
| View | Full-size image |
|
||||||
|
|---|---|
|
||||||
|
| Extension menu | [reddit-extension-popup.png](docs/reddit-extension-popup.png) |
|
||||||
|
| Without extension | [reddit-extension-disabled.png](docs/reddit-extension-disabled.png) |
|
||||||
|
| With extension | [reddit-extension-enabled.png](docs/reddit-extension-enabled.png) |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone or download this repo
|
||||||
|
2. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`
|
||||||
|
3. Click **Load Temporary Add-on...**
|
||||||
|
4. Select the `manifest.json` file from this repo's root
|
||||||
|
|
||||||
|
The extension will stay loaded until you close Firefox. Repeat step 2-4 after restart.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Click the extension icon in the toolbar to open the settings popup. All toggles, color pickers, density controls, and presets live there.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
Reddit Sans, Reddit Sans Condensed, and Reddit Mono are bundled under the [SIL Open Font License 1.1](content/fonts/OFL.txt). See that file for full license text and copyright info.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Extension code is [MIT](LICENSE). Bundled fonts are under the [SIL Open Font License 1.1](content/fonts/OFL.txt).
|
||||||
34
chat-summaries/2026-08-26_21-07-summary.md
Normal file
34
chat-summaries/2026-08-26_21-07-summary.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Card Layout Implementation — Subagent-Driven Execution
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Execute the [card layout implementation plan](../docs/superpowers/plans/2026-08-26-card-layout.md) using subagent-driven development. The plan converts old.reddit.com's flat post list into shadcn/ui-styled cards.
|
||||||
|
|
||||||
|
## Changes Made (6 commits: d659238..6e2aec3)
|
||||||
|
|
||||||
|
| Commit | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `7ac47b5` | Extract `isDarkMode`/`getColorPrefix` to `settings.js`; add card defaults |
|
||||||
|
| `8d3ff72` | Badge layout toggle + FOUC CSS rename (`rt-enabled` → `rt-badges-enabled`) |
|
||||||
|
| `95f0946` | Create `cards.css` with exact shadcn design tokens |
|
||||||
|
| `5521e39` | Create `cards.js` module + unified MutationObserver in `main.js` |
|
||||||
|
| `7b0c80c` | Add card layout controls to popup (toggle, background, spacing) |
|
||||||
|
| `6e2aec3` | Fix stale closures in dark-mode observer and storage listener |
|
||||||
|
|
||||||
|
### Files modified
|
||||||
|
- `lib/settings.js` — shared dark-mode utilities, new defaults
|
||||||
|
- `content/tweaks/badges.js` — removed duplicate observers, use shared utilities
|
||||||
|
- `content/tweaks/color-popover.js` — use shared utilities
|
||||||
|
- `content/tweaks/cards.js` — **new** card tweak module
|
||||||
|
- `content/styles/badges.css` — FOUC selector rename
|
||||||
|
- `content/styles/cards.css` — **new** card styling
|
||||||
|
- `content/main.js` — rewritten: unified observers, both tweaks, storage handler
|
||||||
|
- `popup/popup.html` — badge toggle + card settings section
|
||||||
|
- `popup/popup.js` — wiring for new controls
|
||||||
|
- `manifest.json` — add `cards.css`/`cards.js`, remove `www.reddit.com`
|
||||||
|
|
||||||
|
## Follow-up Items
|
||||||
|
|
||||||
|
- Manual smoke test in Firefox on old.reddit.com (badges, cards, dark mode, popup controls)
|
||||||
|
- Minor: `processNewPosts` signature asymmetry (badges use settings arg, cards ignore it)
|
||||||
|
- Minor: No FOUC prevention for cards (badges have it; cards flash briefly on load)
|
||||||
35
chat-summaries/2026-08-27_05-40-summary.md
Normal file
35
chat-summaries/2026-08-27_05-40-summary.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Reddit Fonts Override Feature
|
||||||
|
|
||||||
|
**Date:** 2026-08-27
|
||||||
|
**Task:** Add Reddit's modern fonts (Reddit Sans, Reddit Sans Condensed, Reddit Mono) to the Firefox extension, overriding old Reddit's default fonts.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### New Files
|
||||||
|
- `content/fonts/RedditSans[wght].woff2` — Reddit Sans variable font (~96KB)
|
||||||
|
- `content/fonts/RedditSansCondensed[wght].woff2` — Reddit Sans Condensed variable font (~94KB)
|
||||||
|
- `content/fonts/RedditMono[wght].woff2` — Reddit Mono variable font (~81KB)
|
||||||
|
- `content/fonts/OFL.txt` — SIL Open Font License
|
||||||
|
- `content/styles/fonts.css` — @font-face declarations and font-family overrides gated on `html.rt-fonts-enabled`
|
||||||
|
|
||||||
|
### Modified Files
|
||||||
|
- `manifest.json` — Added `content/styles/fonts.css` to CSS array
|
||||||
|
- `lib/settings.js` — Added `fontOverride: true` default
|
||||||
|
- `content/main.js` — Toggle `rt-fonts-enabled` class, reload on fontOverride change, removed debug console.log
|
||||||
|
- `popup/popup.html` — Added "Reddit fonts" checkbox
|
||||||
|
- `popup/popup.js` — Wired fontOverride checkbox (populate, save, listener)
|
||||||
|
|
||||||
|
### Design & Planning
|
||||||
|
- `docs/superpowers/specs/2026-08-27-reddit-fonts-design.md` — Design spec
|
||||||
|
- `docs/superpowers/plans/2026-08-27-reddit-fonts.md` — Implementation plan
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
- Fonts from [reddit/redditsans](https://github.com/reddit/redditsans), SIL OFL 1.1 licensed
|
||||||
|
- Variable-weight woff2 files (one per family, all weights via `wght` axis)
|
||||||
|
- CSS-only @font-face + class toggle pattern (matches existing card/badge approach)
|
||||||
|
- Reddit Sans applied to all body text; Reddit Mono to code/pre elements; Condensed declared but not force-applied
|
||||||
|
- Toggleable from popup, on by default
|
||||||
|
|
||||||
|
## Follow-up Items
|
||||||
|
- Manual Firefox testing recommended (load extension, verify fonts render, toggle on/off)
|
||||||
|
- Task 3 commit includes some unrelated pre-existing card-density changes (cosmetic, not a functional issue)
|
||||||
35
chat-summaries/2026-08-27_15-04-summary.md
Normal file
35
chat-summaries/2026-08-27_15-04-summary.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Score Ratio Badge — Subagent-Driven Implementation
|
||||||
|
|
||||||
|
**Date:** 2026-08-27 15:04
|
||||||
|
**Task:** Implement the Score Ratio Badge feature using Subagent-Driven Development
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### Task 1: Settings infrastructure + popup toggle (`dfaca51`)
|
||||||
|
- Added `scoreRatio: true` to DEFAULTS in `lib/settings.js`
|
||||||
|
- Added "Score ratio badge" checkbox to `popup/popup.html` in Card Layout section
|
||||||
|
- Wired `populateControls`, `saveFromControls`, and change listener in `popup/popup.js`
|
||||||
|
|
||||||
|
### Task 2: Font Awesome solid subset + ratio badge CSS (`93931a8`)
|
||||||
|
- Downloaded FA 6.7.2 solid woff2 to `content/fonts/fontawesome/fa-solid-900.woff2` (sha256 verified)
|
||||||
|
- Created `content/styles/fontawesome-scoped.css` — scoped FA selectors under `.rt-badge--ratio`
|
||||||
|
- Created `content/styles/ratio.css` — temperature-colored badges using CSS custom properties, dark mode via `.rt-dark`, neutral state, hover override
|
||||||
|
- Added both CSS files to manifest
|
||||||
|
|
||||||
|
### Task 3: Core ratio module + main.js integration (`2db30fd`)
|
||||||
|
- Created `content/tweaks/ratio.js` — IIFE module with score parsing (title attr, k/m suffixes, bullet detection), comment count parsing, ratio computation, HSL hue mapping, badge DOM injection
|
||||||
|
- Wired into `main.js` at all 5 integration points (init, post observer, dark mode observer, storage reload trigger, settings update)
|
||||||
|
- Added `ratio.js` to manifest JS array
|
||||||
|
|
||||||
|
## Review Results
|
||||||
|
|
||||||
|
- All 3 per-task reviews: Spec ✅, Approved
|
||||||
|
- Final whole-branch review: Ready to merge (no Critical/Important issues blocking)
|
||||||
|
- Minor findings noted: `data-rt-ratio-processed` set before insertion confirm, observer dependency on badgeLayout/cardLayout (accepted), accessibility context on badges
|
||||||
|
|
||||||
|
## Follow-up Items
|
||||||
|
|
||||||
|
- Consider `title` attribute on badges for tooltip/accessibility (e.g. "Score per comment: 12.7")
|
||||||
|
- Dark mode edge case: score-ratio-only mode (both layouts off) won't get `.rt-dark` class — document as unsupported or add shared dark-mode helper
|
||||||
|
- Move `data-rt-ratio-processed` to after successful insertion for minor hardening
|
||||||
|
- Doc naming drift: spec file is `vote-ratio-badge-design.md` but feature is "score ratio"
|
||||||
42
chat-summaries/2026-08-27_15-48-summary.md
Normal file
42
chat-summaries/2026-08-27_15-48-summary.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Recovery of Lost Uncommitted Changes
|
||||||
|
|
||||||
|
**Date:** 2026-08-27
|
||||||
|
**Task:** Restore working directory state after an overzealous `git reset --hard` that removed both the score ratio badge feature AND valuable uncommitted card UI polish work.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
During the score ratio badge brainstorming session, the user decided to remove that feature. A `git reset --hard` was used but it also wiped uncommitted changes to `content/styles/cards.css` (278 lines) and `content/tweaks/cards.js` (93 lines) that contained extensive card UI improvements from the fonts session (`c6ee4c29`).
|
||||||
|
|
||||||
|
## Recovery Process
|
||||||
|
|
||||||
|
1. Analyzed all agent transcripts in `/agent-transcripts/` to find Write/StrReplace operations
|
||||||
|
2. Initially recovered partial state from transcript `85d33c47` (card density, dark mode fixes)
|
||||||
|
3. User reported this was insufficient — missing voting pill, rank badge, theme, and layout changes
|
||||||
|
4. Found the bulk of missing work in transcript `c6ee4c29` (fonts session) — lines 78-227 contained extensive post-fonts card UI polishing
|
||||||
|
5. Used a subagent to extract the final file state from `c6ee4c29`: base Write at line 185 + 11 subsequent StrReplace operations for CSS; Write at line 187 for JS
|
||||||
|
6. Successfully reconstructed both files to their pre-reset state
|
||||||
|
|
||||||
|
## Recovered Features (in cards.css)
|
||||||
|
|
||||||
|
- Voting pill (`.midcol`) with capsule styling and border-radius per density
|
||||||
|
- Rank badge watermark (`.rt-rank-badge`) anchored top-left
|
||||||
|
- Custom selection system (`rt-selected`) replacing Reddit/RES classes
|
||||||
|
- Voted pill tinting: orange for upvoted, purple for downvoted
|
||||||
|
- Deselected pill dim (opacity: 0.45) / selected vibrant (opacity: 1)
|
||||||
|
- Opposing arrow hue matching with reduced opacity
|
||||||
|
- Dark mode variants for all selection/voted states
|
||||||
|
- Bold post titles
|
||||||
|
- Shadcn page backgrounds (light + dark)
|
||||||
|
|
||||||
|
## Recovered Features (in cards.js)
|
||||||
|
|
||||||
|
- Rank badge DOM repositioning with `rt-rank-badge` class
|
||||||
|
- `initSelection()` — mousedown-based `rt-selected` class toggle
|
||||||
|
- `applyDensity()` — density class management
|
||||||
|
- Click-outside deselection
|
||||||
|
|
||||||
|
## Final State
|
||||||
|
|
||||||
|
- `cards.css`: 278 lines (+254/-22 vs committed)
|
||||||
|
- `cards.js`: 93 lines (+33 vs committed)
|
||||||
|
- Git status matches conversation-start state
|
||||||
42
chat-summaries/2026-08-28_00-27-summary.md
Normal file
42
chat-summaries/2026-08-28_00-27-summary.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Chat Summary — 2026-08-28 00:27
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add project housekeeping files: LICENSE, README, VERSION, .editorconfig, expanded .gitignore. Sync manifest version. Set remote and push.
|
||||||
|
|
||||||
|
## Preceded by
|
||||||
|
|
||||||
|
- Sensitive information audit (no issues found beyond expected `cottongin` in git history)
|
||||||
|
- Plan mode discussion to scope housekeeping items and branding conventions
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
| File | Action | Details |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `LICENSE` | Created | MIT license, copyright cottongin 2026 |
|
||||||
|
| `README.md` | Created | Terse/casual, Cursor/Claude notice, personal project disclaimer, features, install via about:debugging |
|
||||||
|
| `VERSION` | Created | `0.1.0` |
|
||||||
|
| `.gitignore` | Expanded | Added .DS_Store, *.xpi, web-ext-artifacts/, node_modules/, .env* |
|
||||||
|
| `.editorconfig` | Created | 2-space indent for JS/CSS/JSON/HTML, UTF-8, LF, trim whitespace |
|
||||||
|
| `manifest.json` | Modified | Version synced from 1.0.0 to 0.1.0 |
|
||||||
|
|
||||||
|
## Commit
|
||||||
|
|
||||||
|
`c0a0c15` — `chore: add LICENSE, README, VERSION, .editorconfig, expand .gitignore`
|
||||||
|
|
||||||
|
Also staged 6 previously untracked files (4 chat summaries, 2 score-ratio docs).
|
||||||
|
|
||||||
|
## Remote
|
||||||
|
|
||||||
|
Set origin to `git@code.cottongin.xyz:cottongin/reddit-tweaks-ffe.git` and pushed master.
|
||||||
|
|
||||||
|
## Branding Reference
|
||||||
|
|
||||||
|
- Full: "Reddit Tweaks Firefox Extension"
|
||||||
|
- Abbreviation: "RTFFE"
|
||||||
|
- Short: "Reddit Tweaks FFE"
|
||||||
|
- Casual: "Reddit Tweaks" / "the extension"
|
||||||
|
|
||||||
|
## Follow-up Items
|
||||||
|
|
||||||
|
- None
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Thermo-Nuclear Code Quality Review — Reddit Tweaks Extension
|
||||||
|
|
||||||
|
**Date:** 2026-08-28 01:08
|
||||||
|
**Task:** Deep code quality audit of the full codebase using thermo-nuclear review criteria
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
Not approved — several structural regressions and missed simplification opportunities identified.
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### Critical
|
||||||
|
|
||||||
|
1. **~200 lines of CSS duplicated** between `content/styles/badges.css` (lines 409–611) and `popup/popup.css` (lines 287–489). Color picker widget and preset badge styles are copy-pasted verbatim. Fix: extract shared `color-picker.css`.
|
||||||
|
|
||||||
|
2. **`badges.css` (611 lines) conflates three concerns** — badge styling (~85 lines), color popover chrome (~230 lines), and color picker widget (~150 lines). Should be decomposed into separate files.
|
||||||
|
|
||||||
|
### High
|
||||||
|
|
||||||
|
3. **Preset management logic duplicated** across `popup.js` (`renderPopupPresets`, `addPopupPreset`, `deletePopupPreset`) and `color-popover.js` (`renderPresets`, `saveAsPreset`, `removePreset`). Nearly identical CRUD pattern. Fix: extract shared `presets.js` module.
|
||||||
|
|
||||||
|
4. **`colorToHex` identity function duplicated** in both `popup.js` and `color-popover.js`.
|
||||||
|
|
||||||
|
### Medium
|
||||||
|
|
||||||
|
5. **Dark mode class management scattered** across 4 files with inconsistent scoping — `cards.js` targets `<html>`, `badges.js` targets each container, `color-popover.js` targets itself. Code-judo: apply `rt-dark` once on `<html>` and let CSS cascade handle the rest.
|
||||||
|
|
||||||
|
6. **DOM expando properties** (`entry._colorState`, `entry._nameInput`, etc.) used as state management in `popup.js`. Should use plain objects.
|
||||||
|
|
||||||
|
7. **Stale closure** in `startPostObserver` — line 47 uses captured `settings` instead of freshly-loaded `updated`.
|
||||||
|
|
||||||
|
### Low
|
||||||
|
|
||||||
|
8. **Dead exports** (`isDarkMode`, `getColorPrefix`) on `badgeTweak` object.
|
||||||
|
9. **Verbose storage change listener** — four repetitive conditions should be data-driven.
|
||||||
|
10. **`getHelpers()` wrapper** in `color-popover.js` adds pointless indirection.
|
||||||
|
|
||||||
|
## Recommended Priority Order
|
||||||
|
|
||||||
|
1. Extract shared `color-picker.css` (~200 lines deleted)
|
||||||
|
2. Split `badges.css` into badges, popover, picker
|
||||||
|
3. Extract shared preset module
|
||||||
|
4. Unify dark mode to single `html.rt-dark` class
|
||||||
|
5. Fix stale closure bug
|
||||||
|
6. Clean up dead exports, verbose conditions, and unnecessary wrappers
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
|
||||||
|
- The thermo-nuclear review skill is useful for catching CSS duplication that would otherwise grow silently — CSS files don't get the same refactoring attention as JS.
|
||||||
|
- Browser extension architecture (content scripts vs popup) makes code sharing harder since there's no module system, but shared files loaded via both manifest.json and popup.html `<script>`/`<link>` tags work fine.
|
||||||
|
- IIFE-scoped modules on a global namespace are a reasonable pattern for small extensions but make duplication easy to miss since there's no import graph to inspect.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Thermo-Nuclear Code Quality Refactor — Reddit Tweaks Extension
|
||||||
|
|
||||||
|
**Date:** 2026-08-28 01:23
|
||||||
|
**Task:** Implement all 10 findings from the thermo-nuclear code quality review
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. CSS Deduplication — Extracted shared `color-picker.css` (NEW FILE)
|
||||||
|
- Created `content/styles/color-picker.css` (259 lines) containing color picker widget styles and preset badge styles
|
||||||
|
- This file is now shared between the content script (via manifest.json) and the popup (via `<link>` tag)
|
||||||
|
- Eliminated ~200 lines of identical CSS that was previously duplicated between `badges.css` and `popup.css`
|
||||||
|
|
||||||
|
### 2. CSS Decomposition — Extracted `color-popover.css` (NEW FILE)
|
||||||
|
- Created `content/styles/color-popover.css` (256 lines) with popover chrome, buttons, dark mode
|
||||||
|
- Trimmed `badges.css` from 611 lines to 85 lines (badges-only)
|
||||||
|
- Trimmed `popup.css` from 489 lines to 220 lines (popup-specific only)
|
||||||
|
- Updated dark mode selectors from `.rt-color-popover.rt-dark` to `.rt-dark .rt-color-popover` (ancestor form)
|
||||||
|
|
||||||
|
### 3. Shared Preset Module — Extracted `lib/presets.js` (NEW FILE)
|
||||||
|
- Created `lib/presets.js` (57 lines) with `render()`, `save()`, `delete()` functions
|
||||||
|
- Refactored `popup.js` and `color-popover.js` to use shared module
|
||||||
|
- Eliminated ~60 lines of duplicated preset rendering and CRUD logic
|
||||||
|
|
||||||
|
### 4. Dark Mode Unification
|
||||||
|
- Added `applyDarkModeClass(settings)` to `lib/settings.js`
|
||||||
|
- Removed per-container dark class toggling from `badges.js` `applyColorsToContainer()`
|
||||||
|
- Removed `applyDarkMode()` from `cards.js` (was redundant with shared function)
|
||||||
|
- Removed `applyDarkModeToPopover()` and `refreshTheme()` from `color-popover.js`
|
||||||
|
- `main.js` now calls `applyDarkModeClass()` centrally in init, dark-mode observer, and storage listener
|
||||||
|
|
||||||
|
### 5. Stale Closure Fix
|
||||||
|
- Fixed `startPostObserver` in `main.js` to use freshly-loaded `updated` settings instead of stale captured `settings`
|
||||||
|
|
||||||
|
### 6. Data-Driven Storage Listener
|
||||||
|
- Replaced 4 verbose `changes.X && changes.X.newValue !== ...` conditions with `RELOAD_KEYS.some()`
|
||||||
|
|
||||||
|
### 7. Dead Export Cleanup
|
||||||
|
- Removed `isDarkMode` and `getColorPrefix` re-exports from `badgeTweak` object in `badges.js`
|
||||||
|
|
||||||
|
### 8. Deleted `getHelpers()` Wrapper
|
||||||
|
- Removed unnecessary indirection in `color-popover.js`, replaced with direct `window.RedditTweaks.*` calls
|
||||||
|
|
||||||
|
### 9. Inlined `colorToHex`
|
||||||
|
- Removed duplicated `colorToHex` function from both `popup.js` and `color-popover.js`
|
||||||
|
- Replaced all call sites with inline `val || "#ffffff"`
|
||||||
|
|
||||||
|
### 10. DOM Expando Refactor
|
||||||
|
- Replaced `entry._colorState`, `entry._nameInput`, etc. DOM expando pattern in `popup.js`
|
||||||
|
- Introduced `subredditEntries` array of plain state objects
|
||||||
|
- `collectSubredditOverrides()` now iterates the state array instead of querying the DOM
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
- `content/main.js` — stale closure fix, data-driven reload keys, central dark mode calls
|
||||||
|
- `content/tweaks/badges.js` — removed dark class toggle, dead exports, refreshTheme call
|
||||||
|
- `content/tweaks/cards.js` — removed applyDarkMode, use shared function
|
||||||
|
- `content/tweaks/color-popover.js` — removed getHelpers, colorToHex, applyDarkModeToPopover, refreshTheme, preset duplication
|
||||||
|
- `lib/settings.js` — added applyDarkModeClass
|
||||||
|
- `popup/popup.js` — inlined colorToHex, used shared presets, state array instead of expandos
|
||||||
|
- `popup/popup.css` — trimmed to popup-only styles
|
||||||
|
- `content/styles/badges.css` — trimmed to badge-only styles (611 → 85 lines)
|
||||||
|
- `manifest.json` — added new CSS and JS files
|
||||||
|
- `popup/popup.html` — added links to new CSS and JS files
|
||||||
|
|
||||||
|
## New Files
|
||||||
|
- `content/styles/color-picker.css` — shared picker + preset badge styles
|
||||||
|
- `content/styles/color-popover.css` — popover chrome styles
|
||||||
|
- `lib/presets.js` — shared preset CRUD module
|
||||||
|
|
||||||
|
## Net Impact
|
||||||
|
- **Before:** 3,180 lines across 12 source files
|
||||||
|
- **After:** 2,862 lines across 15 source files
|
||||||
|
- **Deleted:** ~318 lines of duplication and dead code
|
||||||
|
- No file exceeds 401 lines (previously badges.css was 611)
|
||||||
|
- Dark mode is now managed from a single source instead of 4 scattered functions
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
- CSS duplication across content script and popup contexts is easy to miss because there's no import graph. Shared CSS files referenced by both manifest.json and popup.html `<link>` work well.
|
||||||
|
- Dark mode class management is a natural candidate for centralization — scattered per-element toggling creates CSS selector complexity that cascading from a single ancestor eliminates.
|
||||||
|
- DOM expando properties work but are an anti-pattern that makes code harder to reason about. A separate state array with DOM references is only marginally more code but significantly clearer.
|
||||||
|
- The `colorToHex` "conversion" function was really just null-coalescing — renaming or inlining it makes the intent obvious.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# README Screenshots
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add a Screenshots section to the README featuring a composed collage hero image and links to each full-size original screenshot.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### New files
|
||||||
|
- `docs/make_collage.py` -- Python/Pillow script that composes the three source screenshots into an 800x600 collage image (popup left, disabled top-right, enabled bottom-right, light gray background, 4px gaps).
|
||||||
|
- `docs/reddit-extension-collage.png` -- the generated collage output.
|
||||||
|
|
||||||
|
### Modified files
|
||||||
|
- `README.md` -- added a "Screenshots" section between "What it does" and "Installation" containing the collage image, a blockquote caption describing the layout and noting RES was active during capture, and a table linking to each full-size original.
|
||||||
|
|
||||||
|
## Source screenshots
|
||||||
|
- `docs/reddit-extension-popup.png` (645x2757) -- captured by user by opening `moz-extension://<uuid>/popup/popup.html` in a Firefox tab
|
||||||
|
- `docs/reddit-extension-disabled.png` (2794x1671) -- old.reddit.com without extension
|
||||||
|
- `docs/reddit-extension-enabled.png` (2794x1671) -- old.reddit.com with extension
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
- Firefox extension popups close on focus loss. Opening the popup HTML directly via `moz-extension://` URL in a regular tab is the simplest workaround for full-page captures.
|
||||||
|
- GitHub-flavored markdown cannot crop images, does not honor `rowspan`, and strips most CSS. A pre-composed collage image is the only way to get a multi-panel layout.
|
||||||
|
- Pillow's `Image.LANCZOS` resampling produces clean downscaled screenshots even at significant reduction ratios (e.g., 2794px down to ~546px).
|
||||||
|
|
||||||
|
## Follow-up
|
||||||
|
- The `docs/make_collage.py` script can be re-run if source screenshots are updated.
|
||||||
|
- Consider adding the collage script or output to `.gitignore` if the generated image shouldn't be tracked separately from sources.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# README Screenshots -- Final
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add a Screenshots section to the README with a composed collage hero image and links to full-size originals.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### New files
|
||||||
|
- `docs/reddit-extension-collage.png` -- 1200x900 (4:3) composed collage of all three screenshots.
|
||||||
|
- `docs/make_collage.py` -- Pillow script that generates the collage. Configurable aspect ratio, max dimension, gradient, borders, and drop shadows.
|
||||||
|
- `docs/reddit-extension-popup.png` -- full-page capture of the extension popup (captured manually via `moz-extension://` URL in a tab).
|
||||||
|
- `docs/reddit-extension-disabled.png` -- old.reddit.com without the extension.
|
||||||
|
- `docs/reddit-extension-enabled.png` -- old.reddit.com with the extension active.
|
||||||
|
|
||||||
|
### Modified files
|
||||||
|
- `README.md` -- added Screenshots section between "What it does" and "Installation". Contains the collage hero image, a blockquote caption, and a table linking to each full-size original.
|
||||||
|
|
||||||
|
## Collage Details
|
||||||
|
|
||||||
|
Final collage configuration after iterating through multiple options:
|
||||||
|
- **Layout**: popup on left, disabled screenshot top-right, enabled screenshot bottom-right
|
||||||
|
- **Aspect ratio**: 4:3, clamped to 1200px max dimension (chosen from 5:4, 4:3, 3:2, 16:10, 16:9, 6:5, 7:6 variants)
|
||||||
|
- **Background**: 45-degree linear gradient from `#888888` to `#575757`
|
||||||
|
- **Panel borders**: `#111111`, 1px, 4px corner radius
|
||||||
|
- **Drop shadows**: offset (4,4), blur 8, rgba(0,0,0,0.43), cast bottom-right to complement the gradient angle
|
||||||
|
- **Padding**: 20px outer, 14px gaps between panels (scaled proportionally when clamped)
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
- Firefox extension popups close on focus loss. Opening `moz-extension://<uuid>/popup/popup.html` in a regular tab is the simplest workaround for full-page captures.
|
||||||
|
- GitHub-flavored markdown cannot crop images, strips `rowspan`, and removes CSS. A pre-composed collage image is the only way to get a multi-panel layout in a README.
|
||||||
|
- Iterating on visual output (colors, ratios, effects) works well as a rapid generate-and-review loop with Pillow.
|
||||||
|
- Generating multiple aspect ratio variants at once and letting the user compare side-by-side is faster than guessing one at a time.
|
||||||
|
|
||||||
|
## Follow-up
|
||||||
|
- Run `python docs/make_collage.py` to regenerate the collage if source screenshots are updated.
|
||||||
106
content/fonts/OFL.txt
Normal file
106
content/fonts/OFL.txt
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
Reddit Sans
|
||||||
|
Copyright © 2020-23, Reddit, Inc. (https://github.com/reddit/redditsans)
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org/
|
||||||
|
|
||||||
|
Reddit Sans Condensed
|
||||||
|
Copyright © 2020-23, Reddit, Inc. (https://github.com/reddit/redditsans)
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org/
|
||||||
|
|
||||||
|
Reddit Mono
|
||||||
|
Copyright © 2020-23, Reddit, Inc. (https://github.com/reddit/redditsans)
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SIL OPEN FONT LICENSE
|
||||||
|
|
||||||
|
Version 1.1 - 26 February 2007
|
||||||
|
|
||||||
|
### PREAMBLE
|
||||||
|
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide development
|
||||||
|
of collaborative font projects, to support the font creation efforts of academic
|
||||||
|
and linguistic communities, and to provide a free and open framework in which
|
||||||
|
fonts may be shared and improved in partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The fonts,
|
||||||
|
including any derivative works, can be bundled, embedded, redistributed and/or
|
||||||
|
sold with any software provided that any reserved names are not used by
|
||||||
|
derivative works. The fonts and derivatives, however, cannot be released under
|
||||||
|
any other type of license. The requirement for fonts to remain under this
|
||||||
|
license does not apply to any document created using the fonts or their
|
||||||
|
derivatives.
|
||||||
|
|
||||||
|
### DEFINITIONS
|
||||||
|
|
||||||
|
"Font Software" refers to the set of files released by the Copyright Holder(s)
|
||||||
|
under this license and clearly marked as such. This may include source files,
|
||||||
|
build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the copyright
|
||||||
|
statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting, or
|
||||||
|
substituting — in part or in whole — any of the components of the Original
|
||||||
|
Version, by changing formats or by porting the Font Software to a new
|
||||||
|
environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical writer or other
|
||||||
|
person who contributed to the Font Software.
|
||||||
|
|
||||||
|
### PERMISSION & CONDITIONS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
the Font Software, to use, study, copy, merge, embed, modify, redistribute, and
|
||||||
|
sell modified and unmodified copies of the Font Software, subject to the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
1. Neither the Font Software nor any of its individual components, in Original
|
||||||
|
or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2. Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy contains
|
||||||
|
the above copyright notice and this license. These can be included either as
|
||||||
|
stand-alone text files, human-readable headers or in the appropriate
|
||||||
|
machine-readable metadata fields within text or binary files as long as those
|
||||||
|
fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3. No Modified Version of the Font Software may use the Reserved Font Name(s)
|
||||||
|
unless explicit written permission is granted by the corresponding Copyright
|
||||||
|
Holder. This restriction only applies to the primary font name as presented
|
||||||
|
to the users.
|
||||||
|
|
||||||
|
4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software
|
||||||
|
shall not be used to promote, endorse or advertise any Modified Version,
|
||||||
|
except to acknowledge the contribution(s) of the Copyright Holder(s) and the
|
||||||
|
Author(s) or with their explicit written permission.
|
||||||
|
|
||||||
|
5. The Font Software, modified or unmodified, in part or in whole, must be
|
||||||
|
distributed entirely under this license, and must not be distributed under
|
||||||
|
any other license. The requirement for fonts to remain under this license
|
||||||
|
does not apply to any document created using the Font Software.
|
||||||
|
|
||||||
|
### TERMINATION
|
||||||
|
|
||||||
|
This license becomes null and void if any of the above conditions are not met.
|
||||||
|
|
||||||
|
### DISCLAIMER
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR
|
||||||
|
OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR
|
||||||
|
FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
BIN
content/fonts/RedditMono[wght].woff2
Normal file
BIN
content/fonts/RedditMono[wght].woff2
Normal file
Binary file not shown.
BIN
content/fonts/RedditSansCondensed[wght].woff2
Normal file
BIN
content/fonts/RedditSansCondensed[wght].woff2
Normal file
Binary file not shown.
BIN
content/fonts/RedditSans[wght].woff2
Normal file
BIN
content/fonts/RedditSans[wght].woff2
Normal file
Binary file not shown.
@@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
function startDarkModeObserver(settings) {
|
function startDarkModeObserver(settings) {
|
||||||
var observer = new MutationObserver(function () {
|
var observer = new MutationObserver(function () {
|
||||||
if (settings.darkMode === "auto") {
|
window.RedditTweaks.loadSettings().then(function (updated) {
|
||||||
window.RedditTweaks.loadSettings().then(function (updated) {
|
if (updated.darkMode === "auto") {
|
||||||
if (settings.badgeLayout) {
|
window.RedditTweaks.applyDarkModeClass(updated);
|
||||||
|
if (updated.badgeLayout) {
|
||||||
window.RedditTweaks.badgeTweak.updateColors(updated);
|
window.RedditTweaks.badgeTweak.updateColors(updated);
|
||||||
}
|
}
|
||||||
if (settings.cardLayout) {
|
if (updated.cardLayout) {
|
||||||
window.RedditTweaks.cardTweak.updateSettings(updated);
|
window.RedditTweaks.cardTweak.updateSettings(updated);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
@@ -44,10 +45,10 @@
|
|||||||
}
|
}
|
||||||
if (hasNewPosts) {
|
if (hasNewPosts) {
|
||||||
window.RedditTweaks.loadSettings().then(function (updated) {
|
window.RedditTweaks.loadSettings().then(function (updated) {
|
||||||
if (settings.badgeLayout) {
|
if (updated.badgeLayout) {
|
||||||
window.RedditTweaks.badgeTweak.processNewPosts(updated);
|
window.RedditTweaks.badgeTweak.processNewPosts(updated);
|
||||||
}
|
}
|
||||||
if (settings.cardLayout) {
|
if (updated.cardLayout) {
|
||||||
window.RedditTweaks.cardTweak.processNewPosts(updated);
|
window.RedditTweaks.cardTweak.processNewPosts(updated);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -58,42 +59,54 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const settings = await window.RedditTweaks.loadSettings();
|
try {
|
||||||
if (!settings.enabled) return;
|
var settings = await window.RedditTweaks.loadSettings();
|
||||||
|
if (!settings.enabled) return;
|
||||||
|
|
||||||
if (settings.badgeLayout) {
|
window.RedditTweaks.applyDarkModeClass(settings);
|
||||||
document.documentElement.classList.add("rt-badges-enabled");
|
|
||||||
window.RedditTweaks.badgeTweak.apply(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.cardLayout) {
|
if (settings.badgeLayout) {
|
||||||
window.RedditTweaks.cardTweak.apply(settings);
|
document.documentElement.classList.add("rt-badges-enabled");
|
||||||
}
|
window.RedditTweaks.badgeTweak.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.fontOverride) {
|
||||||
if (settings.badgeLayout) {
|
document.documentElement.classList.add("rt-fonts-enabled");
|
||||||
window.RedditTweaks.badgeTweak.updateColors(updated);
|
}
|
||||||
}
|
|
||||||
if (settings.cardLayout) {
|
if (settings.cardLayout) {
|
||||||
window.RedditTweaks.cardTweak.updateSettings(updated);
|
window.RedditTweaks.cardTweak.apply(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.badgeLayout || settings.cardLayout) {
|
||||||
|
startPostObserver(settings);
|
||||||
|
startDarkModeObserver(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
var RELOAD_KEYS = ["enabled", "badgeLayout", "fontOverride", "cardLayout"];
|
||||||
|
|
||||||
|
browser.storage.onChanged.addListener(function (changes) {
|
||||||
|
var needsReload = RELOAD_KEYS.some(function (key) {
|
||||||
|
return changes[key] && changes[key].newValue !== changes[key].oldValue;
|
||||||
|
});
|
||||||
|
if (needsReload) {
|
||||||
|
location.reload();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.RedditTweaks.loadSettings().then(function (updated) {
|
||||||
|
window.RedditTweaks.applyDarkModeClass(updated);
|
||||||
|
if (updated.badgeLayout) {
|
||||||
|
window.RedditTweaks.badgeTweak.updateColors(updated);
|
||||||
|
}
|
||||||
|
if (updated.cardLayout) {
|
||||||
|
window.RedditTweaks.cardTweak.updateSettings(updated);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} catch (err) {
|
||||||
|
console.error("RedditTweaks init failed:", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -83,529 +83,3 @@ html.rt-badges-enabled .thing.link .flat-list li:has(> .comments) {
|
|||||||
.rt-badge-gear:hover {
|
.rt-badge-gear:hover {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Color popover --- */
|
|
||||||
.rt-color-popover {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10000;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
|
|
||||||
padding: 12px;
|
|
||||||
width: 260px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #1a1a1a;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-popover-visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-close {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #999;
|
|
||||||
padding: 0 2px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-close:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-mode {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-mode select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 3px 6px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row-label {
|
|
||||||
font-size: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row-inputs {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row .rt-color-swatch {
|
|
||||||
width: 28px;
|
|
||||||
height: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row input[type="text"] {
|
|
||||||
width: 70px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-row input[type="text"].rt-input-error {
|
|
||||||
border-color: #d9534f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-preview {
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 6px 0;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 8px;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-popover-footer-right {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-save {
|
|
||||||
background: #4a90d9;
|
|
||||||
color: #fff;
|
|
||||||
border: 1px solid #3a7bc8;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-save:hover {
|
|
||||||
background: #3a7bc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-cancel {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-cancel:hover {
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-reset {
|
|
||||||
background: none;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-btn-reset:hover {
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Popover dark mode --- */
|
|
||||||
.rt-color-popover.rt-dark {
|
|
||||||
background: #2a2a2a;
|
|
||||||
border-color: #555;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-header {
|
|
||||||
border-bottom-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-close {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-close:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-mode select {
|
|
||||||
background: #333;
|
|
||||||
border-color: #555;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-row .rt-color-swatch {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-row input[type="text"] {
|
|
||||||
background: #333;
|
|
||||||
border-color: #555;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-preview {
|
|
||||||
border-top-color: #444;
|
|
||||||
border-bottom-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-btn-save {
|
|
||||||
background: #4a90d9;
|
|
||||||
border-color: #3a7bc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-btn-cancel {
|
|
||||||
background: #444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-btn-reset {
|
|
||||||
border-color: #555;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-btn-reset:hover {
|
|
||||||
background: #333;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Preset badges --- */
|
|
||||||
.rt-popover-presets {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1.4;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-delete {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background: #d9534f;
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 14px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge:hover .rt-preset-delete {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-add {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border: 1px dashed #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: none;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-add:hover {
|
|
||||||
border-color: #999;
|
|
||||||
color: #666;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-popover-presets {
|
|
||||||
border-top-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-preset-add {
|
|
||||||
border-color: #555;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-color-popover.rt-dark .rt-preset-add:hover {
|
|
||||||
border-color: #888;
|
|
||||||
color: #ccc;
|
|
||||||
background: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Color Picker Widget --- */
|
|
||||||
.rt-color-picker {
|
|
||||||
margin: 8px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab {
|
|
||||||
flex: 1;
|
|
||||||
padding: 4px 8px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab--active {
|
|
||||||
color: #1a1a1a;
|
|
||||||
border-bottom-color: #4a90d9;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-panel {
|
|
||||||
max-height: 180px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid--palette {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch:hover {
|
|
||||||
border-color: rgba(0, 0, 0, 0.4);
|
|
||||||
transform: scale(1.2);
|
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch--selected {
|
|
||||||
border: 2px solid #1a1a1a;
|
|
||||||
box-shadow: 0 0 0 1px white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid--named {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-item .rt-picker-swatch {
|
|
||||||
width: 24px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-label {
|
|
||||||
font-size: 8px;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 100%;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-panel--custom {
|
|
||||||
max-height: none;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv-wrap {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv-cursor {
|
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border: 2px solid white;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-hue {
|
|
||||||
position: relative;
|
|
||||||
height: 14px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000);
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-hue-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: -1px;
|
|
||||||
width: 2px;
|
|
||||||
height: calc(100% + 2px);
|
|
||||||
background: white;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Picker dark mode */
|
|
||||||
.rt-dark .rt-picker-tabs,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tabs {
|
|
||||||
border-bottom-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab:hover,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab--active,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab--active {
|
|
||||||
color: #e0e0e0;
|
|
||||||
border-bottom-color: #4a90d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch {
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch:hover,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch--selected,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch--selected {
|
|
||||||
border-color: #e0e0e0;
|
|
||||||
box-shadow: 0 0 0 1px #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-named-label,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-named-label {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-sv,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-sv {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-hue,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-hue {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/* --- Post list container --- */
|
/* --- Post list container --- */
|
||||||
html.rt-card-enabled #siteTable {
|
html.rt-card-enabled #siteTable {
|
||||||
display: flex;
|
padding-left: 8px;
|
||||||
flex-direction: column;
|
padding-right: 6px;
|
||||||
align-items: flex-start;
|
box-sizing: border-box;
|
||||||
gap: var(--rt-card-gap, 1rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rt-card-enabled #siteTable > .clearleft {
|
html.rt-card-enabled #siteTable > .clearleft {
|
||||||
@@ -12,26 +11,110 @@ html.rt-card-enabled #siteTable > .clearleft {
|
|||||||
|
|
||||||
/* --- Card container --- */
|
/* --- Card container --- */
|
||||||
.rt-card {
|
.rt-card {
|
||||||
background: #ffffff;
|
position: relative;
|
||||||
|
background: #ffffff !important;
|
||||||
color: #0a0a0a;
|
color: #0a0a0a;
|
||||||
border: 1px solid #e5e5e5;
|
border: 1px solid #e5e5e5 !important;
|
||||||
border-radius: 0.875rem;
|
border-radius: 0.875rem;
|
||||||
padding: 1.5rem;
|
padding: 1rem;
|
||||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
transition: border-color 0.15s;
|
transition: border-color 0.15s, background-color 0.15s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
width: 100%;
|
margin-bottom: var(--rt-card-gap, 1rem);
|
||||||
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-card:hover {
|
.rt-card:hover {
|
||||||
border-color: #d4d4d4;
|
border-color: #d4d4d4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Card density --- */
|
||||||
|
html.rt-density-cozy .rt-card {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
html.rt-density-cozy .rt-card .midcol {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-density-modern .rt-card {
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
html.rt-density-modern .rt-card .midcol {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-density-extra .rt-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
border-radius: 0.875rem;
|
||||||
|
}
|
||||||
|
html.rt-density-extra .rt-card .midcol {
|
||||||
|
border-radius: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Rank badge: watermark matching card border color --- */
|
||||||
|
.rt-card .rt-rank-badge {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 4px !important;
|
||||||
|
left: 6px !important;
|
||||||
|
float: none !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
width: auto !important;
|
||||||
|
font-size: 9px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
color: #e5e5e5 !important;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-dark .rt-card .rt-rank-badge {
|
||||||
|
color: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Voting pill: interactive capsule --- */
|
||||||
|
.rt-card .midcol {
|
||||||
|
float: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
border-radius: 0.875rem;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-card .midcol .score {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-dark .rt-card .midcol {
|
||||||
|
background: #262626;
|
||||||
|
border-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Post title --- */
|
||||||
|
.rt-card .entry a.title {
|
||||||
|
font-weight: 700 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Card internal layout overrides --- */
|
/* --- Card internal layout overrides --- */
|
||||||
.rt-card .midcol,
|
|
||||||
.rt-card .thumbnail {
|
.rt-card .thumbnail {
|
||||||
float: none;
|
float: none;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -46,22 +129,149 @@ html.rt-card-enabled #siteTable > .clearleft {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Card dark mode --- */
|
/* --- Suppress Reddit/RES selection visuals unconditionally --- */
|
||||||
.rt-dark .rt-card {
|
html.rt-card-enabled .rt-card.last-clicked,
|
||||||
background: #171717;
|
html.rt-card-enabled .rt-card.res-selected,
|
||||||
|
html.rt-card-enabled .rt-card.RES-keyNav-activeElement {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card .entry.res-selected,
|
||||||
|
html.rt-card-enabled .rt-card .entry.RES-keyNav-activeElement {
|
||||||
|
background: transparent !important;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Our selection: .rt-selected (light) --- */
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected {
|
||||||
|
border: 1px solid #c5cdf0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
background: #f0f3fc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .rt-rank-badge {
|
||||||
|
color: #c5cdf0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .midcol {
|
||||||
|
background: #eff7ff;
|
||||||
|
border-color: #c5cdf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .midcol .score.unvoted {
|
||||||
|
color: #336699 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .midcol.unvoted .arrow {
|
||||||
|
filter: brightness(0) saturate(100%) invert(35%) sepia(30%) saturate(1000%) hue-rotate(175deg) brightness(90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Voted pill: tint + dim when deselected --- */
|
||||||
|
html.rt-card-enabled .rt-card .midcol.likes {
|
||||||
|
background: rgba(255, 139, 96, 0.12) !important;
|
||||||
|
border-color: #ff8b60 !important;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card .midcol.dislikes {
|
||||||
|
background: rgba(148, 148, 255, 0.12) !important;
|
||||||
|
border-color: #9494ff !important;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Opposing arrow: tint to match voted arrow, kept secondary */
|
||||||
|
html.rt-card-enabled .rt-card .midcol.likes .arrow.down {
|
||||||
|
filter: brightness(0) saturate(100%) invert(55%) sepia(90%) saturate(700%) hue-rotate(340deg);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card .midcol.dislikes .arrow.up {
|
||||||
|
filter: brightness(0) saturate(100%) invert(55%) sepia(70%) saturate(1200%) hue-rotate(210deg);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Voted pill: vibrant when selected --- */
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .midcol.likes {
|
||||||
|
background: rgba(255, 139, 96, 0.12) !important;
|
||||||
|
border-color: #ff8b60 !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled .rt-card.rt-selected .midcol.dislikes {
|
||||||
|
background: rgba(148, 148, 255, 0.12) !important;
|
||||||
|
border-color: #9494ff !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Our selection: .rt-selected (dark) --- */
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card.rt-selected {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||||
|
outline: none !important;
|
||||||
|
background: #2a2a3a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card.rt-selected .rt-rank-badge {
|
||||||
|
color: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card.rt-selected .midcol {
|
||||||
|
background: #333345;
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Voted pill: dim (deselected, dark) --- */
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card .midcol.likes {
|
||||||
|
background: rgba(255, 139, 96, 0.06) !important;
|
||||||
|
border-color: rgba(255, 139, 96, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card .midcol.dislikes {
|
||||||
|
background: rgba(148, 148, 255, 0.06) !important;
|
||||||
|
border-color: rgba(148, 148, 255, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Voted pill: vibrant (selected, dark) --- */
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card.rt-selected .midcol.likes {
|
||||||
|
background: rgba(255, 139, 96, 0.15) !important;
|
||||||
|
border-color: rgba(255, 139, 96, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-card-enabled.rt-dark .rt-card.rt-selected .midcol.dislikes {
|
||||||
|
background: rgba(148, 148, 255, 0.15) !important;
|
||||||
|
border-color: rgba(148, 148, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Card dark mode (base) --- */
|
||||||
|
html.rt-dark .rt-card {
|
||||||
|
background: #171717 !important;
|
||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
border-color: rgba(255, 255, 255, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-dark .rt-card:hover {
|
html.rt-dark .rt-card:hover {
|
||||||
border-color: rgba(255, 255, 255, 0.18);
|
border-color: rgba(255, 255, 255, 0.18) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Page background override --- */
|
/* --- Page background: shadcn style (light) --- */
|
||||||
html.rt-card-bg-shadcn .content[role="main"] {
|
html.rt-card-bg-shadcn,
|
||||||
background: #f5f5f5;
|
html.rt-card-bg-shadcn body,
|
||||||
|
html.rt-card-bg-shadcn .content,
|
||||||
|
html.rt-card-bg-shadcn .content[role="main"],
|
||||||
|
html.rt-card-bg-shadcn #siteTable,
|
||||||
|
html.rt-card-bg-shadcn .side,
|
||||||
|
html.rt-card-bg-shadcn .footer-parent {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rt-card-bg-shadcn.rt-dark .content[role="main"] {
|
/* --- Page background: shadcn style (dark) --- */
|
||||||
background: #262626;
|
html.rt-card-bg-shadcn.rt-dark,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark body,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .content,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .content[role="main"],
|
||||||
|
html.rt-card-bg-shadcn.rt-dark #siteTable,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .side,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .footer-parent,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .res-nightmode .content,
|
||||||
|
html.rt-card-bg-shadcn.rt-dark .res-nightmode #siteTable {
|
||||||
|
background-color: #0a0a0a !important;
|
||||||
}
|
}
|
||||||
|
|||||||
259
content/styles/color-picker.css
Normal file
259
content/styles/color-picker.css
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/* --- Color Picker Widget --- */
|
||||||
|
.rt-color-picker {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-tab {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-tab:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-tab--active {
|
||||||
|
color: #1a1a1a;
|
||||||
|
border-bottom-color: #4a90d9;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-panel {
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-grid--palette {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-grid-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-swatch {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-swatch:hover {
|
||||||
|
border-color: rgba(0, 0, 0, 0.4);
|
||||||
|
transform: scale(1.2);
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-swatch--selected {
|
||||||
|
border: 2px solid #1a1a1a;
|
||||||
|
box-shadow: 0 0 0 1px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-grid--named {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-named-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-named-item .rt-picker-swatch {
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-named-label {
|
||||||
|
font-size: 8px;
|
||||||
|
color: #888;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-panel--custom {
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-sv-wrap {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-sv {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-sv-cursor {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-hue {
|
||||||
|
position: relative;
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000);
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-picker-hue-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
width: 2px;
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Preset badges --- */
|
||||||
|
.rt-preset-list {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-badge {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.4;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-badge:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-delete {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #d9534f;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-badge:hover .rt-preset-delete {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-add {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-preset-add:hover {
|
||||||
|
border-color: #999;
|
||||||
|
color: #666;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Picker dark mode --- */
|
||||||
|
.rt-dark .rt-picker-tabs {
|
||||||
|
border-bottom-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-tab {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-tab:hover {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-tab--active {
|
||||||
|
color: #e0e0e0;
|
||||||
|
border-bottom-color: #4a90d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-swatch {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-swatch:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-swatch--selected {
|
||||||
|
border-color: #e0e0e0;
|
||||||
|
box-shadow: 0 0 0 1px #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-named-label {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-sv {
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-picker-hue {
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
256
content/styles/color-popover.css
Normal file
256
content/styles/color-popover.css
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
/* --- Color popover --- */
|
||||||
|
.rt-color-popover {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10000;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
|
||||||
|
padding: 12px;
|
||||||
|
width: 260px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-color-popover.rt-popover-visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
padding: 0 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-mode {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-mode select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row-label {
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row .rt-color-swatch {
|
||||||
|
width: 28px;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row input[type="text"] {
|
||||||
|
width: 70px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-row input[type="text"].rt-input-error {
|
||||||
|
border-color: #d9534f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-preview {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-popover-footer-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-save {
|
||||||
|
background: #4a90d9;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #3a7bc8;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-save:hover {
|
||||||
|
background: #3a7bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-cancel {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-cancel:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-reset {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-btn-reset:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Popover preset container --- */
|
||||||
|
.rt-popover-presets {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Popover dark mode --- */
|
||||||
|
.rt-dark .rt-color-popover {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-color: #555;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-header {
|
||||||
|
border-bottom-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-close {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-close:hover {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-mode select {
|
||||||
|
background: #333;
|
||||||
|
border-color: #555;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-row .rt-color-swatch {
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-row input[type="text"] {
|
||||||
|
background: #333;
|
||||||
|
border-color: #555;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-preview {
|
||||||
|
border-top-color: #444;
|
||||||
|
border-bottom-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-btn-save {
|
||||||
|
background: #4a90d9;
|
||||||
|
border-color: #3a7bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-btn-cancel {
|
||||||
|
background: #444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-btn-reset {
|
||||||
|
border-color: #555;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-btn-reset:hover {
|
||||||
|
background: #333;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-popover-presets {
|
||||||
|
border-top-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-preset-add {
|
||||||
|
border-color: #555;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-dark .rt-preset-add:hover {
|
||||||
|
border-color: #888;
|
||||||
|
color: #ccc;
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
53
content/styles/fonts.css
Normal file
53
content/styles/fonts.css
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/* --- @font-face declarations --- */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Sans';
|
||||||
|
src: url("../fonts/RedditSans[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Sans Condensed';
|
||||||
|
src: url("../fonts/RedditSansCondensed[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Mono';
|
||||||
|
src: url("../fonts/RedditMono[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Font overrides (gated on toggle class) --- */
|
||||||
|
html.rt-fonts-enabled,
|
||||||
|
html.rt-fonts-enabled body,
|
||||||
|
html.rt-fonts-enabled input,
|
||||||
|
html.rt-fonts-enabled textarea,
|
||||||
|
html.rt-fonts-enabled select,
|
||||||
|
html.rt-fonts-enabled button {
|
||||||
|
font-family: 'Reddit Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-fonts-enabled .content,
|
||||||
|
html.rt-fonts-enabled .side,
|
||||||
|
html.rt-fonts-enabled .footer-parent,
|
||||||
|
html.rt-fonts-enabled .nav-buttons,
|
||||||
|
html.rt-fonts-enabled .tabmenu,
|
||||||
|
html.rt-fonts-enabled .morelink,
|
||||||
|
html.rt-fonts-enabled .titlebox,
|
||||||
|
html.rt-fonts-enabled .searchpane,
|
||||||
|
html.rt-fonts-enabled .infobar {
|
||||||
|
font-family: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-fonts-enabled code,
|
||||||
|
html.rt-fonts-enabled pre,
|
||||||
|
html.rt-fonts-enabled .md code,
|
||||||
|
html.rt-fonts-enabled .md pre {
|
||||||
|
font-family: 'Reddit Mono', 'SF Mono', 'Fira Code', Consolas, monospace !important;
|
||||||
|
}
|
||||||
@@ -6,12 +6,6 @@
|
|||||||
function applyColorsToContainer(container, settings, subredditName) {
|
function applyColorsToContainer(container, settings, subredditName) {
|
||||||
const prefix = window.RedditTweaks.getColorPrefix(settings);
|
const prefix = window.RedditTweaks.getColorPrefix(settings);
|
||||||
|
|
||||||
if (window.RedditTweaks.isDarkMode(settings)) {
|
|
||||||
container.classList.add("rt-dark");
|
|
||||||
} else {
|
|
||||||
container.classList.remove("rt-dark");
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = subredditName && settings.subredditColors[subredditName];
|
const entry = subredditName && settings.subredditColors[subredditName];
|
||||||
const overrides = entry && entry[prefix];
|
const overrides = entry && entry[prefix];
|
||||||
|
|
||||||
@@ -98,10 +92,6 @@
|
|||||||
const subredditName = container.getAttribute("data-rt-subreddit");
|
const subredditName = container.getAttribute("data-rt-subreddit");
|
||||||
applyColorsToContainer(container, settings, subredditName);
|
applyColorsToContainer(container, settings, subredditName);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.RedditTweaks.colorPopover && window.RedditTweaks.colorPopover.refreshTheme) {
|
|
||||||
window.RedditTweaks.colorPopover.refreshTheme(settings);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply(settings) {
|
function apply(settings) {
|
||||||
@@ -113,8 +103,6 @@
|
|||||||
window.RedditTweaks.badgeTweak = {
|
window.RedditTweaks.badgeTweak = {
|
||||||
apply: apply,
|
apply: apply,
|
||||||
updateColors: updateColors,
|
updateColors: updateColors,
|
||||||
isDarkMode: window.RedditTweaks.isDarkMode,
|
|
||||||
getColorPrefix: window.RedditTweaks.getColorPrefix,
|
|
||||||
processNewPosts: processPosts,
|
processNewPosts: processPosts,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
function processPost(post) {
|
function processPost(post) {
|
||||||
if (post.classList.contains("rt-card")) return;
|
if (post.classList.contains("rt-card")) return;
|
||||||
post.classList.add("rt-card");
|
post.classList.add("rt-card");
|
||||||
|
|
||||||
|
var rank = post.querySelector(":scope > .rank");
|
||||||
|
if (rank) {
|
||||||
|
rank.classList.add("rt-rank-badge");
|
||||||
|
post.appendChild(rank);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processPosts() {
|
function processPosts() {
|
||||||
@@ -13,14 +19,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDarkMode(settings) {
|
|
||||||
if (window.RedditTweaks.isDarkMode(settings)) {
|
|
||||||
document.documentElement.classList.add("rt-dark");
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove("rt-dark");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyBackground(settings) {
|
function applyBackground(settings) {
|
||||||
if (settings.cardBackground === "shadcn") {
|
if (settings.cardBackground === "shadcn") {
|
||||||
document.documentElement.classList.add("rt-card-bg-shadcn");
|
document.documentElement.classList.add("rt-card-bg-shadcn");
|
||||||
@@ -36,17 +34,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DENSITY_CLASSES = ["rt-density-cozy", "rt-density-modern", "rt-density-extra"];
|
||||||
|
|
||||||
|
function applyDensity(settings) {
|
||||||
|
var html = document.documentElement;
|
||||||
|
for (var i = 0; i < DENSITY_CLASSES.length; i++) {
|
||||||
|
html.classList.remove(DENSITY_CLASSES[i]);
|
||||||
|
}
|
||||||
|
var cls = "rt-density-" + (settings.cardDensity || "modern");
|
||||||
|
html.classList.add(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSelection() {
|
||||||
|
document.addEventListener("mousedown", function (e) {
|
||||||
|
var clickedCard = e.target.closest(".rt-card");
|
||||||
|
var prev = document.querySelectorAll(".rt-card.rt-selected");
|
||||||
|
for (var i = 0; i < prev.length; i++) {
|
||||||
|
prev[i].classList.remove("rt-selected");
|
||||||
|
}
|
||||||
|
if (clickedCard) {
|
||||||
|
clickedCard.classList.add("rt-selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function apply(settings) {
|
function apply(settings) {
|
||||||
document.documentElement.classList.add("rt-card-enabled");
|
document.documentElement.classList.add("rt-card-enabled");
|
||||||
applyDarkMode(settings);
|
|
||||||
applyBackground(settings);
|
applyBackground(settings);
|
||||||
|
applyDensity(settings);
|
||||||
applyGap(settings);
|
applyGap(settings);
|
||||||
processPosts();
|
processPosts();
|
||||||
|
initSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings(settings) {
|
function updateSettings(settings) {
|
||||||
applyDarkMode(settings);
|
|
||||||
applyBackground(settings);
|
applyBackground(settings);
|
||||||
|
applyDensity(settings);
|
||||||
applyGap(settings);
|
applyGap(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,22 +13,10 @@
|
|||||||
|
|
||||||
var els = {};
|
var els = {};
|
||||||
|
|
||||||
function getHelpers() {
|
|
||||||
return {
|
|
||||||
isDarkMode: window.RedditTweaks.isDarkMode,
|
|
||||||
getColorPrefix: window.RedditTweaks.getColorPrefix,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function hexValid(val) {
|
function hexValid(val) {
|
||||||
return /^#[0-9a-f]{6}$/i.test(val);
|
return /^#[0-9a-f]{6}$/i.test(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorToHex(val) {
|
|
||||||
if (!val) return "#ffffff";
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPopover() {
|
function createPopover() {
|
||||||
var el = document.createElement("div");
|
var el = document.createElement("div");
|
||||||
el.className = "rt-color-popover";
|
el.className = "rt-color-popover";
|
||||||
@@ -233,7 +221,7 @@
|
|||||||
var props = ["bg", "border", "text"];
|
var props = ["bg", "border", "text"];
|
||||||
for (var i = 0; i < props.length; i++) {
|
for (var i = 0; i < props.length; i++) {
|
||||||
var prop = props[i];
|
var prop = props[i];
|
||||||
var val = colorToHex(colors[prop]);
|
var val = colors[prop] || "#ffffff";
|
||||||
els.rows[prop].swatch.style.backgroundColor = val;
|
els.rows[prop].swatch.style.backgroundColor = val;
|
||||||
els.rows[prop].hex.value = val;
|
els.rows[prop].hex.value = val;
|
||||||
els.rows[prop].hex.classList.remove("rt-input-error");
|
els.rows[prop].hex.classList.remove("rt-input-error");
|
||||||
@@ -250,32 +238,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPresets(presets) {
|
function renderPresets(presets) {
|
||||||
els.presetList.innerHTML = "";
|
window.RedditTweaks.presets.render(
|
||||||
for (var i = 0; i < presets.length; i++) {
|
els.presetList,
|
||||||
(function (index) {
|
presets,
|
||||||
var p = presets[index];
|
applyPreset,
|
||||||
var badge = document.createElement("span");
|
function (index) {
|
||||||
badge.className = "rt-preset-badge";
|
window.RedditTweaks.presets.delete(index, renderPresets);
|
||||||
badge.style.background = p.bg;
|
}
|
||||||
badge.style.borderColor = p.border;
|
);
|
||||||
badge.style.color = p.text;
|
|
||||||
badge.textContent = "Aa";
|
|
||||||
badge.title = "Click to apply";
|
|
||||||
badge.addEventListener("click", function () {
|
|
||||||
applyPreset(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
var del = document.createElement("span");
|
|
||||||
del.className = "rt-preset-delete";
|
|
||||||
del.textContent = "\u00d7";
|
|
||||||
del.addEventListener("click", function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
removePreset(index);
|
|
||||||
});
|
|
||||||
badge.appendChild(del);
|
|
||||||
els.presetList.appendChild(badge);
|
|
||||||
})(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPreset(p) {
|
function applyPreset(p) {
|
||||||
@@ -293,27 +263,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAsPreset() {
|
function saveAsPreset() {
|
||||||
var preset = {
|
window.RedditTweaks.presets.save(
|
||||||
bg: els.rows.bg.hex.value,
|
{
|
||||||
border: els.rows.border.hex.value,
|
bg: els.rows.bg.hex.value,
|
||||||
text: els.rows.text.hex.value,
|
border: els.rows.border.hex.value,
|
||||||
};
|
text: els.rows.text.hex.value,
|
||||||
var settings = await window.RedditTweaks.loadSettings();
|
},
|
||||||
var presets = settings.colorPresets || [];
|
renderPresets
|
||||||
presets.push(preset);
|
);
|
||||||
settings.colorPresets = presets;
|
|
||||||
await window.RedditTweaks.saveSettings(settings);
|
|
||||||
renderPresets(presets);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removePreset(index) {
|
|
||||||
var settings = await window.RedditTweaks.loadSettings();
|
|
||||||
var presets = settings.colorPresets || [];
|
|
||||||
presets.splice(index, 1);
|
|
||||||
settings.colorPresets = presets;
|
|
||||||
await window.RedditTweaks.saveSettings(settings);
|
|
||||||
renderPresets(presets);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionPopover(anchorEl) {
|
function positionPopover(anchorEl) {
|
||||||
@@ -354,15 +312,6 @@
|
|||||||
popoverEl.style.visibility = "";
|
popoverEl.style.visibility = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDarkModeToPopover(settings) {
|
|
||||||
var helpers = getHelpers();
|
|
||||||
if (helpers.isDarkMode(settings)) {
|
|
||||||
popoverEl.classList.add("rt-dark");
|
|
||||||
} else {
|
|
||||||
popoverEl.classList.remove("rt-dark");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function open(badgeEl, subredditName, settings) {
|
function open(badgeEl, subredditName, settings) {
|
||||||
if (!popoverEl) createPopover();
|
if (!popoverEl) createPopover();
|
||||||
|
|
||||||
@@ -379,8 +328,7 @@
|
|||||||
hadOverride.light = !!(existingEntry && existingEntry.light);
|
hadOverride.light = !!(existingEntry && existingEntry.light);
|
||||||
hadOverride.dark = !!(existingEntry && existingEntry.dark);
|
hadOverride.dark = !!(existingEntry && existingEntry.dark);
|
||||||
|
|
||||||
var helpers = getHelpers();
|
selectedMode = window.RedditTweaks.getColorPrefix(settings);
|
||||||
selectedMode = helpers.getColorPrefix(settings);
|
|
||||||
|
|
||||||
editState.light = getColorsForMode(settings, subredditName, "light");
|
editState.light = getColorsForMode(settings, subredditName, "light");
|
||||||
editState.dark = getColorsForMode(settings, subredditName, "dark");
|
editState.dark = getColorsForMode(settings, subredditName, "dark");
|
||||||
@@ -390,7 +338,6 @@
|
|||||||
els.preview.textContent = "r/" + subredditName;
|
els.preview.textContent = "r/" + subredditName;
|
||||||
|
|
||||||
closePicker();
|
closePicker();
|
||||||
applyDarkModeToPopover(settings);
|
|
||||||
loadModeIntoInputs();
|
loadModeIntoInputs();
|
||||||
updatePreview();
|
updatePreview();
|
||||||
renderPresets(settings.colorPresets || []);
|
renderPresets(settings.colorPresets || []);
|
||||||
@@ -446,15 +393,9 @@
|
|||||||
updatePreview();
|
updatePreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshTheme(settings) {
|
|
||||||
if (!popoverEl || !popoverEl.classList.contains("rt-popover-visible")) return;
|
|
||||||
applyDarkModeToPopover(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.RedditTweaks = window.RedditTweaks || {};
|
window.RedditTweaks = window.RedditTweaks || {};
|
||||||
window.RedditTweaks.colorPopover = {
|
window.RedditTweaks.colorPopover = {
|
||||||
open: open,
|
open: open,
|
||||||
close: close,
|
close: close,
|
||||||
refreshTheme: refreshTheme,
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
186
docs/make_collage.py
Normal file
186
docs/make_collage.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
"""Compose a collage from three extension screenshots.
|
||||||
|
|
||||||
|
Layout (with padding and gaps):
|
||||||
|
+--padding--+---gap---+-----padding-----+
|
||||||
|
| | | |
|
||||||
|
| popup | | disabled (right) |
|
||||||
|
| (left) | +---------gap------+
|
||||||
|
| | | enabled (right) |
|
||||||
|
| | | |
|
||||||
|
+--padding--+---------+-----padding-----+
|
||||||
|
|
||||||
|
Usage: python docs/make_collage.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
from PIL import Image, ImageDraw, ImageFilter
|
||||||
|
|
||||||
|
DOCS = Path(__file__).parent
|
||||||
|
|
||||||
|
PAD = 20
|
||||||
|
GAP = 14
|
||||||
|
GRAD_START = (136, 136, 136)
|
||||||
|
GRAD_END = (87, 87, 87)
|
||||||
|
GRAD_ANGLE_DEG = 45
|
||||||
|
|
||||||
|
POPUP_COL_W = 230
|
||||||
|
|
||||||
|
BORDER_COLOR = (17, 17, 17)
|
||||||
|
BORDER_W = 1
|
||||||
|
CORNER_R = 4
|
||||||
|
|
||||||
|
SHADOW_OFFSET = (4, 4)
|
||||||
|
SHADOW_BLUR = 8
|
||||||
|
SHADOW_COLOR = (0, 0, 0, 110)
|
||||||
|
|
||||||
|
|
||||||
|
def scale_and_crop(img, target_w, target_h):
|
||||||
|
"""Scale image to cover target box, then crop from top-left."""
|
||||||
|
scale = max(target_w / img.width, target_h / img.height)
|
||||||
|
new_w = round(img.width * scale)
|
||||||
|
new_h = round(img.height * scale)
|
||||||
|
img = img.resize((new_w, new_h), Image.LANCZOS)
|
||||||
|
return img.crop((0, 0, target_w, target_h))
|
||||||
|
|
||||||
|
|
||||||
|
def make_gradient(w, h):
|
||||||
|
"""Create a linear gradient background at GRAD_ANGLE_DEG degrees."""
|
||||||
|
angle = math.radians(GRAD_ANGLE_DEG)
|
||||||
|
dx, dy = math.cos(angle), math.sin(angle)
|
||||||
|
pixels = []
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
t = (x * dx + y * dy) / (w * dx + h * dy)
|
||||||
|
t = max(0.0, min(1.0, t))
|
||||||
|
r = round(GRAD_START[0] + (GRAD_END[0] - GRAD_START[0]) * t)
|
||||||
|
g = round(GRAD_START[1] + (GRAD_END[1] - GRAD_START[1]) * t)
|
||||||
|
b = round(GRAD_START[2] + (GRAD_END[2] - GRAD_START[2]) * t)
|
||||||
|
pixels.append((r, g, b))
|
||||||
|
img = Image.new("RGB", (w, h))
|
||||||
|
img.putdata(pixels)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def round_corners(img, radius):
|
||||||
|
"""Apply rounded corners using an alpha mask."""
|
||||||
|
img = img.convert("RGBA")
|
||||||
|
mask = Image.new("L", img.size, 255)
|
||||||
|
draw = ImageDraw.Draw(mask)
|
||||||
|
draw.rectangle([0, 0, radius, radius], fill=0)
|
||||||
|
draw.rectangle([img.width - radius, 0, img.width, radius], fill=0)
|
||||||
|
draw.rectangle([0, img.height - radius, radius, img.height], fill=0)
|
||||||
|
draw.rectangle([img.width - radius, img.height - radius, img.width, img.height], fill=0)
|
||||||
|
draw.pieslice([0, 0, radius * 2, radius * 2], 180, 270, fill=255)
|
||||||
|
draw.pieslice([img.width - radius * 2, 0, img.width, radius * 2], 270, 360, fill=255)
|
||||||
|
draw.pieslice([0, img.height - radius * 2, radius * 2, img.height], 90, 180, fill=255)
|
||||||
|
draw.pieslice([img.width - radius * 2, img.height - radius * 2, img.width, img.height], 0, 90, fill=255)
|
||||||
|
img.putalpha(mask)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def add_border(img, color, width, radius):
|
||||||
|
"""Draw a rounded-rect border on an RGBA image."""
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
draw.rounded_rectangle(
|
||||||
|
[0, 0, img.width - 1, img.height - 1],
|
||||||
|
radius=radius, outline=color, width=width,
|
||||||
|
)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def make_shadow(w, h, radius):
|
||||||
|
"""Create a drop shadow image for a panel of size w x h."""
|
||||||
|
spread = SHADOW_BLUR * 2
|
||||||
|
sw = w + spread * 2
|
||||||
|
sh = h + spread * 2
|
||||||
|
shadow = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(shadow)
|
||||||
|
draw.rounded_rectangle(
|
||||||
|
[spread, spread, spread + w, spread + h],
|
||||||
|
radius=radius, fill=SHADOW_COLOR,
|
||||||
|
)
|
||||||
|
shadow = shadow.filter(ImageFilter.GaussianBlur(SHADOW_BLUR))
|
||||||
|
return shadow
|
||||||
|
|
||||||
|
|
||||||
|
def paste_panel(canvas, panel_img, x, y):
|
||||||
|
"""Paste a panel onto the canvas with rounded corners, border, and drop shadow."""
|
||||||
|
w, h = panel_img.size
|
||||||
|
panel = round_corners(panel_img, CORNER_R)
|
||||||
|
panel = add_border(panel, BORDER_COLOR, BORDER_W, CORNER_R)
|
||||||
|
|
||||||
|
shadow = make_shadow(w, h, CORNER_R)
|
||||||
|
spread = SHADOW_BLUR * 2
|
||||||
|
sx = x + SHADOW_OFFSET[0] - spread
|
||||||
|
sy = y + SHADOW_OFFSET[1] - spread
|
||||||
|
canvas.paste(shadow, (sx, sy), shadow)
|
||||||
|
|
||||||
|
canvas.paste(panel, (x, y), panel)
|
||||||
|
|
||||||
|
|
||||||
|
ASPECT_RATIO = (4, 3)
|
||||||
|
MAX_DIMENSION = 1200
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
popup = Image.open(DOCS / "reddit-extension-popup.png")
|
||||||
|
disabled = Image.open(DOCS / "reddit-extension-disabled.png")
|
||||||
|
enabled = Image.open(DOCS / "reddit-extension-enabled.png")
|
||||||
|
|
||||||
|
popup_scale = POPUP_COL_W / popup.width
|
||||||
|
popup_natural_h = round(popup.height * popup_scale)
|
||||||
|
|
||||||
|
right_col_w = 700
|
||||||
|
right_panel_h = (popup_natural_h - GAP) // 2
|
||||||
|
|
||||||
|
content_h = popup_natural_h
|
||||||
|
content_w = POPUP_COL_W + GAP + right_col_w
|
||||||
|
|
||||||
|
shadow_extra = max(SHADOW_OFFSET[0], SHADOW_OFFSET[1]) + SHADOW_BLUR
|
||||||
|
inner_w = content_w + 2 * PAD + shadow_extra
|
||||||
|
inner_h = content_h + 2 * PAD + shadow_extra
|
||||||
|
|
||||||
|
aspect_w, aspect_h = ASPECT_RATIO
|
||||||
|
target_w = max(inner_w, round(inner_h * aspect_w / aspect_h))
|
||||||
|
target_h = max(inner_h, round(target_w * aspect_h / aspect_w))
|
||||||
|
|
||||||
|
# Clamp to MAX_DIMENSION, scaling down proportionally
|
||||||
|
if max(target_w, target_h) > MAX_DIMENSION:
|
||||||
|
scale_down = MAX_DIMENSION / max(target_w, target_h)
|
||||||
|
target_w = round(target_w * scale_down)
|
||||||
|
target_h = round(target_h * scale_down)
|
||||||
|
inner_w = round(inner_w * scale_down)
|
||||||
|
inner_h = round(inner_h * scale_down)
|
||||||
|
popup_natural_h = round(popup_natural_h * scale_down)
|
||||||
|
right_col_w = round(right_col_w * scale_down)
|
||||||
|
right_panel_h = round(right_panel_h * scale_down)
|
||||||
|
popup_col_w = round(POPUP_COL_W * scale_down)
|
||||||
|
pad = round(PAD * scale_down)
|
||||||
|
gap = round(GAP * scale_down)
|
||||||
|
else:
|
||||||
|
popup_col_w = POPUP_COL_W
|
||||||
|
pad = PAD
|
||||||
|
gap = GAP
|
||||||
|
|
||||||
|
canvas = make_gradient(target_w, target_h).convert("RGBA")
|
||||||
|
|
||||||
|
x_off = (target_w - inner_w) // 2
|
||||||
|
y_off = (target_h - inner_h) // 2
|
||||||
|
|
||||||
|
popup_resized = popup.resize((popup_col_w, popup_natural_h), Image.LANCZOS)
|
||||||
|
paste_panel(canvas, popup_resized, x_off + pad, y_off + pad)
|
||||||
|
|
||||||
|
rx = x_off + pad + popup_col_w + gap
|
||||||
|
paste_panel(canvas, scale_and_crop(disabled, right_col_w, right_panel_h), rx, y_off + pad)
|
||||||
|
paste_panel(canvas, scale_and_crop(enabled, right_col_w, right_panel_h), rx, y_off + pad + right_panel_h + gap)
|
||||||
|
|
||||||
|
out = DOCS / "reddit-extension-collage.png"
|
||||||
|
canvas.convert("RGB").save(out, optimize=True)
|
||||||
|
print(f"Saved {out} ({target_w}x{target_h})")
|
||||||
|
print(f"Popup: {popup_col_w}x{popup_natural_h} (showing 100%)")
|
||||||
|
print(f"Right panels: {right_col_w}x{right_panel_h} each")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
docs/reddit-extension-collage.png
Normal file
BIN
docs/reddit-extension-collage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 417 KiB |
BIN
docs/reddit-extension-disabled.png
Normal file
BIN
docs/reddit-extension-disabled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 912 KiB |
BIN
docs/reddit-extension-enabled.png
Normal file
BIN
docs/reddit-extension-enabled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 709 KiB |
BIN
docs/reddit-extension-popup.png
Normal file
BIN
docs/reddit-extension-popup.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
263
docs/superpowers/plans/2026-08-27-reddit-fonts.md
Normal file
263
docs/superpowers/plans/2026-08-27-reddit-fonts.md
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
# Reddit Fonts Override 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:** Override old Reddit's fonts with Reddit Sans, Reddit Sans Condensed, and Reddit Mono, toggleable from the extension popup.
|
||||||
|
|
||||||
|
**Architecture:** Bundle three variable-weight woff2 files in the extension. A static CSS file declares `@font-face` for each family and applies font overrides when `html.rt-fonts-enabled` is present. The JS layer toggles that class based on a `fontOverride` setting, following the same pattern as card layout and badge layout.
|
||||||
|
|
||||||
|
**Tech Stack:** Firefox WebExtension (Manifest V2), vanilla CSS/JS, woff2 variable fonts.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- No external network requests — fonts are bundled locally.
|
||||||
|
- SIL Open Font License 1.1 — no attribution in UI required, but license file ships with fonts.
|
||||||
|
- Variable fonts only (no static weight files) — one file per family covers all weights.
|
||||||
|
- Follow existing codebase patterns: IIFE modules, `window.RedditTweaks` namespace, class-toggle gating.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Download and bundle font assets
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `content/fonts/RedditSans[wght].woff2`
|
||||||
|
- Create: `content/fonts/RedditSansCondensed[wght].woff2`
|
||||||
|
- Create: `content/fonts/RedditMono[wght].woff2`
|
||||||
|
- Create: `content/fonts/OFL.txt`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Nothing
|
||||||
|
- Produces: Font files at `content/fonts/` referenced by CSS in Task 2
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the fonts directory**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p content/fonts
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Download the three variable-weight woff2 files from the reddit/redditsans repo**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -o "content/fonts/RedditSans[wght].woff2" \
|
||||||
|
"https://raw.githubusercontent.com/reddit/redditsans/main/fonts/sans/webfonts/RedditSans%5Bwght%5D.woff2"
|
||||||
|
|
||||||
|
curl -L -o "content/fonts/RedditSansCondensed[wght].woff2" \
|
||||||
|
"https://raw.githubusercontent.com/reddit/redditsans/main/fonts/sans-condensed/webfonts/RedditSansCondensed%5Bwght%5D.woff2"
|
||||||
|
|
||||||
|
curl -L -o "content/fonts/RedditMono[wght].woff2" \
|
||||||
|
"https://raw.githubusercontent.com/reddit/redditsans/main/fonts/mono/webfonts/RedditMono%5Bwght%5D.woff2"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `%5B` and `%5D` are URL-encoded brackets for the variable font filenames.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Download the OFL license file**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -o content/fonts/OFL.txt \
|
||||||
|
"https://raw.githubusercontent.com/reddit/redditsans/main/OFL.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify files downloaded correctly**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la content/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: four files — three woff2 files (~81KB, ~94KB, ~96KB) and OFL.txt.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add content/fonts/
|
||||||
|
git commit -m "feat: bundle Reddit Sans, Condensed, and Mono variable fonts"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Create fonts CSS with @font-face and override rules
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `content/styles/fonts.css`
|
||||||
|
- Modify: `manifest.json:19` (add CSS to manifest)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Font files at `content/fonts/` from Task 1
|
||||||
|
- Produces: CSS classes gated on `html.rt-fonts-enabled` used by Task 3
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create `content/styles/fonts.css`**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* --- @font-face declarations --- */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Sans';
|
||||||
|
src: url("../fonts/RedditSans[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Sans Condensed';
|
||||||
|
src: url("../fonts/RedditSansCondensed[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Reddit Mono';
|
||||||
|
src: url("../fonts/RedditMono[wght].woff2") format("woff2");
|
||||||
|
font-weight: 300 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Font overrides (gated on toggle class) --- */
|
||||||
|
html.rt-fonts-enabled,
|
||||||
|
html.rt-fonts-enabled body,
|
||||||
|
html.rt-fonts-enabled input,
|
||||||
|
html.rt-fonts-enabled textarea,
|
||||||
|
html.rt-fonts-enabled select,
|
||||||
|
html.rt-fonts-enabled button {
|
||||||
|
font-family: 'Reddit Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-fonts-enabled .content,
|
||||||
|
html.rt-fonts-enabled .side,
|
||||||
|
html.rt-fonts-enabled .footer-parent,
|
||||||
|
html.rt-fonts-enabled .nav-buttons,
|
||||||
|
html.rt-fonts-enabled .tabmenu,
|
||||||
|
html.rt-fonts-enabled .morelink,
|
||||||
|
html.rt-fonts-enabled .titlebox,
|
||||||
|
html.rt-fonts-enabled .searchpane,
|
||||||
|
html.rt-fonts-enabled .infobar {
|
||||||
|
font-family: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.rt-fonts-enabled code,
|
||||||
|
html.rt-fonts-enabled pre,
|
||||||
|
html.rt-fonts-enabled .md code,
|
||||||
|
html.rt-fonts-enabled .md pre {
|
||||||
|
font-family: 'Reddit Mono', 'SF Mono', 'Fira Code', Consolas, monospace !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add fonts.css to the manifest CSS array**
|
||||||
|
|
||||||
|
In `manifest.json`, add `"content/styles/fonts.css"` to the `css` array at line 19:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"css": ["content/styles/badges.css", "content/styles/cards.css", "content/styles/fonts.css"],
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the CSS file loads without syntax errors**
|
||||||
|
|
||||||
|
Load the extension in Firefox (`about:debugging` → Load Temporary Add-on) and check the browser console for CSS parse errors. Confirm no errors related to font paths.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add content/styles/fonts.css manifest.json
|
||||||
|
git commit -m "feat: add fonts CSS with @font-face and override rules"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Wire settings, class toggle, and popup
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `lib/settings.js:22` (add default)
|
||||||
|
- Modify: `content/main.js:63-84` (add class toggle and storage listener)
|
||||||
|
- Modify: `popup/popup.html:18-19` (add checkbox)
|
||||||
|
- Modify: `popup/popup.js:71,288,313-314` (populate, save, listen)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: CSS class `html.rt-fonts-enabled` from Task 2
|
||||||
|
- Produces: Complete toggleable font override feature
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `fontOverride` default to `lib/settings.js`**
|
||||||
|
|
||||||
|
Add `fontOverride: true,` after line 22 (`badgeLayout: true,`):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const DEFAULTS = {
|
||||||
|
// ... existing defaults ...
|
||||||
|
badgeLayout: true,
|
||||||
|
fontOverride: true,
|
||||||
|
cardLayout: true,
|
||||||
|
// ... rest ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add class toggle in `content/main.js` init function**
|
||||||
|
|
||||||
|
After the `badgeLayout` class toggle block (line 67-69), add:
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (settings.fontOverride) {
|
||||||
|
document.documentElement.classList.add("rt-fonts-enabled");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add `fontOverride` to the storage change reload check in `content/main.js`**
|
||||||
|
|
||||||
|
In the `browser.storage.onChanged.addListener` callback (around line 80-88), add `fontOverride` to the reload condition:
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (
|
||||||
|
(changes.enabled && changes.enabled.newValue !== changes.enabled.oldValue) ||
|
||||||
|
(changes.badgeLayout && changes.badgeLayout.newValue !== changes.badgeLayout.oldValue) ||
|
||||||
|
(changes.fontOverride && changes.fontOverride.newValue !== changes.fontOverride.oldValue) ||
|
||||||
|
(changes.cardLayout && changes.cardLayout.newValue !== changes.cardLayout.oldValue)
|
||||||
|
) {
|
||||||
|
location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add checkbox to `popup/popup.html`**
|
||||||
|
|
||||||
|
After the "Badge layout" toggle (line 18-19), add:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<label class="toggle-row">
|
||||||
|
<span>Reddit fonts</span>
|
||||||
|
<input type="checkbox" id="fontOverride">
|
||||||
|
</label>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Wire the checkbox in `popup/popup.js`**
|
||||||
|
|
||||||
|
In `populateControls` (after line 71), add:
|
||||||
|
|
||||||
|
```js
|
||||||
|
document.getElementById("fontOverride").checked = settings.fontOverride;
|
||||||
|
```
|
||||||
|
|
||||||
|
In `saveFromControls` (after `badgeLayout` on line 288), add:
|
||||||
|
|
||||||
|
```js
|
||||||
|
fontOverride: document.getElementById("fontOverride").checked,
|
||||||
|
```
|
||||||
|
|
||||||
|
In `init` (after the `badgeLayout` listener on line 314), add:
|
||||||
|
|
||||||
|
```js
|
||||||
|
document.getElementById("fontOverride").addEventListener("change", saveFromControls);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Test the toggle**
|
||||||
|
|
||||||
|
1. Load the extension in Firefox.
|
||||||
|
2. Open old.reddit.com.
|
||||||
|
3. Verify fonts are Reddit Sans by default (toggle is on).
|
||||||
|
4. Open popup, uncheck "Reddit fonts", confirm page reloads and fonts revert to old Reddit defaults.
|
||||||
|
5. Re-check "Reddit fonts", confirm page reloads and fonts switch back to Reddit Sans.
|
||||||
|
6. Verify monospace elements (`code`, `pre`) use Reddit Mono.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add lib/settings.js content/main.js popup/popup.html popup/popup.js
|
||||||
|
git commit -m "feat: add fontOverride toggle for Reddit Sans fonts"
|
||||||
|
```
|
||||||
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"
|
||||||
|
```
|
||||||
70
docs/superpowers/specs/2026-08-27-reddit-fonts-design.md
Normal file
70
docs/superpowers/specs/2026-08-27-reddit-fonts-design.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Reddit Fonts Override
|
||||||
|
|
||||||
|
Replace old Reddit's default fonts with Reddit's modern typeface family (Reddit Sans, Reddit Sans Condensed, Reddit Mono) from the [reddit/redditsans](https://github.com/reddit/redditsans) repository.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Full page override — all text on old.reddit.com and www.reddit.com.
|
||||||
|
- Toggleable from the extension popup (one switch for all three families).
|
||||||
|
- Font files bundled in the extension (no external network requests).
|
||||||
|
|
||||||
|
## Font Assets
|
||||||
|
|
||||||
|
Three variable-weight woff2 files stored in `content/fonts/`:
|
||||||
|
|
||||||
|
| File | Family | Approx Size |
|
||||||
|
|---|---|---|
|
||||||
|
| `RedditSans[wght].woff2` | Reddit Sans | 96KB |
|
||||||
|
| `RedditSansCondensed[wght].woff2` | Reddit Sans Condensed | 94KB |
|
||||||
|
| `RedditMono[wght].woff2` | Reddit Mono | 81KB |
|
||||||
|
|
||||||
|
Variable fonts only — each covers all weights (Light through ExtraBold) via the `wght` axis. No italic variants initially; can be added later if needed.
|
||||||
|
|
||||||
|
Licensed under SIL Open Font License 1.1.
|
||||||
|
|
||||||
|
## CSS Architecture
|
||||||
|
|
||||||
|
New file: `content/styles/fonts.css`, registered in `manifest.json` in the `css` array.
|
||||||
|
|
||||||
|
### @font-face Declarations
|
||||||
|
|
||||||
|
Three `@font-face` blocks, one per family. Extension-relative paths (e.g., `url("../fonts/RedditSans[wght].woff2")`). Each declares `font-display: swap` and the full `font-weight` range (300–800).
|
||||||
|
|
||||||
|
### Override Rules
|
||||||
|
|
||||||
|
All overrides gated on `html.rt-fonts-enabled`:
|
||||||
|
|
||||||
|
- `html.rt-fonts-enabled` sets `font-family: 'Reddit Sans', sans-serif` on the root.
|
||||||
|
- Body, content, sidebar, footer inherit from root.
|
||||||
|
- `code`, `pre`, and `.md code` elements use `font-family: 'Reddit Mono', monospace`.
|
||||||
|
- Reddit Sans Condensed is declared but not force-applied — available as `'Reddit Sans Condensed'` for future tweaks or user stylesheets.
|
||||||
|
|
||||||
|
## Settings & Toggle
|
||||||
|
|
||||||
|
### Default
|
||||||
|
|
||||||
|
`fontOverride: true` added to `DEFAULTS` in `lib/settings.js`. On by default.
|
||||||
|
|
||||||
|
### Class Toggle
|
||||||
|
|
||||||
|
`content/main.js` toggles `html.rt-fonts-enabled` on init and on settings change, following the same pattern as `rt-card-enabled` and `rt-badges-enabled`.
|
||||||
|
|
||||||
|
### Popup
|
||||||
|
|
||||||
|
New checkbox "Reddit fonts" in the top section of `popup/popup.html`, alongside "Enabled" and "Badge layout". Wired in `popup/popup.js` the same way as existing toggles.
|
||||||
|
|
||||||
|
No per-family granularity — one switch controls all three families on or off.
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|---|---|
|
||||||
|
| `content/fonts/RedditSans[wght].woff2` | New — bundled font asset |
|
||||||
|
| `content/fonts/RedditSansCondensed[wght].woff2` | New — bundled font asset |
|
||||||
|
| `content/fonts/RedditMono[wght].woff2` | New — bundled font asset |
|
||||||
|
| `content/styles/fonts.css` | New — @font-face declarations and override rules |
|
||||||
|
| `manifest.json` | Add `content/styles/fonts.css` to `css` array |
|
||||||
|
| `lib/settings.js` | Add `fontOverride: true` to `DEFAULTS` |
|
||||||
|
| `content/main.js` | Toggle `rt-fonts-enabled` class |
|
||||||
|
| `popup/popup.html` | Add "Reddit fonts" checkbox |
|
||||||
|
| `popup/popup.js` | Wire the new checkbox |
|
||||||
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 |
|
||||||
57
lib/presets.js
Normal file
57
lib/presets.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function renderPresets(container, presets, onApply, onDelete) {
|
||||||
|
container.innerHTML = "";
|
||||||
|
for (var i = 0; i < presets.length; i++) {
|
||||||
|
(function (index) {
|
||||||
|
var p = presets[index];
|
||||||
|
var badge = document.createElement("span");
|
||||||
|
badge.className = "rt-preset-badge";
|
||||||
|
badge.style.background = p.bg;
|
||||||
|
badge.style.borderColor = p.border;
|
||||||
|
badge.style.color = p.text;
|
||||||
|
badge.textContent = "Aa";
|
||||||
|
badge.title = "Click to apply";
|
||||||
|
badge.addEventListener("click", function () {
|
||||||
|
onApply(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
var del = document.createElement("span");
|
||||||
|
del.className = "rt-preset-delete";
|
||||||
|
del.textContent = "\u00d7";
|
||||||
|
del.addEventListener("click", function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete(index);
|
||||||
|
});
|
||||||
|
badge.appendChild(del);
|
||||||
|
container.appendChild(badge);
|
||||||
|
})(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePreset(preset, onDone) {
|
||||||
|
var settings = await window.RedditTweaks.loadSettings();
|
||||||
|
var presets = settings.colorPresets || [];
|
||||||
|
presets.push(preset);
|
||||||
|
settings.colorPresets = presets;
|
||||||
|
await window.RedditTweaks.saveSettings(settings);
|
||||||
|
onDone(presets);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePreset(index, onDone) {
|
||||||
|
var settings = await window.RedditTweaks.loadSettings();
|
||||||
|
var presets = settings.colorPresets || [];
|
||||||
|
presets.splice(index, 1);
|
||||||
|
settings.colorPresets = presets;
|
||||||
|
await window.RedditTweaks.saveSettings(settings);
|
||||||
|
onDone(presets);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.RedditTweaks = window.RedditTweaks || {};
|
||||||
|
window.RedditTweaks.presets = {
|
||||||
|
render: renderPresets,
|
||||||
|
save: savePreset,
|
||||||
|
delete: deletePreset,
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -19,8 +19,10 @@
|
|||||||
subredditColors: {},
|
subredditColors: {},
|
||||||
colorPresets: [],
|
colorPresets: [],
|
||||||
badgeLayout: true,
|
badgeLayout: true,
|
||||||
|
fontOverride: true,
|
||||||
cardLayout: true,
|
cardLayout: true,
|
||||||
cardBackground: "reddit",
|
cardBackground: "reddit",
|
||||||
|
cardDensity: "modern",
|
||||||
cardGap: 16,
|
cardGap: 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,6 +76,14 @@
|
|||||||
return isDarkMode(settings) ? "dark" : "light";
|
return isDarkMode(settings) ? "dark" : "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyDarkModeClass(settings) {
|
||||||
|
if (isDarkMode(settings)) {
|
||||||
|
document.documentElement.classList.add("rt-dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("rt-dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.RedditTweaks = window.RedditTweaks || {};
|
window.RedditTweaks = window.RedditTweaks || {};
|
||||||
window.RedditTweaks.DEFAULTS = DEFAULTS;
|
window.RedditTweaks.DEFAULTS = DEFAULTS;
|
||||||
window.RedditTweaks.loadSettings = loadSettings;
|
window.RedditTweaks.loadSettings = loadSettings;
|
||||||
@@ -81,4 +91,5 @@
|
|||||||
window.RedditTweaks.resetSettings = resetSettings;
|
window.RedditTweaks.resetSettings = resetSettings;
|
||||||
window.RedditTweaks.isDarkMode = isDarkMode;
|
window.RedditTweaks.isDarkMode = isDarkMode;
|
||||||
window.RedditTweaks.getColorPrefix = getColorPrefix;
|
window.RedditTweaks.getColorPrefix = getColorPrefix;
|
||||||
|
window.RedditTweaks.applyDarkModeClass = applyDarkModeClass;
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Reddit Tweaks",
|
"name": "Reddit Tweaks",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"description": "Reskins old Reddit post listings with badge-style subreddit and comment indicators.",
|
"description": "Reskins old Reddit post listings with badge-style subreddit and comment indicators.",
|
||||||
"permissions": ["storage", "activeTab"],
|
"permissions": ["storage", "activeTab"],
|
||||||
"icons": {
|
"icons": {
|
||||||
@@ -15,10 +15,11 @@
|
|||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["*://old.reddit.com/*"],
|
"matches": ["*://old.reddit.com/*", "*://www.reddit.com/*"],
|
||||||
"css": ["content/styles/badges.css", "content/styles/cards.css"],
|
"css": ["content/styles/badges.css", "content/styles/color-popover.css", "content/styles/color-picker.css", "content/styles/cards.css", "content/styles/fonts.css"],
|
||||||
"js": [
|
"js": [
|
||||||
"lib/settings.js",
|
"lib/settings.js",
|
||||||
|
"lib/presets.js",
|
||||||
"content/tweaks/badges.js",
|
"content/tweaks/badges.js",
|
||||||
"content/tweaks/color-picker.js",
|
"content/tweaks/color-picker.js",
|
||||||
"content/tweaks/color-popover.js",
|
"content/tweaks/color-popover.js",
|
||||||
|
|||||||
271
popup/popup.css
271
popup/popup.css
@@ -210,7 +210,7 @@ fieldset input[type="text"] {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Preset badges --- */
|
/* --- Popup-specific preset bar --- */
|
||||||
.rt-preset-bar {
|
.rt-preset-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -218,272 +218,3 @@ fieldset input[type="text"] {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-preset-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1.4;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-delete {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background: #d9534f;
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 14px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-badge:hover .rt-preset-delete {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-add {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border: 1px dashed #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: none;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-preset-add:hover {
|
|
||||||
border-color: #999;
|
|
||||||
color: #666;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Color Picker Widget --- */
|
|
||||||
.rt-color-picker {
|
|
||||||
margin: 8px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab {
|
|
||||||
flex: 1;
|
|
||||||
padding: 4px 8px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-tab--active {
|
|
||||||
color: #1a1a1a;
|
|
||||||
border-bottom-color: #4a90d9;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-panel {
|
|
||||||
max-height: 180px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid--palette {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch:hover {
|
|
||||||
border-color: rgba(0, 0, 0, 0.4);
|
|
||||||
transform: scale(1.2);
|
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-swatch--selected {
|
|
||||||
border: 2px solid #1a1a1a;
|
|
||||||
box-shadow: 0 0 0 1px white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-grid--named {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-item .rt-picker-swatch {
|
|
||||||
width: 24px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-named-label {
|
|
||||||
font-size: 8px;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 100%;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-panel--custom {
|
|
||||||
max-height: none;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv-wrap {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-sv-cursor {
|
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border: 2px solid white;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-hue {
|
|
||||||
position: relative;
|
|
||||||
height: 14px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000);
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-picker-hue-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: -1px;
|
|
||||||
width: 2px;
|
|
||||||
height: calc(100% + 2px);
|
|
||||||
background: white;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Picker dark mode */
|
|
||||||
.rt-dark .rt-picker-tabs,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tabs {
|
|
||||||
border-bottom-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab:hover,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-tab--active,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-tab--active {
|
|
||||||
color: #e0e0e0;
|
|
||||||
border-bottom-color: #4a90d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch {
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch:hover,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-swatch--selected,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-swatch--selected {
|
|
||||||
border-color: #e0e0e0;
|
|
||||||
box-shadow: 0 0 0 1px #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-named-label,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-named-label {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-sv,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-sv {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-dark .rt-picker-hue,
|
|
||||||
.rt-color-popover.rt-dark .rt-picker-hue {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="popup.css">
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
<link rel="stylesheet" href="../content/styles/color-picker.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="popup-container">
|
<div class="popup-container">
|
||||||
@@ -17,6 +18,10 @@
|
|||||||
<span>Badge layout</span>
|
<span>Badge layout</span>
|
||||||
<input type="checkbox" id="badgeLayout">
|
<input type="checkbox" id="badgeLayout">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="toggle-row">
|
||||||
|
<span>Reddit fonts</span>
|
||||||
|
<input type="checkbox" id="fontOverride">
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -28,6 +33,37 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Card Layout</h2>
|
||||||
|
<label class="toggle-row">
|
||||||
|
<span>Enable card layout</span>
|
||||||
|
<input type="checkbox" id="cardLayout">
|
||||||
|
</label>
|
||||||
|
<label for="cardDensity">Card Density</label>
|
||||||
|
<select id="cardDensity">
|
||||||
|
<option value="cozy">Cozy</option>
|
||||||
|
<option value="modern">Modern</option>
|
||||||
|
<option value="extra">Extra</option>
|
||||||
|
</select>
|
||||||
|
<label for="cardBackground">Background Style</label>
|
||||||
|
<select id="cardBackground">
|
||||||
|
<option value="reddit">Reddit Default</option>
|
||||||
|
<option value="shadcn">shadcn Style</option>
|
||||||
|
</select>
|
||||||
|
<label for="cardGap">Card Spacing</label>
|
||||||
|
<select id="cardGap">
|
||||||
|
<option value="1">1px</option>
|
||||||
|
<option value="2">2px</option>
|
||||||
|
<option value="4">4px</option>
|
||||||
|
<option value="6">6px</option>
|
||||||
|
<option value="8">8px</option>
|
||||||
|
<option value="12">12px</option>
|
||||||
|
<option value="16">16px</option>
|
||||||
|
<option value="24">24px</option>
|
||||||
|
<option value="32">32px</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Light Mode Colors</h2>
|
<h2>Light Mode Colors</h2>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -83,6 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="../lib/settings.js"></script>
|
<script src="../lib/settings.js"></script>
|
||||||
|
<script src="../lib/presets.js"></script>
|
||||||
<script src="../content/tweaks/color-picker.js"></script>
|
<script src="../content/tweaks/color-picker.js"></script>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
200
popup/popup.js
200
popup/popup.js
@@ -4,6 +4,7 @@
|
|||||||
var picker = null;
|
var picker = null;
|
||||||
var activeSwatchEl = null;
|
var activeSwatchEl = null;
|
||||||
var saveDebounceTimer = null;
|
var saveDebounceTimer = null;
|
||||||
|
var subredditEntries = [];
|
||||||
|
|
||||||
var COLOR_KEYS = [
|
var COLOR_KEYS = [
|
||||||
"light.subredditBgColor",
|
"light.subredditBgColor",
|
||||||
@@ -20,11 +21,6 @@
|
|||||||
"dark.commentsTextColor",
|
"dark.commentsTextColor",
|
||||||
];
|
];
|
||||||
|
|
||||||
function colorToHex(val) {
|
|
||||||
if (!val) return "#ffffff";
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getColorValue(el) {
|
function getColorValue(el) {
|
||||||
return el.dataset.value || "#ffffff";
|
return el.dataset.value || "#ffffff";
|
||||||
}
|
}
|
||||||
@@ -69,11 +65,16 @@
|
|||||||
function populateControls(settings) {
|
function populateControls(settings) {
|
||||||
document.getElementById("enabled").checked = settings.enabled;
|
document.getElementById("enabled").checked = settings.enabled;
|
||||||
document.getElementById("badgeLayout").checked = settings.badgeLayout;
|
document.getElementById("badgeLayout").checked = settings.badgeLayout;
|
||||||
|
document.getElementById("fontOverride").checked = settings.fontOverride;
|
||||||
document.getElementById("darkMode").value = settings.darkMode;
|
document.getElementById("darkMode").value = settings.darkMode;
|
||||||
|
document.getElementById("cardLayout").checked = settings.cardLayout;
|
||||||
|
document.getElementById("cardDensity").value = settings.cardDensity;
|
||||||
|
document.getElementById("cardBackground").value = settings.cardBackground;
|
||||||
|
document.getElementById("cardGap").value = String(settings.cardGap);
|
||||||
|
|
||||||
COLOR_KEYS.forEach(function (key) {
|
COLOR_KEYS.forEach(function (key) {
|
||||||
var el = document.getElementById(key);
|
var el = document.getElementById(key);
|
||||||
if (el) setColorValue(el, colorToHex(settings[key]));
|
if (el) setColorValue(el, settings[key] || "#ffffff");
|
||||||
});
|
});
|
||||||
|
|
||||||
renderSubredditOverrides(settings.subredditColors || {});
|
renderSubredditOverrides(settings.subredditColors || {});
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
function renderSubredditOverrides(overrides) {
|
function renderSubredditOverrides(overrides) {
|
||||||
var list = document.getElementById("subredditList");
|
var list = document.getElementById("subredditList");
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
|
subredditEntries = [];
|
||||||
|
|
||||||
Object.entries(overrides).forEach(function (pair) {
|
Object.entries(overrides).forEach(function (pair) {
|
||||||
addSubredditEntry(pair[0], pair[1]);
|
addSubredditEntry(pair[0], pair[1]);
|
||||||
@@ -90,8 +92,8 @@
|
|||||||
|
|
||||||
function addSubredditEntry(name, modeColors) {
|
function addSubredditEntry(name, modeColors) {
|
||||||
var list = document.getElementById("subredditList");
|
var list = document.getElementById("subredditList");
|
||||||
var entry = document.createElement("div");
|
var el = document.createElement("div");
|
||||||
entry.className = "subreddit-entry";
|
el.className = "subreddit-entry";
|
||||||
|
|
||||||
var header = document.createElement("div");
|
var header = document.createElement("div");
|
||||||
header.className = "subreddit-entry-header";
|
header.className = "subreddit-entry-header";
|
||||||
@@ -101,17 +103,7 @@
|
|||||||
nameInput.placeholder = "subreddit";
|
nameInput.placeholder = "subreddit";
|
||||||
nameInput.value = name || "";
|
nameInput.value = name || "";
|
||||||
|
|
||||||
var removeBtn = document.createElement("button");
|
|
||||||
removeBtn.className = "btn-remove";
|
|
||||||
removeBtn.textContent = "\u00d7";
|
|
||||||
removeBtn.addEventListener("click", function () {
|
|
||||||
closePicker();
|
|
||||||
entry.remove();
|
|
||||||
saveFromControls();
|
|
||||||
});
|
|
||||||
|
|
||||||
header.appendChild(nameInput);
|
header.appendChild(nameInput);
|
||||||
header.appendChild(removeBtn);
|
|
||||||
|
|
||||||
var modeRow = document.createElement("div");
|
var modeRow = document.createElement("div");
|
||||||
modeRow.className = "subreddit-entry-mode";
|
modeRow.className = "subreddit-entry-mode";
|
||||||
@@ -129,39 +121,59 @@
|
|||||||
|
|
||||||
var bgSwatch = document.createElement("div");
|
var bgSwatch = document.createElement("div");
|
||||||
bgSwatch.className = "rt-color-swatch";
|
bgSwatch.className = "rt-color-swatch";
|
||||||
setColorValue(bgSwatch, colorToHex(lightColors.bg));
|
setColorValue(bgSwatch, lightColors.bg || "#ffffff");
|
||||||
bgSwatch.title = "Background";
|
bgSwatch.title = "Background";
|
||||||
|
|
||||||
var borderSwatch = document.createElement("div");
|
var borderSwatch = document.createElement("div");
|
||||||
borderSwatch.className = "rt-color-swatch";
|
borderSwatch.className = "rt-color-swatch";
|
||||||
setColorValue(borderSwatch, colorToHex(lightColors.border));
|
setColorValue(borderSwatch, lightColors.border || "#ffffff");
|
||||||
borderSwatch.title = "Border";
|
borderSwatch.title = "Border";
|
||||||
|
|
||||||
var textSwatch = document.createElement("div");
|
var textSwatch = document.createElement("div");
|
||||||
textSwatch.className = "rt-color-swatch";
|
textSwatch.className = "rt-color-swatch";
|
||||||
setColorValue(textSwatch, colorToHex(lightColors.text));
|
setColorValue(textSwatch, lightColors.text || "#ffffff");
|
||||||
textSwatch.title = "Text";
|
textSwatch.title = "Text";
|
||||||
|
|
||||||
colorsDiv.appendChild(bgSwatch);
|
colorsDiv.appendChild(bgSwatch);
|
||||||
colorsDiv.appendChild(borderSwatch);
|
colorsDiv.appendChild(borderSwatch);
|
||||||
colorsDiv.appendChild(textSwatch);
|
colorsDiv.appendChild(textSwatch);
|
||||||
|
|
||||||
entry._colorState = {
|
var state = {
|
||||||
light: { bg: getColorValue(bgSwatch), border: getColorValue(borderSwatch), text: getColorValue(textSwatch) },
|
nameInput: nameInput,
|
||||||
dark: { bg: colorToHex(darkColors.bg), border: colorToHex(darkColors.border), text: colorToHex(darkColors.text) },
|
bgSwatch: bgSwatch,
|
||||||
|
borderSwatch: borderSwatch,
|
||||||
|
textSwatch: textSwatch,
|
||||||
|
modeSelect: modeSelect,
|
||||||
|
colorState: {
|
||||||
|
light: { bg: getColorValue(bgSwatch), border: getColorValue(borderSwatch), text: getColorValue(textSwatch) },
|
||||||
|
dark: { bg: darkColors.bg || "#ffffff", border: darkColors.border || "#ffffff", text: darkColors.text || "#ffffff" },
|
||||||
|
},
|
||||||
|
hadOverride: {
|
||||||
|
light: !!(modeColors && modeColors.light),
|
||||||
|
dark: !!(modeColors && modeColors.dark),
|
||||||
|
},
|
||||||
|
dirtyModes: { light: false, dark: false },
|
||||||
};
|
};
|
||||||
entry._hadOverride = {
|
subredditEntries.push(state);
|
||||||
light: !!(modeColors && modeColors.light),
|
|
||||||
dark: !!(modeColors && modeColors.dark),
|
var removeBtn = document.createElement("button");
|
||||||
};
|
removeBtn.className = "btn-remove";
|
||||||
entry._dirtyModes = { light: false, dark: false };
|
removeBtn.textContent = "\u00d7";
|
||||||
|
removeBtn.addEventListener("click", function () {
|
||||||
|
closePicker();
|
||||||
|
var idx = subredditEntries.indexOf(state);
|
||||||
|
if (idx !== -1) subredditEntries.splice(idx, 1);
|
||||||
|
el.remove();
|
||||||
|
saveFromControls();
|
||||||
|
});
|
||||||
|
header.appendChild(removeBtn);
|
||||||
|
|
||||||
modeSelect.addEventListener("change", function () {
|
modeSelect.addEventListener("change", function () {
|
||||||
closePicker();
|
closePicker();
|
||||||
var prev = modeSelect.value === "dark" ? "light" : "dark";
|
var prev = modeSelect.value === "dark" ? "light" : "dark";
|
||||||
entry._dirtyModes[prev] = true;
|
state.dirtyModes[prev] = true;
|
||||||
entry._colorState[prev] = { bg: getColorValue(bgSwatch), border: getColorValue(borderSwatch), text: getColorValue(textSwatch) };
|
state.colorState[prev] = { bg: getColorValue(bgSwatch), border: getColorValue(borderSwatch), text: getColorValue(textSwatch) };
|
||||||
var next = entry._colorState[modeSelect.value];
|
var next = state.colorState[modeSelect.value];
|
||||||
setColorValue(bgSwatch, next.bg);
|
setColorValue(bgSwatch, next.bg);
|
||||||
setColorValue(borderSwatch, next.border);
|
setColorValue(borderSwatch, next.border);
|
||||||
setColorValue(textSwatch, next.text);
|
setColorValue(textSwatch, next.text);
|
||||||
@@ -170,7 +182,7 @@
|
|||||||
[bgSwatch, borderSwatch, textSwatch].forEach(function (swatch) {
|
[bgSwatch, borderSwatch, textSwatch].forEach(function (swatch) {
|
||||||
swatch.addEventListener("click", function () {
|
swatch.addEventListener("click", function () {
|
||||||
openPickerForSwatch(swatch, function () {
|
openPickerForSwatch(swatch, function () {
|
||||||
entry._dirtyModes[modeSelect.value] = true;
|
state.dirtyModes[modeSelect.value] = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -179,39 +191,32 @@
|
|||||||
saveFromControls();
|
saveFromControls();
|
||||||
});
|
});
|
||||||
|
|
||||||
entry.appendChild(header);
|
el.appendChild(header);
|
||||||
entry.appendChild(modeRow);
|
el.appendChild(modeRow);
|
||||||
entry.appendChild(colorsDiv);
|
el.appendChild(colorsDiv);
|
||||||
|
|
||||||
entry._nameInput = nameInput;
|
list.appendChild(el);
|
||||||
entry._bgSwatch = bgSwatch;
|
|
||||||
entry._borderSwatch = borderSwatch;
|
|
||||||
entry._textSwatch = textSwatch;
|
|
||||||
entry._modeSelect = modeSelect;
|
|
||||||
|
|
||||||
list.appendChild(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectSubredditOverrides() {
|
function collectSubredditOverrides() {
|
||||||
var overrides = {};
|
var overrides = {};
|
||||||
var entries = document.querySelectorAll(".subreddit-entry");
|
subredditEntries.forEach(function (state) {
|
||||||
entries.forEach(function (entry) {
|
var name = state.nameInput.value.trim().replace(/^r\//, "");
|
||||||
var name = entry._nameInput.value.trim().replace(/^r\//, "");
|
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
var currentMode = entry._modeSelect.value;
|
var currentMode = state.modeSelect.value;
|
||||||
entry._colorState[currentMode] = {
|
state.colorState[currentMode] = {
|
||||||
bg: getColorValue(entry._bgSwatch),
|
bg: getColorValue(state.bgSwatch),
|
||||||
border: getColorValue(entry._borderSwatch),
|
border: getColorValue(state.borderSwatch),
|
||||||
text: getColorValue(entry._textSwatch),
|
text: getColorValue(state.textSwatch),
|
||||||
};
|
};
|
||||||
|
|
||||||
var entryOverrides = {};
|
var entryOverrides = {};
|
||||||
if (entry._hadOverride.light || entry._dirtyModes.light) {
|
if (state.hadOverride.light || state.dirtyModes.light) {
|
||||||
entryOverrides.light = entry._colorState.light;
|
entryOverrides.light = state.colorState.light;
|
||||||
}
|
}
|
||||||
if (entry._hadOverride.dark || entry._dirtyModes.dark) {
|
if (state.hadOverride.dark || state.dirtyModes.dark) {
|
||||||
entryOverrides.dark = entry._colorState.dark;
|
entryOverrides.dark = state.colorState.dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryOverrides.light || entryOverrides.dark) {
|
if (entryOverrides.light || entryOverrides.dark) {
|
||||||
@@ -222,67 +227,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPopupPresets(presets) {
|
function renderPopupPresets(presets) {
|
||||||
var list = document.getElementById("presetList");
|
window.RedditTweaks.presets.render(
|
||||||
list.innerHTML = "";
|
document.getElementById("presetList"),
|
||||||
for (var i = 0; i < presets.length; i++) {
|
presets,
|
||||||
(function (index) {
|
function (p) {
|
||||||
var p = presets[index];
|
setColorValue(document.getElementById("light.subredditBgColor"), p.bg);
|
||||||
var badge = document.createElement("span");
|
setColorValue(document.getElementById("light.subredditBorderColor"), p.border);
|
||||||
badge.className = "rt-preset-badge";
|
setColorValue(document.getElementById("light.subredditTextColor"), p.text);
|
||||||
badge.style.background = p.bg;
|
closePicker();
|
||||||
badge.style.borderColor = p.border;
|
saveFromControls();
|
||||||
badge.style.color = p.text;
|
},
|
||||||
badge.textContent = "Aa";
|
function (index) {
|
||||||
badge.title = "Click to apply to light subreddit colors";
|
window.RedditTweaks.presets.delete(index, renderPopupPresets);
|
||||||
badge.addEventListener("click", function () {
|
}
|
||||||
setColorValue(document.getElementById("light.subredditBgColor"), p.bg);
|
);
|
||||||
setColorValue(document.getElementById("light.subredditBorderColor"), p.border);
|
|
||||||
setColorValue(document.getElementById("light.subredditTextColor"), p.text);
|
|
||||||
closePicker();
|
|
||||||
saveFromControls();
|
|
||||||
});
|
|
||||||
|
|
||||||
var del = document.createElement("span");
|
|
||||||
del.className = "rt-preset-delete";
|
|
||||||
del.textContent = "\u00d7";
|
|
||||||
del.addEventListener("click", function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
deletePopupPreset(index);
|
|
||||||
});
|
|
||||||
badge.appendChild(del);
|
|
||||||
list.appendChild(badge);
|
|
||||||
})(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addPopupPreset() {
|
function addPopupPreset() {
|
||||||
var preset = {
|
window.RedditTweaks.presets.save(
|
||||||
bg: getColorValue(document.getElementById("light.subredditBgColor")),
|
{
|
||||||
border: getColorValue(document.getElementById("light.subredditBorderColor")),
|
bg: getColorValue(document.getElementById("light.subredditBgColor")),
|
||||||
text: getColorValue(document.getElementById("light.subredditTextColor")),
|
border: getColorValue(document.getElementById("light.subredditBorderColor")),
|
||||||
};
|
text: getColorValue(document.getElementById("light.subredditTextColor")),
|
||||||
var settings = await window.RedditTweaks.loadSettings();
|
},
|
||||||
var presets = settings.colorPresets || [];
|
renderPopupPresets
|
||||||
presets.push(preset);
|
);
|
||||||
settings.colorPresets = presets;
|
|
||||||
await window.RedditTweaks.saveSettings(settings);
|
|
||||||
renderPopupPresets(presets);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deletePopupPreset(index) {
|
|
||||||
var settings = await window.RedditTweaks.loadSettings();
|
|
||||||
var presets = settings.colorPresets || [];
|
|
||||||
presets.splice(index, 1);
|
|
||||||
settings.colorPresets = presets;
|
|
||||||
await window.RedditTweaks.saveSettings(settings);
|
|
||||||
renderPopupPresets(presets);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveFromControls() {
|
async function saveFromControls() {
|
||||||
var settings = {
|
var settings = {
|
||||||
enabled: document.getElementById("enabled").checked,
|
enabled: document.getElementById("enabled").checked,
|
||||||
badgeLayout: document.getElementById("badgeLayout").checked,
|
badgeLayout: document.getElementById("badgeLayout").checked,
|
||||||
|
fontOverride: document.getElementById("fontOverride").checked,
|
||||||
darkMode: document.getElementById("darkMode").value,
|
darkMode: document.getElementById("darkMode").value,
|
||||||
|
cardLayout: document.getElementById("cardLayout").checked,
|
||||||
|
cardDensity: document.getElementById("cardDensity").value,
|
||||||
|
cardBackground: document.getElementById("cardBackground").value,
|
||||||
|
cardGap: parseInt(document.getElementById("cardGap").value, 10),
|
||||||
subredditColors: collectSubredditOverrides(),
|
subredditColors: collectSubredditOverrides(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -304,7 +285,12 @@
|
|||||||
|
|
||||||
document.getElementById("enabled").addEventListener("change", saveFromControls);
|
document.getElementById("enabled").addEventListener("change", saveFromControls);
|
||||||
document.getElementById("badgeLayout").addEventListener("change", saveFromControls);
|
document.getElementById("badgeLayout").addEventListener("change", saveFromControls);
|
||||||
|
document.getElementById("fontOverride").addEventListener("change", saveFromControls);
|
||||||
document.getElementById("darkMode").addEventListener("change", saveFromControls);
|
document.getElementById("darkMode").addEventListener("change", saveFromControls);
|
||||||
|
document.getElementById("cardLayout").addEventListener("change", saveFromControls);
|
||||||
|
document.getElementById("cardDensity").addEventListener("change", saveFromControls);
|
||||||
|
document.getElementById("cardBackground").addEventListener("change", saveFromControls);
|
||||||
|
document.getElementById("cardGap").addEventListener("change", saveFromControls);
|
||||||
|
|
||||||
COLOR_KEYS.forEach(function (key) {
|
COLOR_KEYS.forEach(function (key) {
|
||||||
var el = document.getElementById(key);
|
var el = document.getElementById(key);
|
||||||
|
|||||||
Reference in New Issue
Block a user