NeoClassic Theme

A theme to bring back the NeoClassic era (2019-2020) roblox webpage.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         NeoClassic Theme
// @namespace    https://roblox.com/
// @version      1.0
// @license      MIT
// @description  A theme to bring back the NeoClassic era (2019-2020) roblox webpage.
// @match        https://www.roblox.com/*
// @run-at       document-idle
// @icon         https://thatonekeysystem.netlify.app/neoclassic.ico
// ==/UserScript==

(function () {
    'use strict';

    // ====== KEY SYSTEM UI ======
const UNLOCK_STORAGE_KEY = "roblox_ui_unlocked_v1";
const WORKINK_URL = "https://work.ink/2gv6/revertblox"; // <-- put your link here
const KEY_PREFIX = "RBX-";

function isUnlocked() {
    return localStorage.getItem(UNLOCK_STORAGE_KEY) === "true";
}

function createKeyUI() {
    const overlay = document.createElement("div");
    overlay.id = "rbx-key-overlay";
    overlay.innerHTML = `
        <div class="rbx-key-box">
            <h2>Bringing back old Roblox, for you.</h2>
            <p>Just for a favor, please go through the work.ink link to get a one-time key and unlock this theme forever.</p>

            <button id="rbx-get-key">Get your key</button>

            <input id="rbx-key-input" placeholder="Enter key here" />
            <button id="rbx-unlock">Unlock forever</button>

            <p class="rbx-note">One-time unlock • Saved permanently • Made by Ryan</p>
        </div>
    `;

    const style = document.createElement("style");
    style.textContent = `
        #rbx-key-overlay {
            position: fixed;
            inset: 0;
            background: rgba(0,0,0,0.85);
            z-index: 999999;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: system-ui, sans-serif;
        }

        .rbx-key-box {
            background: #1c1c1c;
            padding: 24px;
            border-radius: 12px;
            width: 320px;
            text-align: center;
            color: #eaeaea;
        }

        .rbx-key-box h2 {
            margin-bottom: 10px;
        }

        .rbx-key-box button {
            width: 100%;
            margin-top: 10px;
            padding: 10px;
            background: #01b270;
            border: none;
            border-radius: 8px;
            color: black;
            font-weight: bold;
            cursor: pointer;
        }

        .rbx-key-box input {
            width: 100%;
            margin-top: 12px;
            padding: 10px;
            border-radius: 8px;
            border: none;
            outline: none;
        }

        .rbx-note {
            font-size: 12px;
            opacity: 0.7;
            margin-top: 10px;
        }
    `;

    document.head.appendChild(style);
    document.body.appendChild(overlay);

    document.getElementById("rbx-get-key").onclick = () => {
        window.open(WORKINK_URL, "_blank");
    };

    document.getElementById("rbx-unlock").onclick = () => {
        const key = document.getElementById("rbx-key-input").value.trim();

        if (key.startsWith(KEY_PREFIX) && key.length >= 10) {
            localStorage.setItem(UNLOCK_STORAGE_KEY, "true");
            overlay.remove();
            location.reload();
        } else {
            alert("❌ Invalid key");
        }
    };
}

// BLOCK SCRIPT IF NOT UNLOCKED
if (!isUnlocked()) {
    // Delay until DOM exists
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", createKeyUI);
    } else {
        createKeyUI();
    }

    // HARD STOP: nothing below runs
    throw new Error("Script locked");
}

    /* ---------- COLOR REPLACEMENTS ---------- */
    const COLOR_MAP = {
        // #121215 → #232527
        'rgb(18, 18, 21)': 'rgb(35, 37, 39)',
        '#121215': 'rgb(35, 37, 39)',

        // #272930 → #3a3b3d
        'rgb(39, 41, 48)': 'rgb(58, 59, 61)',
        '#272930': 'rgb(58, 59, 61)',

        // #191a1f → #1a1b1d
        'rgb(25, 26, 31)': 'rgb(26, 27, 29)',
        '#191a1f': 'rgb(26, 27, 29)',

        // #335fff → #01b270
        'rgb(51, 95, 255)': 'rgb(1, 178, 112)',
        '#335fff': 'rgb(1, 178, 112)'
    };

    function normalizeColor(value) {
        return value.replace(/\s+/g, '').toLowerCase();
    }

    function replaceColor(el, prop, value) {
        const normalized = normalizeColor(value);
        for (const key in COLOR_MAP) {
            if (normalized === normalizeColor(key)) {
                el.style[prop] = COLOR_MAP[key];
            }
        }
    }

    /* ---------- HIDE FRIEND TILE BUTTON ---------- */
    function hideFriendTileButtons() {
        document
            .querySelectorAll('.friends-carousel-tile .friend-tile-button')
            .forEach(btn => {
                btn.style.display = 'none';
            });
    }

    /* ---------- TEXT REPLACEMENTS (GLOBAL) ---------- */
    const TEXT_REPLACEMENTS = {
        'Charts': 'Discover',
        'Marketplace': 'Avatar Shop'
    };

    function replaceTextGlobally() {
        document.querySelectorAll('*').forEach(el => {
            if (
                el.childNodes.length === 1 &&
                el.childNodes[0].nodeType === Node.TEXT_NODE
            ) {
                const text = el.textContent.trim();
                if (TEXT_REPLACEMENTS[text]) {
                    el.textContent = TEXT_REPLACEMENTS[text];
                }
            }
        });
    }

    /* ---------- RENAME "CONTINUE" (HOME ONLY) ---------- */
    function replaceContinueText() {
        if (!location.pathname.startsWith('/home')) return;

        document.querySelectorAll('*').forEach(el => {
            if (
                el.childNodes.length === 1 &&
                el.childNodes[0].nodeType === Node.TEXT_NODE &&
                el.textContent.trim() === 'Continue'
            ) {
                el.textContent = 'Continue Playing';
            }
        });
    }

    /* ---------- HIDE PUSH-RIGHT SVG ICON ---------- */
    function hidePushRightIcon() {
        document.querySelectorAll(
            'svg.css-v3f6ic-iconBaseStyles.css-1y0a2rp-iconOverride.css-5x2vn1-iconBaseStyles.sdui-icon.icon-push-right-16x16'
        ).forEach(svg => {
            svg.style.display = 'none';
        });
    }

    function fixUI() {
        document.querySelectorAll('*').forEach(el => {
            const style = getComputedStyle(el);

            /* ---------- COLOR REPLACEMENTS ---------- */
            replaceColor(el, 'backgroundColor', style.backgroundColor);
            replaceColor(el, 'color', style.color);
            replaceColor(el, 'borderColor', style.borderColor);
        });

        hideFriendTileButtons();
        replaceTextGlobally();
        replaceContinueText();
        hidePushRightIcon();
    }

    fixUI();

    new MutationObserver(fixUI).observe(document.body, {
        childList: true,
        subtree: true
    });
})();

