Kogama Plus

3 IN 1: Theme Editor + Font Editor + Player Count

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Kogama Plus
// @namespace    http://tampermonkey.net/
// @version      1.01
// @author       BlueEL
// @icon         https://www.kogama.com/favicon.ico
// @description  3 IN 1: Theme Editor + Font Editor + Player Count
// @match        https://www.kogama.com/*
// @grant        none
// @license Copyright (c) 2026 BlueEL - All rights reserved.
// ==/UserScript==

(() => {
    'use strict';

    const APP_CONFIG = {
        NAV_OPACITY: 0.1,
        PANEL_ID: 'kogama-theme-panel',
        PLAYER_COUNT_ID: 'kogama-player-count',
        ROOT_SELECTOR: '#root-page-mobile',
        CONTENT_CONTAINER_SELECTOR: '#content-container',
        GAME_CARD_SELECTOR: '.gmqKr',
        PLAYER_VALUE_SELECTOR: '._1ZdZA',
        ADS_LINK_SELECTOR: '.MuiCollapse-root a[href*="subscription"]',
        PANEL_TOP_OFFSET: 20,
        PANEL_RIGHT_OFFSET: 20,
        PANEL_WIDTH: 320,
        OBSERVER_DEBOUNCE_DELAY: 250,
        INITIAL_REFRESH_DELAY: 800,
        STATS_REFRESH_INTERVAL: 2000,
        FONT_RESULTS_LIMIT: 10,
        PANEL_SHORTCUT_KEY: 'S',
        DEFAULT_THEME: {
            background: '#0e0a1f',
            gradientStart: '#0e0a1f',
            gradientEnd: '#003138',
            textColor: '#ffffff',
            fontFamily: 'Inter'
        },
        DEFAULT_PANEL_STATE: {
            isOpen: true
        },
        FONT_FALLBACK: 'sans-serif',
FONT_OPTIONS: [
    'Inter',
    'Roboto',
    'Open Sans',
    'Lato',
    'Montserrat',
    'Poppins',
    'Raleway',
    'Ubuntu',
    'Oswald',
    'Merriweather',
    'Playfair Display',
    'Nunito',
    'Rubik',
    'Work Sans',
    'Fira Sans',
    'Quicksand',
    'Inconsolata',
    'DM Sans',
    'Manrope',
    'Space Grotesk',
    'Plus Jakarta Sans',
    'Source Sans 3',
    'Outfit',
    'Urbanist',
    'Mulish',

    'Bebas Neue',
    'Anton',
    'Archivo',
    'Barlow',
    'Barlow Condensed',
    'Barlow Semi Condensed',
    'Cabin',
    'Cairo',
    'Comfortaa',
    'Cormorant',
    'Cormorant Garamond',
    'Dancing Script',
    'Exo 2',
    'Hind',
    'Hind Madurai',
    'IBM Plex Sans',
    'IBM Plex Serif',
    'Josefin Sans',
    'Karla',
    'Libre Baskerville',
    'Libre Franklin',
    'M PLUS Rounded 1c',
    'Noto Sans',
    'Noto Serif',
    'Orbitron',
    'PT Sans',
    'PT Serif',
    'Rajdhani',
    'Red Hat Display',
    'Red Hat Text',
    'Sora',
    'Teko',
    'Titillium Web',
    'Varela Round',
    'Zilla Slab',
    'Asap',
    'Assistant',
    'Baloo 2',
    'Chivo',
    'Encode Sans',
    'Heebo',
    'Jost',
    'Kanit',
    'Lexend',
    'Maven Pro',
    'Public Sans',
    'Sen',
    'Syne',
    'Yantramanav'
],
        STORAGE_KEY: 'kogama-theme-editor-state',
        SELECTORS: {
            toolbar: '.MuiToolbar-root',
            stackHeader: '.MuiStack-root._2JO9f',
            sectionShell: '._3TORb',
            footer: 'footer',
            heroSurface: '._1q4mD',
            heroInnerSurface: '._1q4mD ._1sUGu ._1u05O',
            gameTitle: '.gmqKr ._1ZdZA',
            gameStatsItems: 'ul li'
        },
        ROUTES: {
            game: 'https://www.kogama.com/games/',
            build: 'https://www.kogama.com/build/'
        }
    };

    class DomCache {
        constructor() {
            this.documentHead = document.head;
            this.documentBody = document.body;
        }

        query(selector, root = document) {
            return root.querySelector(selector);
        }

        queryAll(selector, root = document) {
            return [...root.querySelectorAll(selector)];
        }

        getRootPage() {
            return this.query(APP_CONFIG.ROOT_SELECTOR);
        }

        getContentContainer() {
            return this.query(APP_CONFIG.CONTENT_CONTAINER_SELECTOR);
        }

        getGameCards() {
            return this.queryAll(APP_CONFIG.GAME_CARD_SELECTOR);
        }

        getPlayerValueElements() {
            return this.queryAll(APP_CONFIG.PLAYER_VALUE_SELECTOR);
        }

        getAdsLinks() {
            return this.queryAll(APP_CONFIG.ADS_LINK_SELECTOR);
        }
    }

    class StorageService {
        loadRawState() {
            try {
                const rawValue = localStorage.getItem(APP_CONFIG.STORAGE_KEY);
                if (!rawValue) {
                    return {};
                }

                const parsedValue = JSON.parse(rawValue);
                return parsedValue && typeof parsedValue === 'object' ? parsedValue : {};
            } catch {
                return {};
            }
        }

        saveRawState(nextState) {
            try {
                localStorage.setItem(APP_CONFIG.STORAGE_KEY, JSON.stringify(nextState));
            } catch {}
        }

        loadThemeState() {
            const storedState = this.loadRawState();
            return {
                ...APP_CONFIG.DEFAULT_THEME,
                background: typeof storedState.background === 'string' ? storedState.background : APP_CONFIG.DEFAULT_THEME.background,
                gradientStart: typeof storedState.gradientStart === 'string' ? storedState.gradientStart : APP_CONFIG.DEFAULT_THEME.gradientStart,
                gradientEnd: typeof storedState.gradientEnd === 'string' ? storedState.gradientEnd : APP_CONFIG.DEFAULT_THEME.gradientEnd,
                textColor: typeof storedState.textColor === 'string' ? storedState.textColor : APP_CONFIG.DEFAULT_THEME.textColor,
                fontFamily: typeof storedState.fontFamily === 'string' ? storedState.fontFamily : APP_CONFIG.DEFAULT_THEME.fontFamily
            };
        }

        saveThemeState(themeState) {
            const currentState = this.loadRawState();
            this.saveRawState({
                ...currentState,
                ...themeState
            });
        }

        loadPanelState() {
            const storedState = this.loadRawState();
            return {
                ...APP_CONFIG.DEFAULT_PANEL_STATE,
                isOpen: typeof storedState.isOpen === 'boolean' ? storedState.isOpen : APP_CONFIG.DEFAULT_PANEL_STATE.isOpen
            };
        }

        savePanelState(panelState) {
            const currentState = this.loadRawState();
            this.saveRawState({
                ...currentState,
                ...panelState
            });
        }
    }

    class StyleRegistry {
        constructor(domCache) {
            this.domCache = domCache;
            this.registeredStyles = new Map();
        }

        mountStyle(key, cssText) {
            if (this.registeredStyles.has(key)) {
                this.registeredStyles.get(key).textContent = cssText;
                return;
            }

            const styleElement = document.createElement('style');
            styleElement.textContent = cssText;
            this.domCache.documentHead.appendChild(styleElement);
            this.registeredStyles.set(key, styleElement);
        }
    }

    class FontCatalog {
        constructor() {
            this.loadedFonts = new Set();
        }

        searchFonts(queryText = '') {
            const normalizedQuery = queryText.trim().toLowerCase();

            return APP_CONFIG.FONT_OPTIONS
                .filter(fontName => fontName.toLowerCase().includes(normalizedQuery))
                .slice(0, APP_CONFIG.FONT_RESULTS_LIMIT);
        }

        loadFont(fontFamily) {
            if (!fontFamily || this.loadedFonts.has(fontFamily)) {
                return;
            }

            const fontLink = document.createElement('link');
            fontLink.rel = 'stylesheet';
            fontLink.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontFamily).replace(/%20/g, '+')}:wght@400;500;600;700&display=swap`;

            document.head.appendChild(fontLink);
            this.loadedFonts.add(fontFamily);
        }
    }

    class ThemeManager {
        constructor(domCache, storageService, styleRegistry, fontCatalog) {
            this.domCache = domCache;
            this.storageService = storageService;
            this.styleRegistry = styleRegistry;
            this.fontCatalog = fontCatalog;
            this.themeState = this.storageService.loadThemeState();
        }

        initialize() {
            this.fontCatalog.loadFont(this.themeState.fontFamily);
            this.applyStaticShellStyles();
            this.applyTheme();
        }

        getThemeState() {
            return { ...this.themeState };
        }

        updateTheme(patch) {
            this.themeState = {
                ...this.themeState,
                ...patch
            };

            this.storageService.saveThemeState(this.themeState);
            this.fontCatalog.loadFont(this.themeState.fontFamily);
            this.applyTheme();
        }

        applyStaticShellStyles() {
            this.styleRegistry.mountStyle('kogama-shell-overrides', `
                ${APP_CONFIG.SELECTORS.toolbar},
                ${APP_CONFIG.SELECTORS.stackHeader},
                ${APP_CONFIG.SELECTORS.sectionShell},
                ${APP_CONFIG.SELECTORS.footer},
                ${APP_CONFIG.SELECTORS.heroSurface},
                ${APP_CONFIG.SELECTORS.heroInnerSurface} {
                    background: rgba(0, 0, 0, ${APP_CONFIG.NAV_OPACITY}) !important;
                    background-image: none !important;
                }

                ${APP_CONFIG.SELECTORS.gameTitle} {
                    color: #ffffff !important;
                }
            `);
        }

        applyTheme() {
            const rootPage = this.domCache.getRootPage();
            if (!rootPage) {
                return;
            }

            const { background, gradientStart, gradientEnd, textColor, fontFamily } = this.themeState;

            rootPage.style.backgroundColor = background;
            rootPage.style.backgroundImage = `linear-gradient(135deg, ${gradientStart}, ${gradientEnd})`;
            rootPage.style.color = textColor;
            document.body.style.fontFamily = `"${fontFamily}", ${APP_CONFIG.FONT_FALLBACK}`;
        }
    }

    class NumberFormatter {
        formatCompactValue(textValue) {
            const sanitizedText = String(textValue).trim();
            const compactPattern = /([\d.,]+)\s*([kKmM])/;
            const match = sanitizedText.match(compactPattern);

            if (!match) {
                return sanitizedText;
            }

            const numericValue = Number.parseFloat(match[1].replace(/\s/g, '').replace(',', '.'));
            if (Number.isNaN(numericValue)) {
                return sanitizedText;
            }

            const roundedValue = Math.round(numericValue * 10) / 10;
            const outputValue = roundedValue.toFixed(1).replace(/\.0$/, '');

            return `${outputValue}${match[2].toUpperCase()}`;
        }
    }

    class StatsManager {
        constructor(domCache, numberFormatter) {
            this.domCache = domCache;
            this.numberFormatter = numberFormatter;
        }

        refreshGameStats() {
            const gameCards = this.domCache.getGameCards();
            if (!gameCards.length) {
                return;
            }

            gameCards.forEach(card => this.refreshCardStats(card));
        }

        refreshCardStats(cardElement) {
            const statItems = this.domCache.queryAll(APP_CONFIG.SELECTORS.gameStatsItems, cardElement);
            statItems.forEach(statItem => this.refreshStatItem(statItem));
        }

        refreshStatItem(statItem) {
            const textNode = [...statItem.childNodes].reverse().find(node => node.nodeType === Node.TEXT_NODE);
            if (!textNode) {
                return;
            }

            const nextValue = this.numberFormatter.formatCompactValue(textNode.textContent);
            textNode.textContent = ` ${nextValue}`;
        }
    }

    class AdsManager {
        constructor(domCache) {
            this.domCache = domCache;
        }

        removeSubscriptionAds() {
            const adLinks = this.domCache.getAdsLinks();
            if (!adLinks.length) {
                return;
            }

            adLinks.forEach(link => {
                const collapsibleContainer = link.closest('.MuiCollapse-root');
                if (collapsibleContainer) {
                    collapsibleContainer.remove();
                }
            });
        }
    }

    class PlayerCountManager {
        constructor(domCache) {
            this.domCache = domCache;
        }

        refreshPlayerCount() {
            if (!this.isSupportedRoute()) {
                this.removePlayerCountBadge();
                return;
            }

            const totalPlayers = this.calculatePlayerCount();
            const badgeElement = this.getOrCreatePlayerCountBadge();

            if (badgeElement) {
                badgeElement.textContent = `Players Online · ${totalPlayers}`;
            }
        }

        isSupportedRoute() {
            const currentUrl = window.location.href;

            return (
                currentUrl.startsWith(APP_CONFIG.ROUTES.game) ||
                currentUrl.startsWith(APP_CONFIG.ROUTES.build) ||
                currentUrl === "https://www.kogama.com/" ||
                currentUrl === "https://www.kogama.com"
            );
        }

        calculatePlayerCount() {
            return this.domCache.getPlayerValueElements().reduce((total, element) => {
                const value = Number.parseInt(element.textContent.trim(), 10);
                return Number.isNaN(value) ? total : total + value;
            }, 0);
        }

        getOrCreatePlayerCountBadge() {
            const existingBadge = document.getElementById(APP_CONFIG.PLAYER_COUNT_ID);
            if (existingBadge) {
                return existingBadge;
            }

            const targetContainer = this.domCache.getContentContainer();
            if (!targetContainer) {
                return null;
            }

            const badgeElement = document.createElement('div');
            badgeElement.id = APP_CONFIG.PLAYER_COUNT_ID;
            badgeElement.textContent = 'Players Online · 0';
            targetContainer.prepend(badgeElement);

            return badgeElement;
        }

        removePlayerCountBadge() {
            const badgeElement = document.getElementById(APP_CONFIG.PLAYER_COUNT_ID);
            if (badgeElement) {
                badgeElement.remove();
            }
        }
    }

    class PanelView {
        constructor(themeManager, fontCatalog) {
            this.themeManager = themeManager;
            this.fontCatalog = fontCatalog;
            this.elements = {};
        }

        mount() {
            this.render();
            this.bindElements();
            this.populateForm();
            this.renderFontResults();
        }

        bindThemeChange(handler) {
            const colorInputs = [
                this.elements.gradientStartInput,
                this.elements.gradientEndInput
            ];

            colorInputs.forEach(input => {
                input.addEventListener('input', () => {
                    handler({
                        gradientStart: this.elements.gradientStartInput.value,
                        gradientEnd: this.elements.gradientEndInput.value
                    });
                });
            });

            this.elements.fontSearchInput.addEventListener('input', () => {
                this.renderFontResults(this.elements.fontSearchInput.value);
            });

            this.elements.fontResultsList.addEventListener('click', event => {
                const fontOption = event.target.closest('[data-font-family]');
                if (!fontOption) {
                    return;
                }

                const selectedFont = fontOption.dataset.fontFamily;
                this.setSelectedFont(selectedFont);
                handler({ fontFamily: selectedFont });
            });
        }

        bindPanelClose(handler) {
            this.elements.closeButton.addEventListener('click', handler);
        }

        render() {
            const panelElement = document.createElement('aside');
            panelElement.id = APP_CONFIG.PANEL_ID;
            panelElement.setAttribute('aria-label', 'Theme Editor');

            panelElement.innerHTML = `
                <div class="kte-panel__shell">
                    <div class="kte-panel__header">
                        <div class="kte-panel__eyebrow">Appearance</div>
                        <div class="kte-panel__title-row">
                            <h2 class="kte-panel__title">Theme Editor</h2>
                            <button
                                id="kte-panel-close"
                                class="kte-panel__close"
                                type="button"
                                aria-label="Hide theme editor"
                                title="Hide theme editor (Shift+S to show)"
                            >
                                <span class="kte-panel__close-icon" aria-hidden="true">✕</span>
                            </button>
                        </div>
                        <p class="kte-panel__subtitle">Made by BlueEL.</p>
                    </div>

                    <section class="kte-panel__section">
                        <div class="kte-panel__section-header">
                            <h3 class="kte-panel__section-title">Colors</h3>
                            <span class="kte-panel__section-caption">Gradient</span>
                        </div>

                        <div class="kte-color-grid">
                            <label class="kte-field kte-field--color">
                                <span class="kte-field__label">Gradient Start</span>
                                <input id="kte-gradient-start" class="kte-field__input kte-field__input--color" type="color">
                            </label>

                            <label class="kte-field kte-field--color">
                                <span class="kte-field__label">Gradient End</span>
                                <input id="kte-gradient-end" class="kte-field__input kte-field__input--color" type="color">
                            </label>
                        </div>
                    </section>

                    <section class="kte-panel__section">
                        <div class="kte-panel__section-header">
                            <h3 class="kte-panel__section-title">Typography</h3>
                            <span class="kte-panel__section-caption">Search and apply fonts</span>
                        </div>

                        <label class="kte-field">
                            <span class="kte-field__label">Font Family</span>
                            <input id="kte-font-search" class="kte-field__input" type="text" placeholder="Search fonts...">
                        </label>

                        <div id="kte-font-results" class="kte-font-results" role="listbox" aria-label="Font results"></div>
                    </section>
                </div>
            `;

            document.body.appendChild(panelElement);
        }

        bindElements() {
            this.elements.panel = document.getElementById(APP_CONFIG.PANEL_ID);
            this.elements.closeButton = document.getElementById('kte-panel-close');
            this.elements.gradientStartInput = document.getElementById('kte-gradient-start');
            this.elements.gradientEndInput = document.getElementById('kte-gradient-end');
            this.elements.fontSearchInput = document.getElementById('kte-font-search');
            this.elements.fontResultsList = document.getElementById('kte-font-results');
        }

        populateForm() {
            const themeState = this.themeManager.getThemeState();

            this.elements.gradientStartInput.value = themeState.gradientStart;
            this.elements.gradientEndInput.value = themeState.gradientEnd;
            this.elements.fontSearchInput.value = '';
        }

        renderFontResults(searchQuery = '') {
            const availableFonts = this.fontCatalog.searchFonts(searchQuery);
            const activeFont = this.themeManager.getThemeState().fontFamily;

            this.elements.fontResultsList.innerHTML = availableFonts.map(fontFamily => {
                const isActive = fontFamily === activeFont;
                return `
                    <button
                        type="button"
                        class="kte-font-option${isActive ? ' is-active' : ''}"
                        data-font-family="${fontFamily}"
                        style="font-family: '${fontFamily}', ${APP_CONFIG.FONT_FALLBACK};"
                    >
                        <span class="kte-font-option__name">${fontFamily}</span>
                        ${isActive ? '<span class="kte-font-option__badge">Active</span>' : ''}
                    </button>
                `;
            }).join('');
        }

        setSelectedFont() {
            this.renderFontResults(this.elements.fontSearchInput.value);
        }

        show() {
            this.elements.panel.classList.remove('is-hidden');
            this.elements.panel.setAttribute('aria-hidden', 'false');
        }

        hide() {
            this.elements.panel.classList.add('is-hidden');
            this.elements.panel.setAttribute('aria-hidden', 'true');
        }

        isVisible() {
            return !this.elements.panel.classList.contains('is-hidden');
        }
    }

    class PanelController {
        constructor(panelView, storageService) {
            this.panelView = panelView;
            this.storageService = storageService;
            this.handleKeydown = this.handleKeydown.bind(this);
            this.handleCloseButtonClick = this.handleCloseButtonClick.bind(this);
        }

        initialize() {
            this.panelView.bindPanelClose(this.handleCloseButtonClick);
            document.addEventListener('keydown', this.handleKeydown);

            if (!this.storageService.loadPanelState().isOpen) {
                this.panelView.hide();
            }
        }

        handleCloseButtonClick() {
            this.hidePanel();
        }

        handleKeydown(event) {
            const pressedKey = String(event.key || '').toUpperCase();
            if (!event.shiftKey || pressedKey !== APP_CONFIG.PANEL_SHORTCUT_KEY) {
                return;
            }

            this.showPanel();
        }

        showPanel() {
            if (this.panelView.isVisible()) {
                return;
            }

            this.panelView.show();
            this.storageService.savePanelState({ isOpen: true });
        }

        hidePanel() {
            if (!this.panelView.isVisible()) {
                return;
            }

            this.panelView.hide();
            this.storageService.savePanelState({ isOpen: false });
        }
    }

    class PanelStyles {
        constructor(styleRegistry) {
            this.styleRegistry = styleRegistry;
        }

        mount() {
            this.styleRegistry.mountStyle('kogama-theme-panel', `
                #${APP_CONFIG.PANEL_ID} {
                    position: fixed;
                    top: ${APP_CONFIG.PANEL_TOP_OFFSET}px;
                    right: ${APP_CONFIG.PANEL_RIGHT_OFFSET}px;
                    width: ${APP_CONFIG.PANEL_WIDTH}px;
                    z-index: 999999;
                    color: rgba(255, 255, 255, 0.96);
                    font-family: Inter, ${APP_CONFIG.FONT_FALLBACK};
                    transition:
                        opacity 180ms ease,
                        transform 220ms ease,
                        visibility 220ms ease;
                    transform-origin: top right;
                }

                #${APP_CONFIG.PANEL_ID}.is-hidden {
                    opacity: 0;
                    visibility: hidden;
                    pointer-events: none;
                    transform: translateY(-8px) scale(0.985);
                }

                #${APP_CONFIG.PANEL_ID} *,
                #${APP_CONFIG.PLAYER_COUNT_ID} * {
                    box-sizing: border-box;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__shell {
                    background:
                        linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.06)),
                        rgba(12, 16, 28, 0.78);
                    border: 1px solid rgba(255, 255, 255, 0.14);
                    backdrop-filter: blur(20px);
                    -webkit-backdrop-filter: blur(20px);
                    border-radius: 22px;
                    padding: 18px;
                    box-shadow:
                        0 20px 60px rgba(0, 0, 0, 0.38),
                        inset 0 1px 0 rgba(255, 255, 255, 0.08);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__header {
                    margin-bottom: 18px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__eyebrow {
                    font-size: 11px;
                    font-weight: 600;
                    letter-spacing: 0.12em;
                    text-transform: uppercase;
                    color: rgba(255, 255, 255, 0.52);
                    margin-bottom: 8px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__title-row {
                    display: flex;
                    align-items: flex-start;
                    justify-content: space-between;
                    gap: 10px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__title {
                    margin: 0;
                    font-size: 21px;
                    line-height: 1.1;
                    font-weight: 700;
                    letter-spacing: -0.03em;
                    padding-top: 2px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__close {
                    width: 32px;
                    height: 32px;
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    flex-shrink: 0;
                    margin: -2px -2px 0 0;
                    padding: 0;
                    border: 1px solid rgba(255, 255, 255, 0.12);
                    border-radius: 12px;
                    background:
                        linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04)),
                        rgba(255, 255, 255, 0.04);
                    color: rgba(255, 255, 255, 0.86);
                    cursor: pointer;
                    outline: none;
                    box-shadow:
                        inset 0 1px 0 rgba(255, 255, 255, 0.06),
                        0 8px 20px rgba(0, 0, 0, 0.14);
                    transition:
                        transform 160ms ease,
                        background-color 160ms ease,
                        border-color 160ms ease,
                        color 160ms ease,
                        box-shadow 160ms ease;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__close:hover {
                    transform: translateY(-1px);
                    color: rgba(255, 255, 255, 0.96);
                    border-color: rgba(255, 255, 255, 0.2);
                    background:
                        linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.06)),
                        rgba(255, 255, 255, 0.06);
                    box-shadow:
                        inset 0 1px 0 rgba(255, 255, 255, 0.08),
                        0 12px 24px rgba(0, 0, 0, 0.18);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__close:focus-visible {
                    border-color: rgba(138, 180, 248, 0.72);
                    box-shadow:
                        0 0 0 4px rgba(138, 180, 248, 0.16),
                        inset 0 1px 0 rgba(255, 255, 255, 0.08);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__close-icon {
                    font-size: 14px;
                    line-height: 1;
                    transform: translateY(-0.5px);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__subtitle {
                    margin: 10px 0 0;
                    font-size: 13px;
                    line-height: 1.5;
                    color: rgba(255, 255, 255, 0.68);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__section {
                    padding: 14px;
                    border-radius: 18px;
                    background: rgba(255, 255, 255, 0.04);
                    border: 1px solid rgba(255, 255, 255, 0.08);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__section + .kte-panel__section {
                    margin-top: 12px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__section-header {
                    display: flex;
                    align-items: baseline;
                    justify-content: space-between;
                    gap: 10px;
                    margin-bottom: 12px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__section-title {
                    margin: 0;
                    font-size: 14px;
                    font-weight: 600;
                    color: rgba(255, 255, 255, 0.94);
                }

                #${APP_CONFIG.PANEL_ID} .kte-panel__section-caption {
                    font-size: 11px;
                    color: rgba(255, 255, 255, 0.48);
                }

                #${APP_CONFIG.PANEL_ID} .kte-color-grid {
                    display: grid;
                    grid-template-columns: repeat(2, minmax(0, 1fr));
                    gap: 10px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-field {
                    display: flex;
                    flex-direction: column;
                    gap: 7px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__label {
                    font-size: 12px;
                    font-weight: 500;
                    color: rgba(255, 255, 255, 0.72);
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input {
                    width: 100%;
                    border: 1px solid rgba(255, 255, 255, 0.1);
                    background: rgba(255, 255, 255, 0.06);
                    color: rgba(255, 255, 255, 0.96);
                    border-radius: 12px;
                    padding: 12px 14px;
                    outline: none;
                    transition:
                        border-color 160ms ease,
                        background-color 160ms ease,
                        transform 160ms ease,
                        box-shadow 160ms ease;
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input::placeholder {
                    color: rgba(255, 255, 255, 0.34);
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input:hover {
                    background: rgba(255, 255, 255, 0.08);
                    border-color: rgba(255, 255, 255, 0.16);
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input:focus {
                    border-color: rgba(138, 180, 248, 0.72);
                    background: rgba(255, 255, 255, 0.08);
                    box-shadow: 0 0 0 4px rgba(138, 180, 248, 0.16);
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input--color {
                    min-height: 48px;
                    padding: 6px;
                    cursor: pointer;
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input--color::-webkit-color-swatch-wrapper {
                    padding: 0;
                }

                #${APP_CONFIG.PANEL_ID} .kte-field__input--color::-webkit-color-swatch {
                    border: none;
                    border-radius: 9px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-results {
                    display: flex;
                    flex-direction: column;
                    gap: 8px;
                    max-height: 260px;
                    overflow: auto;
                    padding-right: 2px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-results::-webkit-scrollbar {
                    width: 8px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-results::-webkit-scrollbar-thumb {
                    background: rgba(255, 255, 255, 0.16);
                    border-radius: 999px;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-option {
                    width: 100%;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    gap: 12px;
                    text-align: left;
                    padding: 12px 14px;
                    border-radius: 14px;
                    border: 1px solid rgba(255, 255, 255, 0.08);
                    background: rgba(255, 255, 255, 0.05);
                    color: rgba(255, 255, 255, 0.94);
                    cursor: pointer;
                    transition:
                        transform 160ms ease,
                        background-color 160ms ease,
                        border-color 160ms ease,
                        box-shadow 160ms ease;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-option:hover {
                    transform: translateY(-1px);
                    background: rgba(255, 255, 255, 0.08);
                    border-color: rgba(255, 255, 255, 0.14);
                    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.18);
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-option.is-active {
                    background: linear-gradient(180deg, rgba(138, 180, 248, 0.18), rgba(138, 180, 248, 0.1));
                    border-color: rgba(138, 180, 248, 0.45);
                    box-shadow: 0 10px 28px rgba(138, 180, 248, 0.14);
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-option__name {
                    font-size: 14px;
                    font-weight: 500;
                    line-height: 1.3;
                }

                #${APP_CONFIG.PANEL_ID} .kte-font-option__badge {
                    flex-shrink: 0;
                    font-size: 11px;
                    font-weight: 600;
                    letter-spacing: 0.02em;
                    padding: 5px 8px;
                    border-radius: 999px;
                    color: rgba(255, 255, 255, 0.92);
                    background: rgba(138, 180, 248, 0.18);
                    border: 1px solid rgba(138, 180, 248, 0.28);
                }

                #${APP_CONFIG.PLAYER_COUNT_ID} {
                    display: inline-flex;
                    align-items: center;
                    gap: 8px;
                    margin: 14px 0 4px;
                    margin-left: 10px;
                    padding: 10px 14px;
                    border-radius: 999px;
                    background: rgba(15, 20, 32, 0.72);
                    color: rgba(255, 255, 255, 0.96);
                    border: 1px solid rgba(255, 255, 255, 0.1);
                    box-shadow:
                        0 10px 30px rgba(0, 0, 0, 0.2),
                        inset 0 1px 0 rgba(255, 255, 255, 0.05);
                    backdrop-filter: blur(12px);
                    -webkit-backdrop-filter: blur(12px);
                    font-size: 13px;
                    font-weight: 700;
                    letter-spacing: 0.01em;
                    width: fit-content;
                }

                @media (max-width: 980px) {
                    #${APP_CONFIG.PANEL_ID} {
                        top: 12px;
                        right: 12px;
                        width: min(calc(100vw - 24px), ${APP_CONFIG.PANEL_WIDTH}px);
                    }
                }

                @media (max-width: 640px) {
                    #${APP_CONFIG.PANEL_ID} .kte-panel__shell {
                        padding: 14px;
                        border-radius: 18px;
                    }
                }
            `);
        }
    }

    class MutationCoordinator {
        constructor(callback) {
            this.callback = callback;
            this.observer = null;
            this.debounceTimer = null;
        }

        start() {
            this.observer = new MutationObserver(() => this.schedule());
            this.observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        schedule() {
            window.clearTimeout(this.debounceTimer);
            this.debounceTimer = window.setTimeout(() => {
                this.callback();
            }, APP_CONFIG.OBSERVER_DEBOUNCE_DELAY);
        }
    }

    class KogamaCustomizerApp {
        constructor() {
            this.domCache = new DomCache();
            this.storageService = new StorageService();
            this.styleRegistry = new StyleRegistry(this.domCache);
            this.fontCatalog = new FontCatalog();
            this.themeManager = new ThemeManager(this.domCache, this.storageService, this.styleRegistry, this.fontCatalog);
            this.numberFormatter = new NumberFormatter();
            this.statsManager = new StatsManager(this.domCache, this.numberFormatter);
            this.adsManager = new AdsManager(this.domCache);
            this.playerCountManager = new PlayerCountManager(this.domCache);
            this.panelStyles = new PanelStyles(this.styleRegistry);
            this.panelView = new PanelView(this.themeManager, this.fontCatalog);
            this.panelController = new PanelController(this.panelView, this.storageService);
            this.mutationCoordinator = new MutationCoordinator(() => this.refreshDynamicUi());
            this.statsIntervalId = null;
        }

        start() {
            this.panelStyles.mount();
            this.themeManager.initialize();
            this.panelView.mount();
            this.panelView.bindThemeChange(themePatch => this.handleThemeChange(themePatch));
            this.panelController.initialize();
            this.mutationCoordinator.start();
            this.startStatsPolling();
            this.runInitialRefresh();
        }

        handleThemeChange(themePatch) {
            this.themeManager.updateTheme(themePatch);
        }

        refreshDynamicUi() {
            this.adsManager.removeSubscriptionAds();
            this.playerCountManager.refreshPlayerCount();
            this.themeManager.applyTheme();
        }

        startStatsPolling() {
            this.statsIntervalId = window.setInterval(() => {
                this.statsManager.refreshGameStats();
            }, APP_CONFIG.STATS_REFRESH_INTERVAL);
        }

        runInitialRefresh() {
            window.setTimeout(() => {
                this.statsManager.refreshGameStats();
                this.refreshDynamicUi();
            }, APP_CONFIG.INITIAL_REFRESH_DELAY);
        }
    }

    new KogamaCustomizerApp().start();
})();