r/userscripts • • 3d ago

anything to stop videos from auto-pausing when scrolling (on reddit)?

1 Upvotes

I'm looking for a script that will stop reddit from pausing videos when reddit is scrolled so that the video is partially hidden. I already found scripts that will prevent pausing when losing focus, but it's not the same thing.


r/userscripts • • 4d ago

Two userscripts for WEBTOON: a hand-written dark theme and a chapter preloader

5 Upvotes

Both are for webtoons.com, and they're tested together.

Webtoons Dark Mode is a dark theme written by hand for WEBTOON's pages, not a filter, so the comic panels are never touched and white pop-ups don't slip through. Some parts are restyled the way I like reading them (comments as one clean list, read and unread episodes easy to tell apart). It also covers the mobile site. Toggle with Alt+Shift+T, optional panel dim with Alt+Shift+N.

Install from Greasy Fork · Source on GitHub

Webtoons Chapter Preloader starts loading every panel of a chapter while the page is still opening, in tiers (panel 1, then the first screen, then the rest), and decodes panels up to three screens ahead, so a fast scroll doesn't hit blank images. @grant none, runs only on reader pages.

Install from Greasy Fork · Source on GitHub

Neither makes network requests of its own. The theme only uses GM storage and menu commands; the preloader only touches the page's <img> elements. MIT, works in Tampermonkey and Violentmonkey. Both also exist as browser extensions (Toonlight and Toonlight Preloader) for people without a userscript manager.

Downside: the preloader downloads the whole chapter up front (usually 10–20 MB). I'm the author, so feedback and bug reports are welcome.


r/userscripts • • 4d ago

[Userscript] Reddit Gallery to Grid — view all gallery images at once in an ergonomic justified layout (no more swiping carousels)

Post image
10 Upvotes

Hey everyone!

I got really tired of Reddit’s multi-image posts forcing you to click through arrows one by one in a carousel, so I made a lightweight userscript to fix it.

It completely replaces the native carousel with a clean, justified gallery layout (similar to Telegram / Flickr / Google Photos albums).

Features:

  • See all images upfront: No more arrow-clicking or swiping. All pictures are visible immediately.
  • Preserves original aspect ratios: Portrait and landscape images adapt naturally side-by-side with zero ugly cropping.
  • Forces all photos to load: Bypasses Reddit’s lazy-loader that often hides slides after the 3rd image.
  • One-click full res: Clicking any thumbnail directly opens the original uncompressed image (i.redd.it) in a new tab.
  • Lightweight: Works with the modern Reddit UI (Shreddit).

🔗 Tampermonkey: https://pastebin.com/J3awrKUG

Hope you find it useful! Feedback and suggestions are always welcome.


r/userscripts • • 6d ago

[NYT Connections] Color-code and auto-sort tiles before you submit (desktop + iOS Safari)

Post image
2 Upvotes

A userscript for NYT Connections that lets you mark each tile with the color you think it is, right on the board. Marked tiles move into their own rows, purple to yellow by default for reverse rainbows, or yellow to purple, or unsorted. Once three colors are done, the last four fill in automatically, and a Go button selects and submits each group in order.

Optional extras (off by default): split a tile between two candidate colors, a list of wrong guesses that flags one-aways, and Y/G/B/P letters for colorblind players.

A few details for this crowd:

- Single file, no dependencies, u/grant none

- Tested in Tampermonkey (Chrome) and the Userscripts app (iPhone Safari), with a one-row palette on phones

- Marks and the palette live in closed shadow roots, so dark-mode extensions like Dark Reader don't recolor them

- Marks are saved per puzzle in localStorage and stay in sync across tabs

- Behavior tests run it against a simulated board in jsdom

Built with help from Claude.

Greasy Fork: https://greasyfork.org/en/scripts/599362-connections-color-marker

GitHub: https://github.com/dividedby/games-scripts/tree/main/connections#readme

Feedback, feature requests and bug reports welcome!


r/userscripts • • 7d ago

I made a userscript that finds subs you're subscribed to but haven't put in any custom feed (and subs in multiple feeds)

2 Upvotes

If you use custom feeds (multireddits) to organize your subscriptions, it's hard to tell which subs you forgot to add to a feed, or which ones ended up in more than one.

Feed Organizer Auditor is a small userscript that shows you both lists:

  • Subreddits you're subscribed to but that aren't in any custom feed
  • Subreddits that appear in more than one custom feed

How it works

  1. Install a userscript manager (tested with Violentmonkey, which is free and open source).
  2. Install the script: https://raw.githubusercontent.com/kritikostony/reddit-multireddit-auditor/main/feed-organizer-auditor.user.js
  3. Open any reddit.com page while logged in (works on www and old.reddit).
  4. Click the Violentmonkey icon → Audit my custom feeds. Results show up in a panel at the bottom right.

Privacy

  • Runs only in your browser, using your existing Reddit login. No server, no API key, no third-party requests.
  • Read-only. It only reads your custom feeds list and your subscriptions list, and changes nothing.
  • Stores nothing; the results are gone when you close the page.

Source and README: https://github.com/kritikostony/reddit-multireddit-auditor

I wrote it with help from an AI coding assistant and reviewed and tested it myself. Feedback and bug reports welcome.


r/userscripts • • 8d ago

Looking for brief help for a script

3 Upvotes