function replaceConnectionText(root = document.body) {
  const replacements = [
    ["Connections", "Friends"],
    ["Official Store", "Merchandise"],
    ["Communities", "Groups"],
    ["Community", "Group"],
    ["Buy Gift Cards", "Gift Cards"],
    ["Get Premium", "Upgrade Now"],
    ["Connection", "Friend"],
    ["connections", "friends"],
    ["connection", "friend"],
    ["Connect", "Friends"],
    ["connect", "friend"]
  ];

  const walker = document.createTreeWalker(
    root,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );

  let node;
  while ((node = walker.nextNode())) {
    let text = node.nodeValue;
    replacements.forEach(([from, to]) => {
      text = text.replaceAll(from, to);
    });
    node.nodeValue = text;
  }
}

// Run once on load
replaceConnectionText();

// Re-run whenever Roblox updates the page dynamically
const observer = new MutationObserver(mutations => {
  mutations.forEach(m => {
    m.addedNodes.forEach(node => {
      if (node.nodeType === 1) {
        replaceConnectionText(node);
      }
    });
  });
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});

const style = document.createElement("style");
style.textContent = `
.dark-theme .rbx-header .navbar-search .new-input-field {
  background-color: #0a0c0b;
}

.dark-theme .rbx-header .navbar-search .new-input-field::placeholder {
  color: #616965;
  opacity: 1;
}

`;
document.head.appendChild(style);

function removeHomePageGameGrid() {
    document.querySelectorAll('.home-page-game-grid').forEach(el => {
        el.remove();
    });
}

