Latest Version: 2.6.1 | See What's New
- What It Does
- Features
- Prerequisites
- Installation
- Development
- How It Works
- Using the Badge
- Menu Options
- Configuration Tips
- Troubleshooting
- Privacy & Storage
- Keyboard Shortcuts
- Advanced Configuration
- Support & Feedback
- Tips for Best Results
- Provenance
- Licence
This userscript automatically detects and neutralizes sensationalist headlines on websites using OpenAI's API. It tones down dramatic language while preserving factual content, making your browsing experience calmer and more informative.
New in 2.3.0: Fixed intrusive 400 error popups, improved input sanitization, badge locked to right edge, and CSS class namespacing for better cross-site compatibility.
From 2.2.0: Updated AI model pricing to current OpenAI rates. Removed manual pricing configuration - pricing now syncs automatically with model selection.
From 2.1.0: AI model selection - choose from 5 OpenAI models. Default changed to GPT-4.1 Nano Priority for fast headline processing.
From 1.8.0: Streamlined to focus solely on headline neutralization. Body text simplification has been removed.
From 1.7.0: Element Inspection Mode - powerful diagnostic tool to troubleshoot selector configurations. Click 🔍 Inspect in the badge, then click any element to see exactly why it's being processed or ignored, with one-click fixes.
- Automatic headline detection using smart heuristics
- AI model selection - Choose from 5 OpenAI models with different price/quality tradeoffs, or define a custom model ID with your own pricing
- Adjustable neutralization strength (5 levels from Minimal to Maximum)
- Element Inspection Mode - Diagnostic tool to troubleshoot why elements are/aren't being processed
- Global + per-domain CSS selector configuration
- Per-domain additions to selectors and exclusions
- Per-domain enable/disable control (allowlist or denylist mode)
- Intelligent caching to minimize API calls
- On-page badge to restore/reapply changes
- Visual flash animation when headlines are neutralized
- API usage tracking and cost monitoring with real token counts and configurable pricing
- Sanity check for long headlines - Warns before processing text > 500 characters (prevents token waste)
- Stats dialog shows API usage, cache statistics, and all changes
- Browser userscript manager - Install one of these:
- https://www.tampermonkey.net/ (Chrome, Firefox, Edge, Safari, Opera)
- https://www.greasespot.net/ (Firefox)
- https://violentmonkey.github.io/ (Chrome, Firefox, Edge)
- OpenAI API key
-
Sign up at https://platform.openai.com/
-
Generate an API key from your account settings
-
The script uses GPT-4.1 Nano Fast by default (fast processing for headlines)
- Install the userscript:
- Download
dist/headlines-neutralizer.jsfrom the repository - Open it in your browser (or drag & drop into browser)
- Your userscript manager should detect it and prompt you to install
- Click "Install"
- First-time setup:
- When you first visit a website after installation, a welcome dialog will appear
- Click "Set Up API Key" to begin configuration
- Follow the guided steps to get your OpenAI API key:
- Visit OpenAI's API keys page (link provided in dialog)
- Sign in or create an account
- Create a new secret key
- Copy and paste it in the next dialog
- Click "Validate" to test your key, then "Save"
- The script will automatically enable itself on all websites
- You're done! The script will now run automatically on websites.
This project uses a modular ES6 architecture with a build system for easy development and testing.
# Install dependencies
npm install
# Build the userscript
npm run build
# Output: dist/headlines-neutralizer.jsDevelopment workflow:
npm run dev # Watch mode - auto-rebuild on changes
npm run build # Single buildThe source code is organized into modules:
src/main.js- Application entry pointsrc/modules/- Individual feature modules (API, DOM, cache, etc.)src/banner.txt- Userscript metadata header
After building, install dist/headlines-neutralizer.js in your userscript manager.
The project includes a comprehensive test suite with 326 tests covering unit, integration, and E2E scenarios.
npm test # Unit + integration tests (326 tests, 95.7% passing)
npm run test:e2e # E2E browser tests
npm run test:all # All tests
npm run test:coverage # With coverage reportInteractive test UI:
npm run test:ui # Vitest UI
npm run test:e2e:ui # Playwright UITest coverage:
- Cache operations (LRU eviction, storage, retrieval)
- Selector matching (globs, regex, domain patterns)
- DOM manipulation (element detection, replacement, restoration)
- Badge UI (creation, state, positioning)
- Inspection mode (element finding, highlighting)
- API integration (OpenAI calls, token tracking, pricing)
- Storage (GM fallback chain, persistence)
- Scoring (headline heuristics, filtering)
- Utilities (text processing, DOM checks)
For detailed documentation:
-
BUILD.md - Build system, architecture, and releases
-
TESTING.md - Testing guide
-
TESTING-QUICKSTART.md - Quick start
The script uses intelligent heuristics to find headlines:
- Analyzes HTML tags (h1, h2, h3, etc.)
- Evaluates CSS properties (font size, weight)
- Scores text content (word count, punctuation, capitalization)
- Filters out UI elements, navigation, and non-headlines
- Headlines are detected as you scroll (visible-only mode)
- Text is sent to OpenAI API in batches (max 24 at once)
- Neutralized versions are cached per-domain
- Changes are applied with a brief highlight animation
- Original text is preserved and can be restored anytime
A small badge appears in the bottom-right corner of pages where headlines have been neutralized:
┌─────────────────────────────────────┐
│ [H: neutral] (12) │
│ Neutralize Headlines userscript │
└─────────────────────────────────────┘
Badge Controls:
- H: neutral / H: original - Toggle between neutral and original headlines
- 🔍 Inspect - Activate inspection mode to diagnose why elements are/aren't being processed
- (12) - Number of headlines neutralized on this page
Inspection Mode:
- Click the 🔍 Inspect button to activate
- Click any element on the page to see why it's matched/excluded
- Shows detailed diagnostic information
- Action buttons to add selectors or remove exclusions
- Press ESC to exit inspection mode
To hide the badge: Use the menu option "Toggle badge (ON/OFF)"
Access the menu through your userscript manager icon:
- Set / Validate OpenAI API key - Add or test your API key
- AI model - Select from 5 OpenAI models with different price/quality tradeoffs, or a custom model
- GPT-5 Nano - $0.05/$0.40 per 1M tokens (cheapest)
- GPT-5.6 Luna - $0.20/$1.20 per 1M tokens (newest generation, low cost)
- GPT-4.1 Nano Fast (recommended) - $0.20/$0.80 per 1M tokens (fast)
- GPT-5 Mini Fast - $0.45/$3.60 per 1M tokens (better + faster)
- GPT-5.6 Terra Fast - $4.00/$24.00 per 1M tokens (newest flagship tier + faster)
- Custom model - enter any OpenAI model ID with your own pricing, reasoning effort, and Fast mode flag
- Note: GPT-5 models are reasoning models and use minimal reasoning instead of temperature
- Pricing is automatically updated when you change models
- If a previously selected model is removed in a later version, the script falls back to the default and shows a one-time notice
Global Settings (apply to all domains):
- Edit GLOBAL target selectors - Base CSS selectors for all websites
- Examples:
h1, h2, .article-title, [itemprop="headline"]
- Examples:
- Edit GLOBAL excludes: elements (self) - Base element exclusions for all websites
- Examples:
.sponsored, .ad-title, h4.category
- Examples:
- Edit GLOBAL excludes: containers (ancestors) - Base container exclusions for all websites
- Examples:
header, footer, nav, aside
- Examples:
Domain-Specific Additions (for current domain only):
- Edit DOMAIN additions: target selectors - Additional selectors for this domain
- Shows global settings (read-only) + domain additions (editable)
- Domain selectors are added to global ones (not replaced)
- Edit DOMAIN additions: excludes elements - Additional element exclusions for this domain
- Edit DOMAIN additions: excludes containers - Additional container exclusions for this domain
- Current page: ENABLED/DISABLED - Shows status for current domain (informational)
- Domain mode switcher - Toggle between two modes:
- "Allowlist only" - Only runs on domains you explicitly add
- "All domains with Denylist" - Runs everywhere except disabled domains
- Add/Remove this domain - Quickly enable/disable the current site
- Neutralization strength - Adjust how aggressively headlines are rewritten (5 levels from Minimal to Maximum)
- Toggle auto-detect (ON/OFF) - Enable/disable automatic headline detection
- Turn OFF to rely only on manual CSS selectors
- Toggle DEBUG logs (ON/OFF) - Show detailed console logs
- Toggle badge (ON/OFF) - Show/hide the on-page badge
- Show stats & changes (diff audit) - View cache statistics and all changes
- API Usage & Cost - Real-time tracking with actual token counts from OpenAI
- Total input/output tokens and costs since installation
- Pricing based on selected AI model
- API stats persist and are independent from clearable page stats
- Displays headline cache size
- Shows all original → neutralized headline changes on current page
- API Usage & Cost - Real-time tracking with actual token counts from OpenAI
- Process visible now - Manually trigger processing of visible headlines
- Flush headline cache & rerun - Clear headline cache and reprocess everything
- Reset stats counters - Reset the count shown in the badge
How it works:
- Global settings apply to all websites by default
- Domain-specific additions are added on top of global settings for specific domains
- Final selectors = Global + Domain-specific (merged together)
Best practices:
- Set up global selectors that work on most sites (h1, h2, h3, etc.)
- Add domain-specific selectors only when a site needs special handling
- Use global excludes for common patterns (header, footer, nav)
- Use domain excludes for site-specific elements to skip
Example workflow:
- Global selectors:
h1, h2, h3, .article-title - Visit reddit.com → Add domain selector:
.post-title - Visit news.ycombinator.com → Add domain selector:
.storylink - Each domain gets: Global selectors + Their specific additions
When you open a domain-specific editor, you'll see two sections:
┌─────────────────────────────────────────────┐
│ Global settings (read-only): │
│ ┌─────────────────────────────────────────┐ │
│ │ h1 │ │ ← Gray background
│ │ h2 │ │ Cannot edit
│ │ h3 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Domain-specific additions (editable): │
│ ┌─────────────────────────────────────────┐ │
│ │ .post-title │ │ ← White background
│ │ .story-headline │ │ Edit here
│ │ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
- Top section: Shows global settings for reference (read-only, gray)
- Bottom section: Add domain-specific selectors (editable, white)
- Final result combines both sections
Try Auto-detection first (enabled by default). If it doesn't work, add manual selectors:
For all websites:
- Menu → "Edit GLOBAL target selectors"
- Add common selectors like:
h1, h2, h3, .headline
For a specific website:
- Visit the website
- Right-click a headline → "Inspect Element"
- Note the CSS selector (e.g.,
.story-headline) - Menu → "Edit DOMAIN additions: target selectors (hostname)"
- Add the selector in the bottom editable section
- The top section shows global selectors for reference (read-only)
To exclude specific sections from processing:
Global exclusions (all websites):
- Menu → "Edit GLOBAL excludes: containers (ancestors)"
- Add:
nav, footer, aside, .sidebar
Domain-specific exclusions:
- Visit the website
- Menu → "Edit DOMAIN additions: excludes containers (hostname)"
- Add site-specific containers to exclude
Allowlist Mode (safest):
- Menu → Switch to "Allowlist only"
- Visit sites you want to neutralize
- Menu → "Add this domain to allowlist" for each site
Denylist Mode (works everywhere):
- Default mode - runs on all sites
- Visit sites you DON'T want affected
- Menu → "Disable on this domain"
Show Included Elements - Visualize which elements will be processed (green) or excluded.
- Check if domain is disabled: Menu → Current page status
- Verify API key: Menu → "Set / Validate OpenAI API key" → Validate
- Check browser console (F12) for errors
- Enable DEBUG logs: Menu → "Toggle DEBUG logs (ON)"
- Use Inspection Mode (Recommended):
- Click the 🔍 Inspect button in the badge
- Click the headline you want to check
- The diagnostic dialog will show exactly why it's not being processed:
- ❌ Not matched by selectors → Use "Add as Global/Domain Selector" button
⚠️ Excluded by a rule → Use "Remove Exclusion" button- ✅ Matched → Check if it's over 500 characters (sanity check blocking it)
- Try manual selectors:
- For all sites: Menu → "Edit GLOBAL target selectors"
- For this site only: Menu → "Edit DOMAIN additions: target selectors"
- Adjust exclusions: You might be excluding too much
- Check both global and domain-specific exclusions
- Check if publisher opted out: Console will show "publisher opt-out detected"
- Use Inspection Mode:
- Click the 🔍 Inspect button in the badge
- Click the unwanted element being processed
- Check which selector is matching it
- Use the diagnostic dialog to add exclusions or remove problematic selectors
- Disable auto-detect: Menu → "Toggle auto-detect (OFF)"
- Use only manual selectors for precise targeting
- Add problematic elements to exclusions:
- For all sites: Menu → "Edit GLOBAL excludes"
- For this site only: Menu → "Edit DOMAIN additions: excludes"
- 401 Unauthorized: Invalid API key
- 429 Rate Limited: Too many requests, wait a minute
- 400 Bad Request: Page text may contain parsing errors
Some sites can opt out by adding this meta tag:
<meta name="neutralizer" content="no-transform">
The script will respect this and disable itself.
- API key: Stored locally (GM storage → localStorage → memory fallback)
- API usage stats: Stored locally, persists across sessions (input/output tokens, call counts)
- API pricing configuration: Stored locally, user-configurable
- Global settings: Stored locally in your browser
- Domain-specific settings: Stored locally per-domain
- Headline cache: Stored locally, per-domain, up to 1500 entries
- Nothing is sent to external servers except OpenAI API calls
- Headline text for neutralization
- No personal data, cookies, or browsing history
When editing configuration modals:
- Esc - Close modal
- Ctrl/Cmd + Enter - Save and close
Use the userscript menu: AI model (current model name) to select from 5 built-in models or define a custom one. The menu shows current model and opens a selection dialog with pricing information. Changing models clears the cache and updates pricing automatically.
Edit line 30:
maxBatch: 24, // Reduce to 12 if hitting rate limits
Edit line 33:
highlight: false,
Edit lines 49-50:
scoreThreshold: 75, // Lower = more aggressive (60-90 range)
topKPerCard: 1, // Increase to 2-3 for multiple headlines per card
- Issues: Report bugs or request features on the project's repository
- Console Logs: Enable DEBUG mode for detailed troubleshooting info
- Stats Dialog: Use "Show stats & changes" to monitor API usage, costs, cache statistics, and verify changes
- Start with auto-detect ON - Let the script learn the site structure
- Set up global selectors first - Use settings that work on most sites
- Add domain-specific selectors sparingly - Only for sites that need special handling
- Check the stats dialog - Monitor API usage and costs, verify neutralizations, and track cache efficiency
- Use allowlist mode for sensitive sites - Avoid false positives
- Keep cache enabled - Dramatically reduces API costs on revisits
- Toggle badge OFF for clean UI - Access controls via menu instead
This UserScript was authored by Fanis Hatzidakis with assistance from large-language-model tooling (ChatGPT and Claude Code). All code was reviewed, tested, and adapted by Fanis.
Copyright (c) 2025 Fanis Hatzidakis
Licensed under PolyForm Internal Use License 1.0.0
See LICENCE.md