I've got a script that someone else provided for me (I don't code). I've tried looking into how to make it work only in some places and not on others, on the same site, and found that the "exclude" modifier just makes it not fire on that site at all, so I'm not sure what's wrong. It's set up to create a popup when I click into a reply field on Bluesky with a "hey do you actually need to say anything here" message in it, BUT I talk to my partner on Bsky all the time and I want it to not do the popup there (it gets annoying when it shows up half the time I tab back over there). I tried having it include Bsky as a whole and exclude just that one message URL, I tried having it only include bsky.app/profile/* and exclude the message URL, and that just leads to it not working at all.

The script is below, I don't know how to make Reddit not auto-convert the @ lines into user links, so just pretend they're correct.

// ==UserScript==

// u/nameI am a dumbass

// u/namespacehttp://bsky.app/

// u/version2026-10-02

// u/description try to take over the world!

// u/authorYou

// u/matchhttps://bsky.app/profile/*

// u/excludehttps://bsky.app/messages/*

// u/iconhttps://www.google.com/s2/favicons?sz=64&domain=bsky.app

// u/grantnone

// ==/UserScript==

(function() {

'use strict';

document.addEventListener('focusin', e => { const box = e.target.closest('textarea, [contenteditable="true"]'); if (!box || box.dataset.ok) return; box.blur(); if (confirm('Do you really need to say anything here, dipshit?')) { box.dataset.ok = 1; box.focus(); } });

})();


r/userscripts • • 9d ago

F.B. Purity stopped blocking Facebook ads, so I went ahead and made a simple, temporary fix.

11 Upvotes

https://greasyfork.org/en/scripts/598911-fb-feed-cleaner

Script starts with Hiding Ads off by Default, and puts an red outline on Sponsored posts. Mainly done to try and detect false positives: https://i.imgur.com/hM0Gx6d.png

Just go to Greasemonkey/Tampermonkey and toggle Hide Hits Off to Hide Hits On:

https://i.imgur.com/XO9ZMAq.png

https://i.imgur.com/OUgLOa5.png

Facebook changes their site frequently, so it'll probably break eventually. But it seems to be working for me so far on English.


r/userscripts • • 11d ago

Is there any way to bulk remove Snapchat friends?

Thumbnail
2 Upvotes

r/userscripts • • 13d ago

Triage: bulk delete, archive and rename ChatGPT chats without the "Too many requests" lockout

9 Upvotes

I made a userscript for cleaning up ChatGPT's chat history, and I'd like feedback from people who know userscripts.

The problem: ChatGPT has no multi-select, and deleting chats quickly gets your conversations locked ("Too many requests... We've temporarily limited access to your conversations"). The sidebar fires several list reloads per delete, and that list endpoint is what gets rate-limited.

What the script does: - Adds a cleanup panel to chatgpt.com: your chats on the left (oldest first), the selected chat readable on the right - Mark with a key: D delete, A archive, R rename, P protect. Nothing changes until you press Run Queue - Runs one change every 8 seconds; on a 429 it stops, honours Retry-After (or backs off 2/5/15/30 min) and resumes by itself - Queue, marks and progress survive reloads; an interrupted run picks up where it stopped - Optional Markdown backup before deleting, and a check at the end that everything really went - Project chats are kept out of the main list by default

Tech notes: - A single file, @grant none, @match https://chatgpt.com/*, no @require, no remote code - The UI lives in a Shadow DOM - The only requests are to chatgpt.com's own web API, and a Network panel lists every one - The list is virtualised, so 3,000 chats stay fast

Links: - Demo on a fake account (runs in your browser, nothing to install): https://nasircissistic.github.io/chatgpt-triage/demo/ - Greasy Fork: https://greasyfork.org/en/scripts/597323-triage - Source (MIT): https://github.com/NASIRCISSISTIC/chatgpt-triage

On Chrome and Edge, Tampermonkey needs "Allow User Scripts" switched on in its extension details. Tested on a personal account; Team/Enterprise workspaces aren't tested yet. Bug reports welcome!


r/userscripts • • 16d ago

Collection Sorter

Thumbnail
3 Upvotes

r/userscripts • • 18d ago

I built an Ultimate Audio Enhancer userscript

Post image
28 Upvotes

I built a userscript that improves audio quality directly in the browser.

Ultimate Audio Enhancer adds real-time:

• 10-band equalizer
• Bass & treble boost
• AutoEq headphone profiles
• Volume boost
• DFX audio enhancement
• Reverb, echo, chorus & other effects
• Stereo / 3D surround enhancement
• High-pass & low-pass filters
• Configurable limiter
• Custom presets
• Support for audio and videos across many websites

It runs through Tampermonkey/Violentmonkey and doesn't require separate audio software or a virtual audio cable.

I've been developing and optimizing it for a while and would appreciate feedback, especially regarding sound quality, performance and compatibility on different websites.

GreasyFork:
https://greasyfork.org/scripts/595277-ultimate-audio-enhancer

GitHub:
https://github.com/nextscript/Ultimate-Audio-Enhancer


r/userscripts • • 19d ago

I built an Ultimate Video Enhancer userscript with real-time sharpening, HDR and color controls

Post image
17 Upvotes

Hey,

I’ve been working on a userscript called Ultimate Video Enhancer that adds real-time video enhancement directly in the browser.

It works with Tampermonkey/Violentmonkey and is designed to improve online video without requiring a separate desktop application.

Current features include:

  • Real-time sharpening
  • HDR-style enhancement
  • Contrast, brightness and saturation controls
  • Color adjustments
  • Multiple video filters
  • GPU/WebGL acceleration
  • Custom presets
  • Import / export of settings
  • Support for videos across many websites

I’m still actively improving performance, compatibility and image quality.

I’d especially appreciate feedback about:

  • websites where it doesn’t work correctly
  • GPU / CPU performance
  • useful filters or controls that are still missing
  • visual artifacts or compatibility issues
  • ideas for future features

GreasyFork:
https://greasyfork.org/scripts/561189-ultimate-video-enhancer-sharpen-hdr-color-boost

GitHub:
https://github.com/nextscript/Ultimate-Video-Enhancer


r/userscripts • • 19d ago

TabCircle: Native macOS & Linux MRU tab switcher (Ctrl + Tab) for Chromium browsers with visual previews

Thumbnail
1 Upvotes

r/userscripts • • 24d ago

Bookmark ChatGPT answers and ask side questions without losing your place

6 Upvotes

I made a Tampermonkey userscript that adds bookmarks and side conversations to ChatGPT. Highlight a sentence to bookmark it or ask about it in a separate chat using ChatGPT’s branching feature, keeping the conversation history and available attachments.

It also remembers where you were when you click ChatGPT’s down arrow, so you can jump back to what you were reading.

Install: https://greasyfork.org/en/scripts/596830-chatgpt-workflow-toolkit

If you use ChatGPT for studying, coding, or long conversations and keep losing your place, give it a try.


r/userscripts • • 24d ago

PrismCheat — an open-source Survev cheat with automated client updates

Thumbnail
3 Upvotes

r/userscripts • • 25d ago

I made a script to delete all your tweets. No need to pay for that anymore

Thumbnail
8 Upvotes

saving this


r/userscripts • • 26d ago

PixelCast - Fishing Macro for Roblox game FishIt

Thumbnail
2 Upvotes

r/userscripts • • 28d ago

InfiniteCourses.Org Direct Download Link Injector script

6 Upvotes

InfiniteCourses.Org Direct Link Injector

Version Greasy Fork Tampermonkey Violentmonkey License: MIT

A lightweight userscript designed to intercept and display direct download links on InfiniteCourses download pages. It seamlessly extracts backend data to bypass hidden links and injects a clean, categorized user interface natively into the page.

[!IMPORTANT] Dynamic Extraction:
This script fetches the page's HTML and parses the __NEXT_DATA__ JSON payload to uncover hidden download URLs. If links fail to appear in the custom section, simply reloading the page will trigger a fresh fetch.

[!TIP] Native UI Integration:
The script injects a visually appealing, frosted-glass themed container right below the standard .download-section. Interactive buttons with SVG icons and hover animations are generated for each extracted link part.

✨ Features

  • Direct Link Extraction: Intercepts backend data by parsing Next.js page properties to find hidden direct URLs.
  • Smart Categorization: Automatically identifies and groups download URLs into specific providers like Pixeldrain and Send.now, categorizing the rest as "Other".
  • Seamless UI Injection: Creates a custom download section with a modern UI, utilizing styling like backdrop-filter: blur(8px) and hover transformations without disrupting the host site's layout.
  • Dynamic Polling: Uses a 500ms interval check to ensure the custom direct links section is injected as soon as the target .download-section appears in the DOM.
  • Error Handling: Gracefully handles missing link data or network errors by displaying helpful error messages directly in the injected UI.

🌐 Supported Domains

Platform Domains Direct Extraction Categorization Notes
InfiniteCourses www.infinitecourses.org/* ✅ Active ✅ Active Specifically targets the /course/ paths to render the injector.

🚀 Installation

Step 1: Install a Userscript Manager

Make sure you have an active userscript extension installed in your browser:

Step 2: Install the Script

Install or update to version 1.0 via your manager:
👉 Install from Greasy Fork

Step 3: Usage

Navigate to any course download page on InfiniteCourses. Scroll down to the download section, and the script will automatically append a "DIRECT DOWNLOADS" panel with your extracted links.

📄 License

This script, InfiniteCourses.Org Direct Link Injector, is open source and distributed under the MIT License.

🛠️ Author & Support

  • Developed and maintained by rehan dilawar.
  • If you encounter fetching errors or broken selectors, feel free to open an issue or contribute on GitHub.

<p align="center"> <em>If you found this script helpful, please consider starring the repository!</em> </p>


r/userscripts • • Sep 15 '26

I made a userscript to auto-scroll Reddit with the numpad – is this useful? Feedback welcome.

Post image
5 Upvotes

I often read Reddit on a second monitor while doing something else, e.g., full-screen gaming, and wanted to control it without switching windows. So I wrote a userscript for it, and I'd like some honest feedback before I take it further.

What it does

  • Auto-scrolls the feed or a thread. Num + / − change speed, Num 0 pauses
  • Num 8 / 2 select posts, Enter opens one, . goes back
  • Num 4 / 6 step through gallery images
  • A small on-screen readout shows the state, the speed and the selected post

The keys work while the browser has focus. An optional Windows helper (a small Python program that only talks to the script over 127.0.0.1) keeps them working while another app, e.g. a full-screen game, has focus.

Install: Greasy Fork · Source on GitHub

Tested in Firefox + Violentmonkey and Chrome + Tampermonkey. Current Reddit only, not old.reddit.

What I'd like to know

  • Would you use this, or does something similar already exist?
  • Is anything confusing in the setup or the controls?
  • Which keys or features would you expect?
  • Any bugs, especially on setups I haven't tried

r/userscripts • • Sep 15 '26

Made a userscript for checking out only selected items from your Steam cart

2 Upvotes

I got tired of having to remove stuff from my Steam cart whenever I only wanted to buy a few things, so I made a userscript for it.

It adds a small selection panel on the cart page. You pick the items you actually want to buy there, it shows the total for those items, and then checks out only that selection.

I tried to keep it non-destructive where possible. For normal items it uses a separate temporary cart, so the original Steam cart stays untouched. If Steam's pricing gets weird with account-specific discounts, bundles, gifts, etc., it falls back to temporarily removing the unselected items and restoring them afterwards.

There’s also recovery logic in case checkout or restore fails, because I really didn’t want this thing eating someone’s cart.

Works with Tampermonkey / Violentmonkey / AdGuard for Windows.

Greasy Fork:
Cart Selection for Steam

GitHub:
[https://github.com/HiSkyZen/steam-cart-selection]()

Install:
[https://raw.githubusercontent.com/HiSkyZen/steam-cart-selection/main/cart-selection-for-steam.user.js]()

Steam’s cart APIs are internal, so Valve can break this whenever they feel like it. If anyone finds a weird edge case with bundles, discounts, gifts, etc., let me know.


r/userscripts • • Sep 13 '26

Add *Arr Controls Directly To IMDB

14 Upvotes

GitHub: https://github.com/TechSquidTV/arr-userscripts

A collection of user scripts that add Sonarr and Radarr controls directly in IMDB.

It also contains a script that does the same in Plex.


r/userscripts • • Sep 12 '26

[User Script] Unround Everything Everywhere - For Those Who Prefer Sharp, Squared UIs

Thumbnail greasyfork.org
2 Upvotes

r/userscripts • • Sep 11 '26

Google Snake Nos Cheat Client

6 Upvotes

Made a hacker-themed cheat menu for Google Snake as a bookmarklet — no extension, no Tampermonkey, just a bookmark you click.

It's got 23 real mods across 3 categories: speed control (turbo up to 4x, slow-mo, freeze time, a manual slider you can drag anywhere from 0.1x to 6x), visual stuff (invert, matrix vision, screen shake, a glitchy scanline overlay, rainbow cycle, upside-down mode), and automation (stops the game from pausing when you alt-tab, anti-AFK, a genuinely random autopilot since there's no way to actually read the board state to cheat with, rapid-fire your last direction, a key logger if you're curious what's firing).

Whole thing is styled like a CRT terminal — green-on-black, scanlines, and the title text visibly glitches and scrambles a couple characters every frame instead of just sitting there static.

The reason it's built this way: Snake's actual game state is locked away in the page's own code where nothing outside can reach it, so instead of faking mods that pretend to see/change stuff that isn't actually accessible, everything here is a real trick that verifiably works — messing with the clock for speed, CSS filters for visuals, fake key presses for automation. No "wallhacks" or nonsense that can't really do anything.

Install is just: make a new bookmark, paste the script as the URL, click it while on the Snake game. Click again to close it.

Built this from scratch as a fun little project — figured I'd share it since Snake's such a classic. Let me know if u find bugs!

javascript:(function(){(()=>{(()=>{"use strict";if(window.__snakeGUI){if(window.__snakeGUI.version===5){window.__snakeGUI.toggle();return}const e=window.__snakeGUI;try{e.destroy&&e.destroy()}catch{}try{e.S&&(e.S.map=null,e.S.spawner=!1,e.S.teleport=!1,e.S.god=!1,e.S.bot=!1,e.S.grid=!1)}catch{}["sgui","sgui-grid","sgui-css"].forEach(t=>document.getElementById(t)?.remove()),document.body.classList.remove("sgui-picking");try{delete window.__snakeGUI}catch{window.__snakeGUI=void 0}}const oe=[],D=(e,t,r,n)=>{e.addEventListener(t,r,n),oe.push([e,t,r,n])};let q=null;const O={UP:[0,-1],DOWN:[0,1],LEFT:[-1,0],RIGHT:[1,0]},K=e=>{try{return Object.keys(e)}catch{return[]}},we=e=>e&&typeof e=="object"&&typeof e.x=="number"&&typeof e.y=="number",T=e=>typeof Element<"u"&&e instanceof Element,M=(e,t,r)=>Math.max(t,Math.min(r,e));function Ne(e,t,r=10,n=4e5){const s=new WeakSet,a=e.map(c=>[c,0]);let l=0;for(;a.length&&l<n;){const[c,u]=a.shift();if(l++,!(!c||typeof c!="object"&&typeof c!="function")&&!(u>r||s.has(c))){s.add(c);try{if(t(c))return c}catch{}if(!(T(c)&&u>1))for(const f of K(c)){let d;try{d=c[f]}catch{continue}d&&(typeof d=="object"||typeof d=="function")&&a.push([d,u+1])}}}return null}function ne(){const e=[window];for(const t of document.querySelectorAll("[jscontroller]"))t.__jscontroller&&e.push(t.__jscontroller);return Ne(e,t=>typeof t.ticks=="number"&&typeof t.turn=="function"&&t.settings&&typeof t.settings=="object"&&"isMobile"in t.settings&&t.localStorage&&typeof t.localStorage=="object")}function xe(e){const t=Object.getPrototypeOf(e);return Object.getOwnPropertyNames(t).filter(r=>{if(r==="constructor")return!1;try{return typeof t[r]=="function"}catch{return!1}}).map(r=>{let n="";try{n=t[r].toString()}catch{}return[r,n]})}function Pe(e){const t={},r=/this\.(\w+)\s*=\s*!0\s*;?\s*this\.(\w+)\s*=\s*this\.ticks/;for(const[n,s]of xe(e)){const a=s.match(r);if(!a)continue;t.deathMethod=n,t.overKey=a[1];const l=s.match(/\(\s*this\.\w+\s*,\s*\d+\s*,\s*this\.(\w+)\s*\)/);l&&(t.scoreKey=l[1]);break}return t}function De(e,t){const r={},n=new RegExp("(\\w+)\\.(\\w+)===0\\?\\(\\1\\.(\\w+)=\\1\\.(\\w+),\\1\\.\\4=\\1\\."+t+"\\.pop\\(\\)");for(const[,s]of xe(e)){const a=s.match(n);if(a){r.growKey=a[2],r.tailPrevKey=a[3],r.tailKey=a[4];break}}return r}function Ge(){return[...document.querySelectorAll("div,span")].filter(e=>e.children.length===0&&/^\d{1,4}$/.test(e.textContent.trim())).map(e=>parseInt(e.textContent.trim(),10))}function L(){let e=null;for(const t of document.querySelectorAll("canvas"))(!e||t.width*t.height>e.width*e.height)&&(e=t);return e}function re(e){const t={game:e,...Pe(e)};for(const r of K(e)){const n=e[r];if(!n||typeof n!="object"||T(n)||typeof n.direction!="string")continue;const s=K(n).find(a=>{const l=n[a];return Array.isArray(l)&&l.length&&we(l[0])&&Number.isInteger(l[0].x)});if(s){t.snake=n,t.bodyKey=s,t.colorKeys=K(n).filter(a=>typeof n[a]=="string"&&/^%23[0-9a-f]{6}$/i.test(n[a]));break}}t.colorKeys||(t.colorKeys=[]),t.bodyKey&&Object.assign(t,De(e,t.bodyKey));for(const r of K(e)){const n=e[r];if(!(!n||typeof n!="object"||T(n))){for(const s of K(n)){const a=n[s];if(Array.isArray(a)&&a.length&&a[0]&&we(a[0].pos)){t.appleOwner=n,t.appleKey=s;break}}if(t.appleOwner)break}}for(const r of K(e)){const n=e[r];if(!(!n||typeof n!="object"||T(n))){for(const s of K(n)){const a=n[s];if(a&&typeof a=="object"&&!T(a)&&Number.isInteger(a.width)&&Number.isInteger(a.height)&&a.width>4&&a.width<60&&a.height>4&&a.height<60){t.board=a,t.boardOwner=n;break}}if(t.board)break}}if(t.board||(t.board={width:17,height:15}),!t.scoreKey){const r=Ge();t.scoreKey=K(e).find(n=>n!=="ticks"&&Number.isInteger(e[n])&&e[n]>=0&&r.includes(e[n]))||null}return t}function G(){const e=L(),t=o.map;if(!e||!t.boardOwner)return 25;const r=t.board.width,n=t.board.height;let s=null;for(const a of K(t.boardOwner)){const l=t.boardOwner[a];if(typeof l!="number"||l<8||l>100)continue;const c=(e.width-r*l)/2,u=(e.height-n*l)/2;if(c<0||u<0)continue;const f=Math.abs(c-u);(!s||f<s.err)&&(s={v:l,err:f})}return s?s.v:Math.floor(Math.min(e.width/r,e.height/n))}function se(e,t){const r=L();if(!r)return null;const n=r.getBoundingClientRect();if(!n.width||!n.height)return null;const s=(e-n.left)*(r.width/n.width),a=(t-n.top)*(r.height/n.height),l=o.map.board.width,c=o.map.board.height,u=G(),f=(r.width-l*u)/2,d=(r.height-c*u)/2,g=Math.floor((s-f)/u),p=Math.floor((a-d)/u);return g<0||p<0||g>=l||p>=c?null:{x:g,y:p}}const o={map:null,god:!1,wrap:!1,freeze:!1,pause:!1,slow:1,turbo:0,dodge:!1,autoRespawn:!1,magnet:!1,spawner:!1,keepApples:0,appleMouse:!1,autoScore:!1,multiplier:1,lockScore:!1,lockedAt:0,bot:!1,botDelay:3,botSafe:!0,botRestart:!1,rainbow:!1,rainbowSpeed:3,grid:!1,trail:!1,hideHud:!1,zoom:100,hue:0,disco:!1,invert:!1},x=()=>o.map.game,v=()=>o.map.snake,w=()=>{const e=v();return e&&e[o.map.bodyKey]},S=()=>o.map.appleOwner?o.map.appleOwner[o.map.appleKey]:[],y=e=>{const t=o.map.scoreKey;return t?(e!==void 0&&(x()[t]=e),x()[t]):null},z=()=>o.map.overKey?x()[o.map.overKey]:!1,ie=()=>{const e=x();return typeof e.reset=="function"?(e.reset(),"reset()"):o.map.overKey?(e[o.map.overKey]=!1,"flag cleared"):"no way in"},ke=()=>new Set((w()||[]).map(e=>e.x+","+e.y).concat(S().map(e=>e.pos.x+","+e.pos.y)));function F(){const e=ke(),t=o.map.board.width,r=o.map.board.height;for(let n=0;n<200;n++){const s=Math.floor(Math.random()*t),a=Math.floor(Math.random()*r);if(!e.has(s+","+a))return{x:s,y:a}}return null}function ae(){const e=x(),t=o.map.deathMethod;if(!t||e.__godHooked)return;const r=Object.getPrototypeOf(e)[t];e[t]=function(...n){if(!o.god)return r.apply(this,n)},e.__godHooked=!0}let ce=0;function V(){const e=x();if(e.__tickHooked)return;const t=Object.getPrototypeOf(e).tick;typeof t=="function"&&(e.tick=function(...r){if(!o.pause&&!(o.slow>1&&(ce=(ce+1)%25o.slow,ce!==0)))return o.dodge&&ze(),t.apply(this,r)},e.__tickHooked=!0)}function ze(){const e=w(),t=v();if(!e||e.length<2||!t)return;const r=o.map.board.width,n=o.map.board.height,s=O[t.direction];if(!s)return;const a=new Set(e.slice(0,-1).map(u=>u.x+","+u.y)),l=(u,f)=>u<0||f<0||u>=r||f>=n||a.has(u+","+f);if(!l(e[0].x+s[0],e[0].y+s[1]))return;const c=e[1];for(const[u,f]of Object.entries(O)){const d=e[0].x+f[0],g=e[0].y+f[1];if(!l(d,g)&&!(c&&d===c.x&&g===c.y)){x().turn(u);return}}}function Ue(){const e=w();if(!e||!e.length)return;const t=o.map.board.width,r=o.map.board.height,n=e[0];(n.x<0||n.x>=t||n.y<0||n.y>=r)&&(n.x=(n.x%25t+t)%25t,n.y=(n.y%25r+r)%25r)}function Be(){const e=x();if(!(typeof e.tick!="function"||z()))for(let t=0;t<o.turbo;t++)try{e.tick()}catch{}}function ve(){if(!z())return;ie();const e=v();e&&x().turn(e.direction&&O[e.direction]?e.direction:"RIGHT")}function X(e){const t=v(),r=o.map.growKey;return t?r?(t[r]=Math.max(0,(t[r]||0)+e),"queued +"+e+" (grows as you move)"):"growth counter not resolved":"no snake"}function le(e){const t=v(),r=w();if(!(!t||!r))for(let n=0;n<e&&r.length>3;n++){const s=r.length;o.map.tailPrevKey&&o.map.tailKey&&(t[o.map.tailPrevKey]=t[o.map.tailKey]);const a=r.pop();o.map.tailKey&&(t[o.map.tailKey]=a);for(const l of K(t)){const c=t[l];Array.isArray(c)&&c!==r&&c.length===s&&c.pop()}}}function Y(e){const t=w();if(!t)return"";const r=M(e,3,o.map.board.width*o.map.board.height);return r>t.length?X(r-t.length):(le(t.length-r),"length = "+w().length)}function We(e,t){const r=w();if(!r||!r.length)return"no body";const n=o.map.board.width,s=o.map.board.height,a=e-r[0].x,l=t-r[0].y;if(!r.every(c=>c.x+a>=0&&c.x+a<n&&c.y+l>=0&&c.y+l<s))return"the tail would end up off-board";for(const c of r)c.x+=a,c.y+=l;return`moved to ${e},${t}`}const Se=()=>We(o.map.board.width>>1,o.map.board.height>>1);function qe(){const e=w(),t=v();if(!e||e.length<2)return"body too short";e.reverse();const r=e[0],n=e[1],s=Object.entries(O).find(([,a])=>a[0]===r.x-n.x&&a[1]===r.y-n.y);return s&&(t.direction=s[0],x().turn(s[0])),"reversed, now heading "+t.direction}function Fe(){const e=w(),t=v();if(!e||!e.length)return"no body";const r=O[t.direction]||[1,0],n=o.map.board.width,s=o.map.board.height,a={...e[0]};return e.every((c,u)=>{const f=a.x-r[0]*u,d=a.y-r[1]*u;return f>=0&&d>=0&&f<n&&d<s})?(e.forEach((c,u)=>{c.x=a.x-r[0]*u,c.y=a.y-r[1]*u}),"straightened"):"not enough room ahead \u2014 centre or shorten first"}function ue(){if(o.map.tpl)return o.map.tpl;const e=S();return e.length&&(o.map.tpl={...e[0]},o.map.tplPos=e[0].pos),o.map.tpl}function H(e,t){const r=S(),n=ue();if(!n||!o.map.tplPos)return!1;const s={...n};return s.pos=o.map.tplPos.clone(),s.pos.x=e,s.pos.y=t,r.push(s),!0}function Ve(e=1){const t=v(),r=w();if(!t||!r||!r.length)return null;const n=O[t.direction]||[1,0];return{x:M(r[0].x+n[0]*e,0,o.map.board.width-1),y:M(r[0].y+n[1]*e,0,o.map.board.height-1)}}function fe(){const e=Ve(1);if(e)for(const t of S())t.pos.x=e.x,t.pos.y=e.y}function Ee(){for(const e of S()){const t=F();t&&(e.pos.x=t.x,e.pos.y=t.y)}return"rerolled"}function Xe(e){let t=0;for(let r=0;r<e;r++){const n=F();n&&H(n.x,n.y)&&t++}return`dropped ${t} apples (${S().length} on the board)`}function Ye(){const e=ke(),t=o.map.board.width,r=o.map.board.height;let n=0;for(let s=0;s<r;s++)for(let a=0;a<t;a++)e.has(a+","+s)||H(a,s)&&n++;return`filled ${n} squares`}function Je(){const e=S().length;for(let t=0;t<e;t++){const r=F();r&&H(r.x,r.y)}return S().length+" apples"}function Ze(){if(S().length>=o.keepApples)return;const t=F();t&&H(t.x,t.y)}function Qe(){const e=S();for(;e.length>1;)e.pop();return"trimmed to 1"}let U=null;function et(){if(!U)return;const e=S();e.length&&(e[0].pos.x=U.x,e[0].pos.y=U.y)}let B=null;function tt(){const e=y();e!==null&&(B!==null&&e>B&&o.multiplier>1&&y(e+(e-B)*(o.multiplier-1)),B=y())}function ot(){y()!==null&&y(o.lockedAt)}function nt(e,t,r){const n=t/100,s=r/100,a=u=>(u+e/30)%2512,l=n*Math.min(s,1-s),c=u=>Math.round(255*(s-l*Math.max(-1,Math.min(a(u)-3,Math.min(9-a(u),1)))));return"%23"+[c(0),c(8),c(4)].map(u=>u.toString(16).padStart(2,"0")).join("")}function rt(e,t){const r=parseInt(e.slice(1),16);return"%23"+[r>>16&255,r>>8&255,r&255].map(n=>M(Math.round(n+n*t),0,255).toString(16).padStart(2,"0")).join("")}let de=0;function st(){const e=v();!e||!o.map.colorKeys.length||(de=(de+o.rainbowSpeed)%25360,o.map.colorKeys.forEach((t,r)=>{e[t]=nt((de+r*40)%25360,85,r?40:58)}))}function Ke(){const e=v();e&&o.map.origColors&&o.map.colorKeys.forEach((t,r)=>e[t]=o.map.origColors[r])}function it(e,t){const r=v();!r||!o.map.colorKeys[e]||(o.rainbow=!1,r[o.map.colorKeys[e]]=t)}const Me=["%234e7cf6","%23e8453c","%23f9bc15","%232fa84f","%239c27b0","%2300bcd4","%23ff6d00","%23111111"];let pe=0;function Oe(){const e=v();if(!e||!o.map.colorKeys.length)return"snake has no colour fields";o.rainbow=!1,pe=(pe+1)%25Me.length;const t=Me[pe];return o.map.colorKeys.forEach((r,n)=>{e[r]=n?rt(t,-.35):t}),"colour "+t}let he=0;function at(){const e=L();if(!e)return;o.disco&&(he=(he+4)%25360);const t=o.disco?he:o.hue,r=[];t&&r.push(`hue-rotate(${t}deg)`),o.invert&&r.push("invert(1)");const n=r.join(" "),s=o.zoom!==100?`scale(${o.zoom/100})`:"";e.style.filter!==n&&(e.style.filter=n),e.style.transform!==s&&(e.style.transform=s,e.style.transformOrigin="center center")}function _e(){const e=L();e&&(e.style.filter="",e.style.transform="")}function je(){const e=L();if(!e)return null;const t=e.getBoundingClientRect();if(!t.width||!t.height)return null;const r=o.map.board.width,n=o.map.board.height,s=G(),a=t.width/e.width,l=t.height/e.height;return{left:t.left+(e.width-r*s)/2*a,top:t.top+(e.height-n*s)/2*l,w:r*s*a,h:n*s*l,px:s*a,py:s*l,cols:r,rows:n}}let C=null,ge="";function ct(){const e=je();if(!e)return;C||(C=document.createElement("div"),C.id="sgui-grid",C.style.cssText="position:fixed;pointer-events:none;z-index:2147483000;",document.body.appendChild(C));const t=[e.left,e.top,e.w,e.h].join("|");t!==ge&&(ge=t,Object.assign(C.style,{left:e.left+"px",top:e.top+"px",width:e.w+"px",height:e.h+"px",backgroundImage:`repeating-linear-gradient(to right, rgba(255,255,255,.25) 0 1px, transparent 1px ${e.px}px),repeating-linear-gradient(to bottom, rgba(255,255,255,.25) 0 1px, transparent 1px ${e.py}px)`,outline:"1px solid rgba(255,255,255,.45)"}))}function Ce(){C&&C.remove(),C=null,ge=""}let E=null,W=null,A=[];function lt(){const e=je();if(!e)return;E||(E=document.createElement("canvas"),E.id="sgui-trail",E.style.cssText="position:fixed;pointer-events:none;z-index:2147482999;",document.body.appendChild(E),W=E.getContext("2d"));const t=(w()||[])[0];if(t){const s=A[A.length-1];(!s||s.x!==t.x||s.y!==t.y)&&A.push({x:t.x,y:t.y}),A.length>220&&A.shift()}const r=Math.round(e.w),n=Math.round(e.h);(E.width!==r||E.height!==n)&&(E.width=r,E.height=n),Object.assign(E.style,{left:e.left+"px",top:e.top+"px",width:e.w+"px",height:e.h+"px"}),W.clearRect(0,0,r,n),A.forEach((s,a)=>{const l=(a+1)/A.length;W.fillStyle=`rgba(255,255,255,${(l*.5).toFixed(3)})`,W.fillRect(s.x*e.px+e.px*.3,s.y*e.py+e.py*.3,e.px*.4,e.py*.4)})}function Re(){E&&E.remove(),E=null,W=null,A=[]}let J=[];function Ae(e){const t=L();if(e){if(!t)return;const r=t.closest("div")?.parentElement;if(!r)return;for(const n of r.querySelectorAll("div")){const s=n.textContent.trim();if(n.children.length===0&&/^\d{1,4}$/.test(s)){const a=n.parentElement;a&&!a.contains(t)&&!J.includes(a)&&(J.push(a),a.style.visibility="hidden")}}}else J.forEach(r=>r.style.visibility=""),J=[]}function ye(e,t,r,n,s){const a=new Set(t.map(f=>f.x+","+f.y)),l=new Map,c=new Set([e.x+","+e.y]),u=[e];for(;u.length;){const f=u.shift();if(a.has(f.x+","+f.y)){const d=[];let g=f.x+","+f.y;for(;l.has(g);){const[p,k]=g.split(",").map(Number);d.unshift({x:p,y:k}),g=l.get(g)}return d}for(const[d,g]of Object.values(O)){const p=f.x+d,k=f.y+g,i=p+","+k;p<0||k<0||p>=n||k>=s||c.has(i)||r.has(i)||(c.add(i),l.set(i,f.x+","+f.y),u.push({x:p,y:k}))}}return null}function ut(e,t,r,n){const s=[t,...e.slice(0,-1)],a=new Set(s.slice(0,-1).map(f=>f.x+","+f.y)),l=s[s.length-1],c=new Set([t.x+","+t.y]),u=[t];for(;u.length;){const f=u.shift();if(f.x===l.x&&f.y===l.y)return!0;for(const[d,g]of Object.values(O)){const p=f.x+d,k=f.y+g,i=p+","+k;p<0||k<0||p>=r||k>=n||c.has(i)||a.has(i)||(c.add(i),u.push({x:p,y:k}))}}return!1}function Ie(){const e=w(),t=v();if(!e||e.length<2||!t||z())return;const r=o.map.board.width,n=o.map.board.height,s=e[0],a=e[e.length-1];if(s.x<0||s.y<0||s.x>=r||s.y>=n)return;const l=S().map(p=>p.pos).filter(p=>p.x>=0&&p.y>=0&&p.x<r&&p.y<n),c=new Set(e.slice(0,-1).map(p=>p.x+","+p.y));let u=l.length?ye(s,l,c,r,n):null;if((!u||!u.length)&&(u=ye(s,[a],c,r,n)),!u||!u.length)return;let f=u[0];const d=e[1];if(d&&f.x===d.x&&f.y===d.y)return;if(o.botSafe&&!ut(e,f,r,n)){const p=ye(s,[a],c,r,n);if(p&&p.length&&!(d&&p[0].x===d.x&&p[0].y===d.y))f=p[0];else return}const g=Object.keys(O).find(p=>O[p][0]===f.x-s.x&&O[p][1]===f.y-s.y);g&&g!==t.direction&&x().turn(g)}let Z=null;function be(e=x(),t="game",r=4){const n={},s=new WeakSet;return(function a(l,c,u){if(!(!l||typeof l!="object"||u<0||s.has(l)||T(l)||l===window)){s.add(l);for(const f of K(l)){let d;try{d=l[f]}catch{continue}const g=c+"."+f;d===null||["number","string","boolean"].includes(typeof d)?n[g]=d:Array.isArray(d)?(n[g]=`Array(${d.length})`,d.length<60&&a(d,g,u-1)):typeof d=="object"&&a(d,g,u-1)}}})(e,t,r),n}function $e(e,t){const r={};for(const n of new Set([...Object.keys(e),...Object.keys(t)]))e[n]!==t[n]&&(r[n]=[e[n],t[n]]);return r}const ft=`%0A%23sgui,%23sgui *{box-sizing:border-box;margin:0;padding:0}%0A%23sgui{position:fixed;top:16px;left:16px;z-index:2147483647;display:flex;align-items:flex-start;gap:4px;%0A flex-wrap:wrap;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);overflow:auto;%0A font:600 12px/1 "Segoe UI",system-ui,-apple-system,sans-serif;color:%23dbe6f5;%0A text-shadow:0 1px 1px rgba(0,0,0,.55);cursor:default}%0A%23sgui.hide{display:none}%0A%23sgui .col{width:152px;flex:0 0 auto}%0A%23sgui .hd{display:flex;align-items:center;justify-content:center;position:relative;height:26px;%0A background:linear-gradient(%233b82f6,%231d5fd0);border:1px solid %23123a80;color:%23fff;letter-spacing:.02em;%0A cursor:move;user-select:none}%0A%23sgui .hd span{pointer-events:none}%0A%23sgui .hd .ar{position:absolute;right:5px;top:50%25;transform:translateY(-50%25);width:16px;height:16px;%0A display:flex;align-items:center;justify-content:center;font-size:9px;cursor:pointer;border-radius:3px;%0A background:rgba(255,255,255,.12);transition:background .12s}%0A%23sgui .hd .ar:hover{background:rgba(255,255,255,.3)}%0A%23sgui .bd{border:1px solid %23123a80;border-top:0;background:rgba(14,20,34,.9);backdrop-filter:blur(2px)}%0A%23sgui .bd.closed{display:none}%0A%23sgui .it{display:flex;align-items:center;justify-content:space-between;gap:6px;height:24px;padding:0 8px;%0A border-left:3px solid transparent;cursor:pointer;white-space:nowrap;overflow:hidden;transition:background .1s}%0A%23sgui .it:hover{background:rgba(59,130,246,.22)}%0A%23sgui .it.on{background:rgba(59,130,246,.34);border-left-color:%237fb2ff;color:%23fff}%0A%23sgui .it.flash{background:%233b82f6;color:%23fff}%0A%23sgui .it input{width:46px;height:17px;background:%230b1220;border:1px solid %232b5ea8;color:%23dbe6f5;%0A border-radius:3px;font:600 11px/1 inherit;text-align:center;outline:0;cursor:text}%0A%23sgui .it input:focus{border-color:%237fb2ff}%0A%23sgui .it input[type=color]{width:38px;height:16px;padding:0;border:1px solid %232b5ea8;background:%230b1220;cursor:pointer}%0A%23sgui .st{padding:5px 8px;font-weight:500;font-size:10.5px;line-height:1.5;color:%238fb4e8;%0A border:1px solid %23123a80;border-top:0;background:rgba(14,20,34,.9)}%0A%23sgui .lg{max-height:150px;overflow:auto;padding:5px 8px;font:500 10px/1.5 ui-monospace,Menlo,monospace;%0A color:%238fb4e8;white-space:pre-wrap;word-break:break-all;border:1px solid %23123a80;border-top:0;%0A background:rgba(8,12,22,.95);cursor:text;user-select:text}%0Abody.sgui-picking canvas{cursor:crosshair!important}%0A`;function b(e,t={},...r){const n=document.createElement(e);for(const[s,a]of Object.entries(t))s==="class"?n.className=a:s.startsWith("on")?n.addEventListener(s.slice(2),a):n.setAttribute(s,a);for(const s of r)s!=null&&n.append(s);return n}let I;const R=e=>{I&&(I.textContent=(e+`%0A`+I.textContent).split(`%0A`).slice(0,60).join(`%0A`))},Q=[];function m(e,t,r){const n=b("div",{class:"it"},b("span",{},e)),s=()=>n.classList.toggle("on",!!t());return n.addEventListener("click",()=>{r(!t()),s(),Q.forEach(a=>a._sync())}),s(),n._sync=s,Q.push(n),n}function h(e,t){const r=b("div",{class:"it"},b("span",{},e));return r.addEventListener("click",()=>{try{const n=t();typeof n=="string"&&n&&R(n)}catch(n){R("! "+n.message)}r.classList.add("flash"),setTimeout(()=>r.classList.remove("flash"),130)}),r}function _(e,t,r){const n=b("input",{type:"text",value:String(t)}),s=()=>{const l=parseInt(n.value,10);if(!Number.isNaN(l))try{const c=r(l);typeof c=="string"&&c&&R(c)}catch(c){R("! "+c.message)}};["keydown","keyup","keypress"].forEach(l=>n.addEventListener(l,c=>c.stopPropagation())),n.addEventListener("keydown",l=>{l.key==="Enter"&&(s(),n.blur())}),n.addEventListener("change",s);const a=b("div",{class:"it"},b("span",{},e),n);return a.addEventListener("click",l=>{l.target!==n&&(n.focus(),n.select())}),a}function Te(e,t){const n=b("input",{type:"color",value:o.map.snake&&o.map.colorKeys[t]&&o.map.snake[o.map.colorKeys[t]]||"%234e7cf6"});n.addEventListener("input",()=>it(t,n.value)),["keydown","keyup","keypress"].forEach(a=>n.addEventListener(a,l=>l.stopPropagation()));const s=b("div",{class:"it"},b("span",{},e),n);return s.addEventListener("click",a=>{a.target!==n&&n.click()}),s}function $(e,t,r=!1){const n=b("div",{class:"bd"+(r?" closed":"")},...t),s=b("div",{class:"ar"},r?"\u25B6":"\u25BC");s.addEventListener("mousedown",l=>l.stopPropagation()),s.addEventListener("click",l=>{l.stopPropagation();const c=n.classList.toggle("closed");s.textContent=c?"\u25B6":"\u25BC"});const a=b("div",{class:"hd"},b("span",{},e),s);return{el:b("div",{class:"col"},a,n),hd:a,bd:n}}function dt(){document.head.append(b("style",{id:"sgui-css"},ft));const e=b("div",{class:"st"},"resolving\u2026");I=b("div",{class:"lg"},"ready");const t=$("Player",[m("God Mode",()=>o.god,i=>{o.god=i,i&&(ae(),o.wrap=!0)}),m("Wall Wrap",()=>o.wrap,i=>o.wrap=i),m("Auto Dodge",()=>o.dodge,i=>{o.dodge=i,i&&V()}),m("Pause",()=>o.pause,i=>{o.pause=i,i&&V()}),m("Freeze Body",()=>o.freeze,i=>o.freeze=i),m("Auto Respawn",()=>o.autoRespawn,i=>o.autoRespawn=i),_("Slow-Mo",1,i=>(o.slow=M(i,1,20),V(),"runs 1 tick in "+o.slow)),_("Turbo",0,i=>(o.turbo=M(i,0,8),"turbo = "+o.turbo)),h("Step",()=>{const i=x();return typeof i.tick=="function"?(i.tick(),"stepped"):"no tick()"}),h("New Run",()=>"new run: "+ie())]),r=$("Snake",[h("Grow +5",()=>X(5)),h("Grow +25",()=>X(25)),h("Shrink -5",()=>(le(5),"length = "+(w()||[]).length)),_("Length",10,i=>Y(i)),h("Tiny (3)",()=>Y(3)),h("Giant (40)",()=>Y(40)),h("Centre",Se),h("Reverse",qe),h("Straighten",Fe),h("Head \u2192 Click",()=>(o.teleport=!0,document.body.classList.add("sgui-picking"),"click a square"))]),n=$("Apples",[m("Spawner (click)",()=>o.spawner,i=>{o.spawner=i,document.body.classList.toggle("sgui-picking",i||!!o.teleport),i&&R("click any square to drop an apple")}),m("Magnet",()=>o.magnet,i=>o.magnet=i),m("Follow Mouse",()=>o.appleMouse,i=>o.appleMouse=i),_("Keep N",0,i=>(o.keepApples=M(i,0,120),"keeping "+o.keepApples)),_("Rain N",10,i=>Xe(M(i,1,120))),h("Double",Je),h("Fill Board",Ye),h("Apple \u2192 Head",()=>(fe(),"apple moved")),h("Reroll",Ee),h("Trim to 1",Qe)]),s=$("Score",[_("Set",100,i=>(y(i),"score = "+i)),h("+100",()=>(y((y()||0)+100),"score = "+y())),h("+1000",()=>(y((y()||0)+1e3),"score = "+y())),h("Reset",()=>(y(0),"score = 0")),_("Multiplier",1,i=>(o.multiplier=M(i,1,100),"x"+o.multiplier+" per apple")),m("Auto +1/s",()=>o.autoScore,i=>o.autoScore=i),m("Lock Score",()=>o.lockScore,i=>{o.lockScore=i,i&&(o.lockedAt=y()||0)}),h("Bank Record",()=>{if(o.god)return"turn God Mode off first \u2014 banking needs the run to end";const i=y();return x()[o.map.deathMethod](),"ended the run at "+i+" to bank it"})]),a=$("Bot",[m("Autoplay",()=>o.bot,i=>o.bot=i),m("Safe Mode",()=>o.botSafe,i=>o.botSafe=i),m("Auto Restart",()=>o.botRestart,i=>o.botRestart=i),_("Delay",o.botDelay,i=>(o.botDelay=M(i,1,60),"delay = "+o.botDelay)),h("Step Bot",()=>(Ie(),"bot stepped")),h("Stop Bot",()=>(o.bot=!1,Q.forEach(i=>i._sync()),"bot off"))]),l=$("Visual",[m("Rainbow",()=>o.rainbow,i=>{o.rainbow=i,i||Ke()}),_("Rainbow Spd",3,i=>(o.rainbowSpeed=M(i,1,30),"speed "+o.rainbowSpeed)),h("Cycle Colour",Oe),Te("Head",0),Te("Body",1),h("Reset Colour",()=>(o.rainbow=!1,Ke(),"colours restored")),m("Grid Overlay",()=>o.grid,i=>{o.grid=i,i||Ce()}),m("Trail",()=>o.trail,i=>{o.trail=i,i||Re()}),m("Hide HUD",()=>o.hideHud,i=>{o.hideHud=i,Ae(i)}),m("Disco",()=>o.disco,i=>o.disco=i),m("Invert",()=>o.invert,i=>o.invert=i),_("Hue",0,i=>(o.hue=(i%25360+360)%25360,"hue "+o.hue)),_("Zoom %25",100,i=>(o.zoom=M(i,40,200),"zoom "+o.zoom+"%25")),h("Reset View",()=>(o.zoom=100,o.hue=0,o.disco=!1,o.invert=!1,_e(),Q.forEach(i=>i._sync()),"view reset"))]),c=$("Dev",[h("Snapshot",()=>(Z=be(),"snapshot: "+Object.keys(Z).length+" fields")),h("Diff",()=>{if(!Z)return"take a Snapshot first";const i=$e(Z,be()),j=Object.entries(i).filter(([,[P,me]])=>typeof P=="number"&&typeof me=="number");return`--- ${Object.keys(i).length} changed, ${j.length} numeric:%0A`+j.slice(0,25).map(([P,[me,pt]])=>`${P}: ${me} -> ${pt}`).join(`%0A`)}),h("Print Map",()=>{const i=o.map;return console.log("[snake] map",i),`body=.${i.bodyKey}  apples=.${i.appleKey}  score=.${i.scoreKey}%0Aover=.${i.overKey}  death=.${i.deathMethod}()  grow=.${i.growKey}%0Aboard=${i.board.width}x${i.board.height} cell=${G()}`}),h("Copy Map",()=>{const i=o.map,j=JSON.stringify({bodyKey:i.bodyKey,appleKey:i.appleKey,scoreKey:i.scoreKey,overKey:i.overKey,deathMethod:i.deathMethod,growKey:i.growKey,tailKey:i.tailKey,board:{w:i.board.width,h:i.board.height},cell:G()},null,1);try{return navigator.clipboard.writeText(j),"map copied"}catch{return j}}),h("Re-resolve",()=>(o.map=re(ne()),ae(),"re-resolved")),h("Clear Log",()=>(I.textContent="","")),h("Unload Menu",()=>(setTimeout(()=>window.__snakeGUI.destroy(),0),"unloading\u2026"))],!0),u=[t,r,n,s,a,l,c],f=()=>{I.style.display=c.bd.classList.contains("closed")?"none":""};c.hd.querySelector(".ar").addEventListener("click",()=>setTimeout(f,0));const d=b("div",{id:"sgui"},...u.slice(0,6).map(i=>i.el),b("div",{class:"col"},c.hd,c.bd,e,I));f(),document.body.append(d);let g=0,p=0,k=!1;for(const i of u)i.hd.addEventListener("mousedown",j=>{if(j.target.classList.contains("ar"))return;k=!0;const P=d.getBoundingClientRect();g=j.clientX-P.left,p=j.clientY-P.top,j.preventDefault()});return D(window,"mousemove",i=>{k&&(d.style.left=Math.max(0,i.clientX-g)+"px",d.style.top=Math.max(0,i.clientY-p)+"px")}),D(window,"mouseup",()=>k=!1),{root:d,status:e}}const ee=ne();if(!ee){alert(`Snake game object not found.%0AOpen google.com/fbx?fbx=snake_arcade, then run this again.`);return}if(o.map=re(ee),!o.map.snake||!o.map.bodyKey){window.__snakeMap=o.map,alert(`Found the Snake game, but could not locate the snake itself.%0AGoogle probably changed the build \u2014 inspect window.__snakeMap in the console.`);return}if(o.map.deathMethod)try{delete ee[o.map.deathMethod]}catch{}try{delete ee.__godHooked}catch{}o.map.origColors=o.map.colorKeys.map(e=>o.map.snake[e]),ue();const te=dt();te.status.textContent="v5 resolved \u2713",o.map.growKey||R("note: growth counter not found \u2014 Grow is disabled, Shrink still works"),D(window,"click",e=>{if(!o.spawner&&!o.teleport||e.target.closest&&e.target.closest("%23sgui"))return;const t=se(e.clientX,e.clientY);if(t){if(e.preventDefault(),e.stopPropagation(),o.teleport){const r=w();if(r&&r.length){const n=t.x-r[0].x,s=t.y-r[0].y,a=o.map.board.width,l=o.map.board.height;if(r.every(c=>c.x+n>=0&&c.x+n<a&&c.y+s>=0&&c.y+s<l)){for(const c of r)c.x+=n,c.y+=s;R(`head \u2192 ${t.x},${t.y}`)}else R("cannot move there without clipping the tail off-board")}o.teleport=!1,document.body.classList.toggle("sgui-picking",o.spawner);return}R(H(t.x,t.y)?`apple @ ${t.x},${t.y}`:"no apple template yet \u2014 start a run first")}},!0),D(window,"mousemove",e=>{o.appleMouse&&(U=se(e.clientX,e.clientY)||U)}),D(window,"keydown",e=>{e.key==="\\"&&!/^(INPUT|TEXTAREA)$/.test(e.target.tagName)&&(te.root.classList.toggle("hide"),e.preventDefault())},!0);let N=null,Le=0,He=0;q=setInterval(function(){if(!(!o.map||!o.map.game))try{if(o.god&&ae(),(o.pause||o.slow>1||o.dodge)&&V(),o.wrap&&Ue(),o.turbo&&Be(),o.autoRespawn&&ve(),o.magnet&&fe(),o.appleMouse&&et(),o.keepApples&&Ze(),o.multiplier>1?tt():B=y(),o.lockScore&&ot(),o.rainbow&&st(),o.grid&&ct(),o.trail&&lt(),at(),o.map.tpl||ue(),o.freeze){const t=w();t&&(N?t.forEach((r,n)=>N[n]&&(r.x=N[n].x,r.y=N[n].y)):N=t.map(r=>({...r})))}else N=null;o.bot&&o.botRestart&&z()&&ve(),o.bot&&--Le<=0&&(Ie(),Le=o.botDelay),o.autoScore&&--He<=0&&(y((y()||0)+1),He=62),te.status.textContent=`score ${y()??"?"}  len ${(w()||[]).length}  apples ${S().length}  ${z()?"DEAD":"alive"}  v5`}catch{}},16),window.__snakeGUI={version:5,destroy(){q&&clearInterval(q),q=null,oe.forEach(([e,t,r,n])=>{try{e.removeEventListener(t,r,n)}catch{}}),oe.length=0,Ce(),Re(),Ae(!1),_e();try{const e=o.map&&o.map.game;e&&(delete e.tick,delete e.__tickHooked)}catch{}["sgui","sgui-css"].forEach(e=>document.getElementById(e)?.remove()),document.body.classList.remove("sgui-picking"),o.map=null},toggle:()=>te.root.classList.toggle("hide"),S:o,map:()=>o.map,game:x,snake:v,body:w,apples:S,score:y,newRun:ie,snapshot:be,diff:$e,resolve:re,findGame:ne,grow:X,shrink:le,setLength:Y,centre:Se,appleToHead:fe,spawnApple:H,rerollApples:Ee,cellFromPoint:se,cellSize:G,cycleColour:Oe},console.log("[snake] ClickGUI v5 loaded. \\ toggles. window.__snakeGUI for the API.",o.map)})();})()})()

r/userscripts • • Sep 06 '26

Userscripts app vs Tampermonkey iOS

18 Upvotes

Anyone know which is superior - besides the cost/OSS difference?


r/userscripts • • Sep 07 '26

Instagram tool for DM unsend and mutual checks

2 Upvotes

Finished a release of Insta Toolbox, an open-source userscript/toolkit for Instagram that I'm somewhat satisfied with.

The idea is to have a few useful Instagram tools in one clean local toolbox instead of having a bunch of different scripts to do one task.

Current tools:

  • Mutual Checker - compares Followers vs Following and shows mutuals / non-mutuals, with search and export.
  • Follow / Unfollow - builds the target list first, lets you review it, then processes the confirmed list. Still polishing this one.
  • DM Unsend - works inside the conversation you currently have open and unsends messages you sent, with progress + Stop.

The easiest install is the Tampermonkey userscript. There's also a Chrome extension, Windows/macOS desktop builds, and a Web/PWA version.

GitHub:
https://github.com/slaveofsolace/Insta-Toolbox

I'm mainly looking for feedback on real-world Instagram UI edge cases now — especially large follower lists, different DM layouts/locales, and anything in the overlay UX that feels unnecessarily complicated.

If you try it and something breaks, let me know the browser/userscript manager and what screen you were on.