// run once
removeHomePageGameGrid();

// keep it gone if Roblox re-inserts it
new MutationObserver(removeHomePageGameGrid).observe(document.body, {
    childList: true,
    subtree: true
});


(function() {
    'use strict';

    // This is the exact selector from your AdBlocker rule.
    // We are targeting the first tile in the friends carousel list.
    const selector = '#HomeContainer > div.place-list-container:last-child > div.game-home-page-container > div > div.friend-carousel-container:first-child > div.react-friends-carousel-container > div:last-child > div.friends-carousel-container > div.friends-carousel-list-container > div.friends-carousel-tile:first-child';

    // This function will find and remove the element.
    function removeFriendsCarousel() {
        const elementToRemove = document.querySelector(selector);

        if (elementToRemove) {
            console.log('Tampermonkey: Found and removing the Friends Carousel tile.');
            elementToRemove.remove();

            // IMPORTANT: Once we've found and removed the element, we can stop
            // observing the page to save resources.
            observer.disconnect();
        }
    }

    // Create a MutationObserver to watch for changes in the document's body.
    // This is necessary because the carousel is loaded dynamically.
    const observer = new MutationObserver((mutationsList, obs) => {
        // We don't need to inspect the mutations, just run our check function.
        removeFriendsCarousel();
    });

    // Start observing the document body for added nodes.
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Also, run the check once immediately in case the element is already there.
    removeFriendsCarousel();

})();


(function() {
    'use strict';

    console.log('Tampermonkey: "Remove Next Sibling" script started.');

    // This function finds the friends carousel and removes the element right after it.
    function removeElementAfterCarousel() {
        // This selector targets the Friends Carousel container, which should be stable.
        const carouselSelector = '.friend-carousel-container';

        const carousel = document.querySelector(carouselSelector);

        // If the carousel exists...
        if (carousel) {
            // ...get the very next element sibling.
            const elementToRemove = carousel.nextElementSibling;

            // If a next sibling exists and it's a DIV...
            if (elementToRemove && elementToRemove.tagName === 'DIV') {
                console.log('Tampermonkey: Found element after carousel. Removing it:', elementToRemove);
                elementToRemove.remove();

                // We're done, so stop observing.
                observer.disconnect();
                console.log('Tampermonkey: Task complete. Observer disconnected.');
            } else {
                console.log('Tampermonkey: Found carousel, but no DIV element to remove after it.');
            }
        }
    }

    const observer = new MutationObserver(removeElementAfterCarousel);
    observer.observe(document.body, { childList: true, subtree: true });

    // Run once immediately
    removeElementAfterCarousel();

})();


