Skip to content

JeraldPascual/enoughBruh

Repository files navigation

enoughBruh

A cross-browser extension (Chrome MV3 + Firefox) and embeddable web widget that detects doomscrolling and interrupts you with a pixel-art jumpscare, meme burst, and modal prompt.

No tracking. No telemetry. All data local.


Quick Start

git clone https://github.com/youruser/enough-bruh.git
cd enough-bruh
npm install
npm run build

Load in Chrome: chrome://extensions/ → Enable Developer Mode → Load Unpacked → select dist/chrome

Load in Firefox: about:debugging → Load Temporary Add-on → select dist/firefox/manifest.json


Architecture

src/
├── shared/
│   ├── types.ts          # All types, DEFAULT_SETTINGS, SOCIAL_PATTERNS, BUNDLED_MEMES
│   └── storage.ts        # chrome.storage.local / localStorage abstraction + snooze/backoff helpers
├── heuristics/
│   └── detector.ts       # DoomscrollDetector class — rolling window engine
├── audio/
│   └── player.ts         # AudioPlayer — HTMLAudioElement, volume, intensity, reduced-motion
├── visuals/
│   ├── overlay.ts        # OverlayRenderer — meme burst + static safe mode
│   └── modal.ts          # ModalUI — ARIA dialog, EN/TL copy, keyboard accessible
├── content/
│   ├── index.ts          # Content script — scroll listener, evaluation loop, coordinator
│   └── overlay.css       # All injected styles (overlay, modal, jumpscare warning)
├── background/
│   └── index.ts          # Service worker — messaging, cross-tab tracking, programmatic injection
├── popup/
│   ├── popup.html        # Quick-access toolbar popup
│   └── popup.ts
├── options/
│   ├── options.html      # Full settings page
│   ├── options.css       # Pixel/retro UI theme (Press Start 2P)
│   └── options.ts
├── widget/
│   ├── widget.ts         # Standalone embeddable widget (no extension needed)
│   └── demo.html
└── manifests/
    ├── manifest.chrome.json
    └── manifest.firefox.json

scripts/
├── build.mjs             # esbuild pipeline for chrome / firefox / widget targets
├── gen-icons.mjs         # Generates valid PNG icons (16/48/128px) via pure Node zlib
└── gen-memes.mjs         # Generates pixel-art meme PNGs via pure Node zlib (no canvas)

tests/
├── heuristics.test.ts    # 23 tests — DoomscrollDetector, extractDomain, isSocialFeed
├── accessibility.test.ts # 10 tests — OverlayRenderer safe/reduced-motion guards
├── audio.test.ts         # 8 tests  — AudioPlayer volume, sequence, photosensitive
├── storage.test.ts       # 7 tests  — loadSettings, saveSettings, snooze, backoff
└── e2e/widget.spec.ts    # Playwright E2E — widget demo page

Detection Engine (src/heuristics/detector.ts)

DoomscrollDetector tracks three independent signals:

Signal How it works Config key
Scroll rate Counts scroll events in a rolling time window; fires if rate >= threshold scrollThresholdPerSec, rollingWindowSec
Time on site Stamps domainEnterTime on page visit; elapsed time checked on every evaluation — counts while idle sustainedTimeSec
Consecutive social visits Increments per social-feed navigation across tabs consecutiveVisits

Trigger rule:

  • Any 2+ signals fires (scroll+time, scroll+visits, time+visits).
  • Idle-alone path: if countIdleTime=true and current domain is a social feed and timeOnDomain >= sustainedTimeSec, fires regardless of scroll — so just having the tab open counts.

Clock injection: DoomscrollDetector accepts a nowFn?: () => number for deterministic testing without Date.now().


Settings Reference (src/shared/types.ts)

Key Type Default Description
enabled boolean true Master on/off
audioEnabled boolean true Enable all audio
fahhEnabled boolean true Play fahh.mp3 clip
flashbangAudioEnabled boolean true Play flashbang.mp3
volume 0–100 80 Master volume (intentionally loud)
visualsEnabled boolean true Enable meme burst
visualIntensity 0–100 60 Controls image count and animation speed
safeMode boolean false Limits brightness/contrast, caps images at 3
photosensitiveMode boolean false Disables ALL flashing + aggressive audio
scrollThresholdPerSec number 3 Scroll events/sec to count as active scrolling
sustainedTimeSec number 15 Seconds on social feed before time signal fires
consecutiveVisits number 3 Social tab visits in a row to fire visits signal
rollingWindowSec number 10 Window for measuring scroll rate
snoozeDurationMin number 5 Duration of snooze in minutes
exponentialBackoff boolean true Soften intensity on repeated triggers
jumpscareWarning boolean true Show flashing WARNING screen before interrupt
dndEnabled boolean false Enable Do Not Disturb schedule
dndStart / dndEnd string 22:00/08:00 DND window (HH:MM, supports overnight)
trackedDomains string[] [] Allowlist mode — when non-empty, only these domains are monitored
disabledDomains string[] [] Never run on these domains
whitelistedDomains string[] [] Never trigger on these (whitelisted)
localLoggingEnabled boolean false Log trigger events locally to storage
assetPackOptIn boolean false Allow downloading extra meme packs

