Greasy Fork is available in English.

Reddit Customizer Pro

Floating customization panel with drag, keyboard shortcuts, and preset profiles

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Reddit Customizer Pro
// @namespace    https://greatest.deepsurf.us/users/YOUR_USER_ID
// @version      3.0.0
// @description  Floating customization panel with drag, keyboard shortcuts, and preset profiles
// @author       YourNameOrHandle
// @match        https://*.reddit.com/*
// @match        https://*.old.reddit.com/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// @supportURL   https://greatest.deepsurf.us/scripts/SCRIPT_ID/feedback
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = 'reddit_customizer_v3';
    const DEFAULT_SETTINGS = {
        fontSize: '14', compactMode: false, hideSidebar: false, hideTopNav: false,
        hideAds: true, hideChat: true, hideAwards: false, maxWidth: '960',
        thumbnailSize: 'medium', hideCommentScores: false, highlightOP: true,
        disableAnimations: false, customScrollbar: true, bgImage: '', bgOverlayOpacity: '0.85',
        accentColor: '#ff4500', panelTransparency: '0.95', customCSS: ''
    };

    let state = loadState();
    let panel = null, btn = null, styleEl = null;
    let observer = null, updateTimer = null;

    // ─── State Management ──────────────────────────────────────────────
    function getDefaultState() {
        return {
            settings: { ...DEFAULT_SETTINGS },
            presets: { "Default": { ...DEFAULT_SETTINGS } },
            activePreset: "Default",
            positions: { fab: { x: window.innerWidth - 68, y: window.innerHeight - 68 }, panel: { x: window.innerWidth - 360, y: window.innerHeight - 140 } }
        };
    }

    function loadState() {
        try {
            const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
            const def = getDefaultState();
            return { ...def, ...stored, settings: { ...def.settings, ...(stored?.settings || {}) } };
        } catch { return getDefaultState(); }
    }

    function saveState() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
    }

    function applyPreset(name) {
        if (!state.presets[name]) return;
        state.settings = { ...state.presets[name] };
        state.activePreset = name;
        saveState();
        applyStyles();
        if (panel) populatePanel();
    }

    function saveCurrentAsPreset(name) {
        name = name.trim();
        if (!name) return alert("Preset name cannot be empty.");
        state.presets[name] = { ...state.settings };
        state.activePreset = name;
        saveState();
        populatePanel();
    }

    function deletePreset(name) {
        if (name === "Default") return alert("Cannot delete Default preset.");
        if (!confirm(`Delete preset "${name}"?`)) return;
        delete state.presets[name];
        if (state.activePreset === name) applyPreset("Default");
        saveState();
        populatePanel();
    }

    // ─── CSS Engine ────────────────────────────────────────────────────
    function generateCSS() {
        const s = state.settings;
        const thumb = { small: '60px', medium: '100px', large: '140px' }[s.thumbnailSize] || '100px';
        let css = `
            :root { --rc-font: ${s.fontSize}px; --rc-accent: ${s.accentColor}; --rc-panel-bg: rgba(22, 22, 23, ${s.panelTransparency}); }
            body, .shreddit-post, .Post, .comment, .scrollerItem, .md { font-size: var(--rc-font) !important; }
        `;
        if (s.maxWidth !== '100%') css += `@media(min-width:1024px){shreddit-app,main,.main-container{max-width:${s.maxWidth}px!important;margin:0 auto!important;}}`;
        if (s.compactMode) css += `shreddit-post,.Post,.scrollerItem{padding:6px 8px!important;margin-bottom:2px!important;}shreddit-comment,.comment{margin-bottom:4px!important;}.flex{padding:4px 0!important;}`;
        if (s.hideSidebar) css += `shreddit-sidebar,.sidebar,.right-sidebar,.side{display:none!important;}`;
        if (s.hideTopNav) css += `shreddit-header,header,.shreddit-header{display:none!important;}`;
        if (s.hideAds) css += `shreddit-promoted,.promotedlink,.promoted,[data-ad-location],[data-promoted],.scrollerItem[data-testid="ad-post"]{display:none!important;}`;
        if (s.hideChat) css += `chat-app,.chat-widget,[data-testid="chat-widget"],#chat-widget{display:none!important;}`;
        if (s.hideAwards) css += `.award-icon,.awards,shreddit-awards{display:none!important;}`;
        if (s.hideCommentScores) css += `score-tag,.comment-score,.score{display:none!important;}`;
        if (s.highlightOP) css += `shreddit-comment[op="true"],.comment.op{border-left:3px solid var(--rc-accent)!important;background:rgba(255,69,0,0.05)!important;}`;
        css += `.post-image-container,.Post .post-thumbnail{width:${thumb}!important;height:${thumb}!important;min-height:${thumb}!important;}`;
        if (s.disableAnimations) css += `*,*::before,*::after{animation-duration:0.001ms!important;transition-duration:0.001ms!important;scroll-behavior:auto!important;}`;
        if (s.customScrollbar) css += `::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#161617}::-webkit-scrollbar-thumb{background:#444;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--rc-accent)}`;
        if (s.bgImage.trim()) css += `body{background-image:url('${s.bgImage}')!important;background-size:cover!important;background-attachment:fixed!important;background-position:center!important;}shreddit-app,main,.main-container{background:rgba(20,20,21,${s.bgOverlayOpacity})!important;}`;
        if (s.customCSS.trim()) css += `\n/* User CSS */\n${s.customCSS.trim()}\n`;
        return css;
    }

    function applyStyles() {
        if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = 'rc-styles'; document.head.appendChild(styleEl); }
        styleEl.textContent = generateCSS();
    }

    // ─── UI Builder ────────────────────────────────────────────────────
    function createButton() {
        btn = document.createElement('button');
        btn.id = 'rc-fab';
        btn.innerHTML = '⚙️';
        btn.title = 'Open Customizer (Alt+C)';
        btn.style.cssText = `position:fixed;width:48px;height:48px;background:${state.settings.accentColor};color:#fff;border:none;border-radius:50%;font-size:22px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,0.3);z-index:999999;transition:transform 0.2s;display:flex;align-items:center;justify-content:center;`;
        document.body.appendChild(btn);
        makeDraggable(btn, 'fab');
        btn.addEventListener('click', togglePanel);
        btn.addEventListener('mouseenter', () => btn.style.transform = 'scale(1.1)');
        btn.addEventListener('mouseleave', () => btn.style.transform = 'scale(1)');
    }

    function createPanel() {
        panel = document.createElement('div');
        panel.id = 'rc-panel';
        panel.style.cssText = `position:fixed;width:340px;max-width:92vw;max-height:80vh;background:var(--rc-panel-bg);color:#d7dadc;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,0.4);padding:14px;z-index:999998;display:none;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;border:1px solid #333;backdrop-filter:blur(8px);user-select:none;`;

        const presetOptions = Object.keys(state.presets).map(p => `<option value="${p}" ${p===state.activePreset?'selected':''}>${p}</option>`).join('');

        panel.innerHTML = `
            <div id="rc-drag-handle" style="cursor:grab;padding:0 0 8px 0;border-bottom:1px solid #333;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;">
                <h3 style="margin:0;color:${state.settings.accentColor};font-size:17px;">Reddit Customizer</h3>
                <button id="rc-close" style="background:none;border:none;color:#888;cursor:pointer;font-size:20px;padding:2px 6px;border-radius:4px;">✕</button>
            </div>
            <div style="max-height:65vh;overflow-y:auto;padding-right:4px;scrollbar-width:thin;scrollbar-color:#444 transparent;">
                <!-- Presets -->
                <section style="margin-bottom:12px;background:#222;padding:8px;border-radius:8px;">
                    <div style="display:flex;gap:6px;align-items:center;margin-bottom:6px;">
                        <select id="rc-preset-sel" style="flex:1;background:#272729;color:#d7dadc;border:1px solid #444;border-radius:4px;padding:4px;font-size:12px;">${presetOptions}</select>
                        <button id="rc-save-preset" title="Save current as preset" style="background:#333;color:#aaa;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;">💾</button>
                        <button id="rc-del-preset" title="Delete preset" style="background:#333;color:#aaa;border:none;padding:4px 6px;border-radius:4px;cursor:pointer;font-size:11px;">🗑️</button>
                    </div>
                    <div style="font-size:10px;color:#666;text-align:center;">⌨️ Shortcuts: <b>Alt+C</b> Toggle | <b>Alt+S</b> Save | <b>Alt+R</b> Reset</div>
                </section>

                <section style="margin-bottom:12px;"><h4 style="margin:0 0 6px 0;font-size:11px;color:#777;text-transform:uppercase;">Appearance</h4>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Font Size: <span id="rc-fs-val">${state.settings.fontSize}px</span><input type="range" id="rc-fontsize" min="12" max="20" value="${state.settings.fontSize}" style="width:100%;margin-top:3px;"></label>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Accent Color<input type="color" id="rc-accent" value="${state.settings.accentColor}" style="width:100%;height:28px;border:none;border-radius:6px;cursor:pointer;background:#272729;margin-top:2px;"></label>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Content Width (px)<input type="number" id="rc-maxwidth" value="${state.settings.maxWidth}" min="600" max="1400" style="width:100%;background:#272729;color:#d7dadc;border:1px solid #444;border-radius:4px;padding:4px;margin-top:2px;"></label>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Panel Opacity<input type="range" id="rc-paneltrans" min="0.7" max="1" step="0.05" value="${state.settings.panelTransparency}" style="width:100%;margin-top:3px;"></label>
                </section>

                <section style="margin-bottom:12px;"><h4 style="margin:0 0 6px 0;font-size:11px;color:#777;text-transform:uppercase;">Layout & Content</h4>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-compact" ${state.settings.compactMode?'checked':''}> Compact Mode</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-sidebar" ${state.settings.hideSidebar?'checked':''}> Hide Sidebar</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-topnav" ${state.settings.hideTopNav?'checked':''}> Hide Top Nav</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-ads" ${state.settings.hideAds?'checked':''}> Hide Ads</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-chat" ${state.settings.hideChat?'checked':''}> Hide Chat</label>
                    <label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-awards" ${state.settings.hideAwards?'checked':''}> Hide Awards</label>
                </section>

                <section style="margin-bottom:10px;"><h4 style="margin:0 0 6px 0;font-size:11px;color:#777;text-transform:uppercase;">Comments & Polish</h4>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-hidescores" ${state.settings.hideCommentScores?'checked':''}> Hide Comment Scores</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-op" ${state.settings.highlightOP?'checked':''}> Highlight OP</label>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Thumbnails<select id="rc-thumb" style="width:100%;background:#272729;color:#d7dadc;border:1px solid #444;border-radius:4px;padding:4px;margin-top:2px;"><option value="small" ${state.settings.thumbnailSize==='small'?'selected':''}>Small</option><option value="medium" ${state.settings.thumbnailSize==='medium'?'selected':''}>Medium</option><option value="large" ${state.settings.thumbnailSize==='large'?'selected':''}>Large</option></select></label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-animations" ${state.settings.disableAnimations?'checked':''}> Disable Animations</label>
                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;cursor:pointer;"><input type="checkbox" id="rc-scrollbar" ${state.settings.customScrollbar?'checked':''}> Custom Scrollbar</label>
                    <label style="display:block;margin-bottom:6px;font-size:12px;">Background URL<input type="text" id="rc-bgimg" value="${state.settings.bgImage}" placeholder="https://..." style="width:100%;background:#272729;color:#d7dadc;border:1px solid #444;border-radius:4px;padding:4px;font-size:11px;margin-top:2px;"></label>
                </section>
                <section><label style="display:block;margin-bottom:4px;font-size:12px;">Custom CSS</label><textarea id="rc-customcss" style="width:100%;height:60px;background:#272729;color:#d7dadc;border:1px solid #444;border-radius:6px;padding:6px;font-family:monospace;font-size:11px;resize:vertical;">${state.settings.customCSS}</textarea></section>
            </div>
            <div style="display:flex;gap:6px;margin-top:8px;padding-top:8px;border-top:1px solid #333;">
                <button id="rc-apply" style="flex:1;background:${state.settings.accentColor};color:white;border:none;padding:7px;border-radius:6px;cursor:pointer;font-weight:500;font-size:13px;">Apply & Save</button>
                <button id="rc-reset" style="background:#343536;color:#aaa;border:none;padding:7px;border-radius:6px;cursor:pointer;font-size:12px;">Reset</button>
            </div>
        `;

        document.body.appendChild(panel);
        makeDraggable(panel, 'panel');
        populatePanel();
    }

    function populatePanel() {
        if (!panel) return;
        const q = (id) => panel.querySelector(`#${id}`);
        q('rc-fontsize').value = state.settings.fontSize; q('rc-fs-val').textContent = `${state.settings.fontSize}px`;
        q('rc-accent').value = state.settings.accentColor; q('rc-maxwidth').value = state.settings.maxWidth;
        q('rc-paneltrans').value = state.settings.panelTransparency; q('rc-compact').checked = state.settings.compactMode;
        q('rc-sidebar').checked = state.settings.hideSidebar; q('rc-topnav').checked = state.settings.hideTopNav;
        q('rc-ads').checked = state.settings.hideAds; q('rc-chat').checked = state.settings.hideChat;
        q('rc-awards').checked = state.settings.hideAwards; q('rc-hidescores').checked = state.settings.hideCommentScores;
        q('rc-op').checked = state.settings.highlightOP; q('rc-thumb').value = state.settings.thumbnailSize;
        q('rc-animations').checked = state.settings.disableAnimations; q('rc-scrollbar').checked = state.settings.customScrollbar;
        q('rc-bgimg').value = state.settings.bgImage; q('rc-customcss').value = state.settings.customCSS;

        // Preset dropdown
        const sel = q('rc-preset-sel');
        sel.innerHTML = Object.keys(state.presets).map(p => `<option value="${p}" ${p===state.activePreset?'selected':''}>${p}</option>`).join('');
        updatePanelTheme();
    }

    function togglePanel() {
        if (!panel) createPanel();
        panel.style.display = panel.style.display === 'block' ? 'none' : 'block';
        if (panel.style.display === 'block') populatePanel();
    }

    function updatePanelTheme() {
        if (!panel) return;
        panel.querySelector('h3').style.color = state.settings.accentColor;
        panel.querySelector('#rc-apply').style.background = state.settings.accentColor;
    }

    // ─── Drag Engine ───────────────────────────────────────────────────
    function makeDraggable(el, storageKey) {
        let isDragging = false, offsetX, offsetY;
        const pos = state.positions[storageKey];
        if (pos) { el.style.left = pos.x + 'px'; el.style.top = pos.y + 'px'; }

        const onStart = (e) => {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT' || e.target.closest('button') || (storageKey==='panel' && !e.target.closest('#rc-drag-handle'))) return;
            isDragging = true;
            const cX = e.touches ? e.touches[0].clientX : e.clientX;
            const cY = e.touches ? e.touches[0].clientY : e.clientY;
            const rect = el.getBoundingClientRect();
            offsetX = cX - rect.left; offsetY = cY - rect.top;
            el.style.zIndex = 999999; el.style.cursor = 'grabbing';
            e.preventDefault();
        };
        const onMove = (e) => {
            if (!isDragging) return;
            const cX = e.touches ? e.touches[0].clientX : e.clientX;
            const cY = e.touches ? e.touches[0].clientY : e.clientY;
            let newX = cX - offsetX, newY = cY - offsetY;
            // Clamp to viewport
            const w = window.innerWidth, h = window.innerHeight;
            const ew = el.offsetWidth, eh = el.offsetHeight;
            newX = Math.max(0, Math.min(w - ew, newX));
            newY = Math.max(0, Math.min(h - eh, newY));
            el.style.left = newX + 'px'; el.style.top = newY + 'px';
            e.preventDefault();
        };
        const onEnd = () => {
            if (!isDragging) return;
            isDragging = false; el.style.zIndex = 999998; el.style.cursor = 'grab';
            const rect = el.getBoundingClientRect();
            state.positions[storageKey] = { x: rect.left, y: rect.top };
            saveState();
        };
        el.style.cursor = 'grab';
        el.addEventListener('mousedown', onStart); el.addEventListener('touchstart', onStart, { passive: false });
        document.addEventListener('mousemove', onMove); document.addEventListener('touchmove', onMove, { passive: false });
        document.addEventListener('mouseup', onEnd); document.addEventListener('touchend', onEnd);
    }

    // ─── Keyboard Shortcuts ────────────────────────────────────────────
    document.addEventListener('keydown', (e) => {
        if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName) && e.target.id !== 'rc-customcss') return;
        if (e.altKey) {
            if (e.key.toLowerCase() === 'c') { e.preventDefault(); togglePanel(); }
            else if (e.key.toLowerCase() === 's') { e.preventDefault(); const n = prompt("Save current settings as preset name:"); saveCurrentAsPreset(n); }
            else if (e.key.toLowerCase() === 'r') { e.preventDefault(); resetCurrentSettings(); }
        }
    });

    // ─── Event Binding ─────────────────────────────────────────────────
    function bindEvents() {
        if (!panel) return;
        const q = (id) => panel.querySelector(`#${id}`);
        q('rc-fontsize').addEventListener('input', e => q('rc-fs-val').textContent = `${e.target.value}px`);
        q('rc-accent').addEventListener('input', e => updatePanelTheme(e.target.value));
        q('rc-close').addEventListener('click', () => panel.style.display = 'none');

        q('rc-preset-sel').addEventListener('change', e => applyPreset(e.target.value));
        q('rc-save-preset').addEventListener('click', () => { const n = prompt("Preset name:"); saveCurrentAsPreset(n); });
        q('rc-del-preset').addEventListener('click', () => deletePreset(state.activePreset));

        q('rc-apply').addEventListener('click', () => {
            state.settings.fontSize = q('rc-fontsize').value; state.settings.accentColor = q('rc-accent').value;
            state.settings.maxWidth = q('rc-maxwidth').value; state.settings.panelTransparency = q('rc-paneltrans').value;
            state.settings.compactMode = q('rc-compact').checked; state.settings.hideSidebar = q('rc-sidebar').checked;
            state.settings.hideTopNav = q('rc-topnav').checked; state.settings.hideAds = q('rc-ads').checked;
            state.settings.hideChat = q('rc-chat').checked; state.settings.hideAwards = q('rc-awards').checked;
            state.settings.hideCommentScores = q('rc-hidescores').checked; state.settings.highlightOP = q('rc-op').checked;
            state.settings.thumbnailSize = q('rc-thumb').value; state.settings.disableAnimations = q('rc-animations').checked;
            state.settings.customScrollbar = q('rc-scrollbar').checked; state.settings.bgImage = q('rc-bgimg').value.trim();
            state.settings.customCSS = q('rc-customcss').value;
            saveState(); applyStyles(); updatePanelTheme(); panel.style.display = 'none';
        });

        q('rc-reset').addEventListener('click', resetCurrentSettings);

        document.addEventListener('click', e => {
            if (panel?.style.display === 'block' && !panel.contains(e.target) && e.target !== btn) panel.style.display = 'none';
        });
    }

    function resetCurrentSettings() {
        state.settings = { ...DEFAULT_SETTINGS };
        state.activePreset = "Default";
        saveState(); applyStyles(); populatePanel(); btn.style.background = state.settings.accentColor;
    }

    // ─── Initialization ────────────────────────────────────────────────
    function init() {
        createButton();
        createPanel();
        bindEvents();
        applyStyles();

        observer = new MutationObserver(() => { clearTimeout(updateTimer); updateTimer = setTimeout(applyStyles, 200); });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
})();