(function () {
    'use strict';

    const selector = 'span.css-v3f6ic-iconBaseStyles.css-1y0a2rp-iconOverride.css-5x2vn1-iconBaseStyles.sdui-icon.icon-push-right-16x16';

    function removeIcon() {
        document.querySelectorAll(selector).forEach(el => {
            // Option 1: remove from DOM
            el.remove();

            // Option 2 (fallback): hide if Roblox reinserts it
            // el.style.display = 'none';
        });
    }

    // Run once
    removeIcon();

    // Keep removing if Roblox re-renders UI
    const observer = new MutationObserver(removeIcon);
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

/* =====================================================
   ROBLOX PROFILE GREETING + CSS FIXES
===================================================== */

(async () => {
    'use strict';

    // ----- CSS INJECTION -----
    const style = document.createElement('style');
    style.textContent = `
        /* Flex display for profile header */
        #HomeContainer > div.section > div > h1{
            display: flex;
            align-items: center;
            margin-bottom: 15px;
            height: 100% !important;
            float: left;
        }

        /* FIX FOR ROPRO */
        #HomeContainer > div.section > div > div{
            margin-top: 0 !important;
        }
    `;
    document.head.appendChild(style);

    // ----- HELPER FUNCTIONS -----
    function waitForElementToExist(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) return resolve(document.querySelector(selector));

            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    resolve(document.querySelector(selector));
                    observer.disconnect();
                }
            });
            observer.observe(document.body, { subtree: true, childList: true });
        });
    }

    function waitForElement(selector, targetClass, targetSelector, callbackClass, callbackElement) {
        const targetElement = document.querySelector(selector);

        if (targetElement && targetElement.classList.contains(targetClass)) {
            callbackClass();
            return;
        }

        const observer = new MutationObserver(() => {
            if (targetElement && targetElement.classList.contains(targetClass)) {
                observer.disconnect();
                callbackClass();
            } else if (document.querySelector(targetSelector)) {
                observer.disconnect();
                callbackElement();
            }
        });

        observer.observe(document.body, { attributes: true, subtree: true });
    }

    async function setProfilePicture(imageElement){
        const avatarImage = await waitForElementToExist("#navigation .avatar img");
        imageElement.src = avatarImage?.src || "";
    }

    // ----- GREETINGS -----
    const greetings = {
        "id_id": "Halo","de_de": "Hallo","en_us": "Hello","es_es": "Hola",
        "fr_fr": "Bonjour","it_it": "Ciao","pt_br": "Olá","th_th": "สวัสดี",
        "zh_cn": "你好","zh_tw": "你好","ja_jp": "こんにちは","ko_kr": "안녕하세요",
        "ms_my": "Helo","nb_no": "Hei","sr_rs": "Здраво","da_dk": "Hej",
        "et_ee": "Tere","fil_ph": "Kamusta","hr_hr": "Bok","lv_lv": "Sveiki",
        "lt_lt": "Sveiki","hu_hu": "Helló","nl_nl": "Hallo","pl_pl": "Cześć",
        "ro_ro": "Salut","sq_al": "Përshëndetje","sl_sl": "Zdravo","sk_sk": "Ahoj",
        "fi_fi": "Hei","sv_se": "Hej","vi_vn": "Xin chào","tr_tr": "Merhaba",
        "uk_ua": "Привіт","cs_cz": "Ahoj","el_gr": "Γειά σας","bs_ba": "Zdravo",
        "bg_bg": "Здравейте","ru_ru": "Привет","kk_kz": "Сәлеметсіздерге",
        "ar_001": "مرحبًا","hi_in": "नमस्ते","bn_bd": "হ্যালো","si_lk": "හෙලෝ",
        "my_mm": "မင်္ဂလာ","ka_ge": "გამარჯობა","km_kh": "ជំរាប"
    };

    try {
        const userDataMeta = document.querySelector('meta[name="user-data"]');
        const userLocaleMeta = document.querySelector('meta[name="locale-data"]');

        const userId = userDataMeta?.getAttribute('data-userid') || '1';
        const displayName = userDataMeta?.getAttribute('data-displayname') || 'DisplayName';
        const isPremiumUser = userDataMeta?.getAttribute('data-ispremiumuser') === "true";

        const languageCode = userLocaleMeta?.getAttribute('data-language-code') || 'en_us';
        const greeting = greetings[languageCode] || "Hello";

        // Inject greeting HTML
        document.querySelector("#HomeContainer > div.section > div").innerHTML = `
            <h1>
                <a class="avatar avatar-card-fullbody" style="margin-right:15px;width:128px;height:128px;" href="/users/${userId}/profile">
                    <span class="avatar-card-link friend-avatar icon-placeholder-avatar-headshot" style="width:128px;height:128px;">
                        <thumbnail-2d class="avatar-card-image">
                            <span id="avatar-image" class="thumbnail-2d-container">
                                <img style="background-color: #d4d4d4"></img>
                            </span>
                        </thumbnail-2d>
                    </span>
                </a>
                ${isPremiumUser ? '<span class="icon-premium-medium" style="margin-right: 10px;"></span>' : ""}
                <a href="/users/${userId}/profile" class="user-name-container">${greeting}, ${displayName}!</a>
            </h1>
        `;

        const avatarImage = document.getElementById("avatar-image");
        const avatarImg = avatarImage.querySelector('img');

        // Wait for avatar or blocked icon
        waitForElement('#navigation .avatar .avatar-card-image', 'icon-blocked', "#navigation .avatar img",
            () => { avatarImage.classList.add("icon-blocked"); avatarImg.style.display = 'none'; },
            () => { setProfilePicture(avatarImg); }
        );

    } catch (err) {
        console.error(err);
    }

})();