Build Pipeline

npm run build          # gen-icons + gen-memes + esbuild → dist/chrome, dist/firefox, dist/widget
npm run gen-icons      # Generates assets/icons/icon-{16,48,128}.png via Node zlib (no deps)
npm run gen-memes      # Generates assets/images/meme01-06.png via Node zlib (no deps)
npm run build:watch    # Watch mode
npm run clean          # Delete dist/ and coverage/
npm run lint           # ESLint on src/
npm test               # Jest unit tests (48 tests)
npm run test:e2e       # Playwright E2E (requires build first)
npm run zip:chrome     # Zip dist/chrome → enough-bruh-chrome.zip
npm run zip:firefox    # Zip dist/firefox → enough-bruh-firefox.zip

esbuild compiles all TypeScript targets as IIFE bundles (not ESM) — required for Chrome MV3 service workers and Firefox background scripts.


Permissions

Permission Why
storage Saves settings, snooze state, trigger log to chrome.storage.local
activeTab Read current tab URL for domain checks
alarms Snooze timer via chrome.alarms
scripting Programmatically inject content.js into already-open tabs on install/update
tabs Query existing tabs for programmatic injection
host_permissions: <all_urls> Content script injection on all sites

Site-Specific Monitoring

By default enoughBruh monitors all domains matching SOCIAL_PATTERNS. To restrict it to specific sites only:

  1. Open Settings → [08] DOMAINS
  2. Add domains to Tracked domains (allowlist), e.g.:
    reddit.com
    tiktok.com
    
  3. When the allowlist is non-empty, enoughBruh only runs on those domains and ignores everything else.

Jumpscare Warning

When jumpscareWarning: true (default), a full-screen pixel-art warning flashes for ~2 seconds before the meme burst:

  • Red hazard-stripe border, blinking !! icon
  • Text: WARNING / JUMPSCARE INCOMING / THIS MAY CAUSE A HEART ATTACK
  • Audio fires immediately after at full volume (default 80)
  • Disable in Settings → [03] BEHAVIOR → Jumpscare warning screen

Accessibility & Safety

  • Photosensitivity safe mode — disables ALL flashing visuals and aggressive audio
  • Safe mode — limits brightness/contrast, caps images at 3
  • prefers-reduced-motion — automatically shows static toast + skips flashbang
  • Fullscreen video — visuals suppressed when video is fullscreen
  • ARIA — overlay: role="alert", modal: role="dialog" aria-modal="true", all buttons focusable, Escape dismisses
  • Jumpscare warning is skipped automatically if photosensitiveMode is enabled

Testing

npm test                    # 48 unit tests
npm test -- --coverage      # With coverage report
npm run build && npm run test:e2e   # Playwright E2E

Test setup uses jest-environment-jsdom. E2E tests use Playwright against dist/widget/demo.html.


Privacy

All data is stored locally in chrome.storage.local. No network requests are made by the extension itself. No telemetry, analytics, or tracking. Debug logging is off by default and writes only to local storage when enabled. See PRIVACY.md.


Features

  • Smart detection — Monitors scroll intensity, time on site, and consecutive social media visits using a configurable rolling window.
  • Audio interrupts — Plays a "fahhh" clip followed by an optional flashbang meme sound.
  • Meme image burst — Cascades meme images across the screen in a playful animation.
  • Friendly modal — Offers Snooze, Take a Break, Disable on Site, and Settings options.
  • Exponential backoff — Repeated triggers become softer so it's not annoying.
  • Full settings UI — Audio toggles, visual intensity slider, sensitivity controls, DND schedule, domain whitelist.
  • Privacy-first — All data stored locally. No telemetry by default. Minimum permissions.

Accessibility & Safety

Safety Checklist for Photosensitive Users

  1. Photosensitivity / Epilepsy Safe mode — Enable this in Settings to disable ALL flashing visuals and aggressive audio.
  2. Safe Mode — Limits brightness, contrast, and animation frequency. Caps images at 3.
  3. prefers-reduced-motion — If your OS has this enabled, enoughBruh automatically shows only a static, non-animated toast with no flashbang sound.
  4. Fullscreen video — Visuals are suppressed when a fullscreen video is playing.
  5. Individual toggles — You can disable audio and visuals separately. You can also disable only the flashbang sound while keeping the "fahhh" clip.
  6. To fully disable all effects: Turn off both "Sound" and "Visuals" in the popup, or disable the extension entirely.

ARIA & Keyboard

  • Overlay has role="alert" and aria-live="assertive".
  • Modal has role="dialog" and aria-modal="true".
  • All buttons are keyboard-focusable. Modal can be dismissed with Escape.
  • Screen-reader-only text provides context for assistive tech.

Quick Start

Prerequisites

  • Node.js ≥ 18
  • npm

Install & Build

git clone https://github.com/youruser/enough-bruh.git
cd enough-bruh
npm install
npm run build

Load in Chrome

  1. Go to chrome://extensions/
  2. Enable Developer mode
  3. Click Load unpacked
  4. Select the dist/chrome folder

Load in Firefox

  1. Go to about:debugging#/runtime/this-firefox
  2. Click Load Temporary Add-on
  3. Select dist/firefox/manifest.json

Try the Widget Demo

After building, open dist/widget/demo.html in any browser — no extension needed.


Testing

# Unit tests (Jest)
npm test

# Unit tests with coverage
npm test -- --coverage

# E2E tests (Playwright) — requires build first
npm run build
npm run test:e2e

Project Structure

enough-bruh/
├── assets/
│   ├── audio/          # fahh.mp3, flashbang.mp3 (replace placeholders)
│   ├── icons/          # Extension icons (replace placeholders)
│   └── images/         # meme01–06.webp (replace placeholders)
├── src/
│   ├── shared/
│   │   ├── types.ts    # All types, defaults, social patterns
│   │   └── storage.ts  # chrome.storage / localStorage abstraction
│   ├── heuristics/
│   │   └── detector.ts # Doomscroll detection engine
│   ├── audio/
│   │   └── player.ts   # Audio playback module
│   ├── visuals/
│   │   ├── overlay.ts  # Meme image burst renderer
│   │   └── modal.ts    # Interruption modal/toast
│   ├── content/
│   │   ├── index.ts    # Content script (scroll listener + coordinator)
│   │   └── overlay.css # All overlay/modal styles
│   ├── background/
│   │   └── index.ts    # Service worker (messaging, cross-tab tracking)
│   ├── popup/
│   │   ├── popup.html  # Quick-access popup UI
│   │   └── popup.ts
│   ├── options/
│   │   ├── options.html # Full settings page
│   │   ├── options.css
│   │   └── options.ts
│   ├── widget/
│   │   ├── widget.ts   # Standalone embeddable widget
│   │   └── demo.html   # Demo page with controls
│   └── manifests/
│       ├── manifest.chrome.json
│       └── manifest.firefox.json
├── tests/
│   ├── setup.ts
│   ├── heuristics.test.ts
│   ├── accessibility.test.ts
│   ├── audio.test.ts
│   ├── storage.test.ts
│   └── e2e/
│       └── widget.spec.ts
├── scripts/
│   └── build.mjs       # esbuild build script
├── .github/workflows/
│   └── ci.yml           # GitHub Actions CI
├── package.json
├── tsconfig.json
├── jest.config.cjs
├── playwright.config.ts
├── .eslintrc.cjs
├── .editorconfig
├── .gitignore
├── README.md
├── PRIVACY.md
└── PUBLISHING.md

Privacy

See PRIVACY.md for full details.

TL;DR:

  • All data stays on your device.
  • No telemetry, analytics, or tracking by default.
  • No network requests unless you explicitly opt into asset packs.
  • Minimum browser permissions: storage, activeTab, alarms.
  • Debug logging is local-only and off by default.

Publishing

See PUBLISHING.md for step-by-step guides for Chrome Web Store and Firefox Add-ons.


🛠️ Development

# Watch mode (rebuilds on file change)
npm run build:watch

# Lint
npm run lint

# Clean
npm run clean

Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure npm test and npm run lint pass
  5. Submit a PR

About

stop the doomscroll with another generic interruptor

Resources

Stars

Watchers

Forks

Contributors