Nitro Type - Mod Menu

Unified Nitro Type mod menu shell at /settings/mods with route takeover, mod tabs, and race-options-inspired settings layout.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Nitro Type - Mod Menu
// @namespace    https://nitrotype.info
// @version      3.1.0
// @description  Unified Nitro Type mod menu shell at /settings/mods with route takeover, mod tabs, and race-options-inspired settings layout.
// @author       Captain.Loveridge
// @match        https://www.nitrotype.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @connect      greasyfork.org
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const SCRIPT_SINGLETON_KEY = '__ntModMenuSingleton';
    if (window[SCRIPT_SINGLETON_KEY]) {
        return;
    }
    window[SCRIPT_SINGLETON_KEY] = true;

    const MOD_MENU_PATH = '/settings/mods';
    const DROPDOWN_ITEM_CLASS = 'ntmods-dropdown-item';
    const MANIFEST_PREFIX = 'ntcfg:manifest:';
    const ALIVE_PREFIX = 'ntcfg:alive:';
    const PAGE_SESSION_START = Date.now();
    const VERSION_CACHE_KEY = 'ntmods:version-cache';
    const VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
    const UI_STATE_KEY = 'ntmods:ui-state';
    const TAB_ORDER_KEY = 'ntmods:tab-order';
    const PREVIEW_VALUE_PREFIX = 'ntmods:preview:';
    const PREVIEW_FALLBACK_ENABLED = true;
    let renderInProgress = false;
    let modMenuPageSyncTimer = null;
    let pendingModMenuRouteSync = false;
    let _lastAliveSnapshot = '';
    let globalResetState = null;

    // ── Anti-flicker curtain (injected unconditionally, gated by class) ──
    // Matches the leaderboard script pattern: CSS is always present on every page,
    // but only activates when html.is-mods-route is set. This ensures the curtain
    // is already loaded for SPA navigations, not just fresh page loads.
    const curtainEl = document.createElement('style');
    curtainEl.id = 'ntmods-curtain';
    curtainEl.textContent = `
        html.is-mods-route main.structure-content {
            opacity: 0 !important;
            visibility: hidden !important;
        }
        html.is-mods-route main.structure-content.custom-loaded {
            opacity: 1 !important;
            visibility: visible !important;
            transition: opacity 0.15s ease-in;
        }
    `;
    (document.head || document.documentElement).appendChild(curtainEl);

    // If already on the mods route at document-start, activate the curtain immediately
    if (location.pathname.replace(/\/+$/, '') === MOD_MENU_PATH) {
        document.documentElement.classList.add('is-mods-route');
    }

    function initNTRouteHelper(targetWindow = window) {
        const hostWindow = targetWindow || window;
        const existing = hostWindow.NTRouteHelper;
        if (existing && existing.__ntRouteHelperReady && typeof existing.subscribe === 'function') {
            return existing;
        }

        const helper = existing || {};
        const listeners = helper.listeners instanceof Set ? helper.listeners : new Set();
        let currentKey = `${hostWindow.location.pathname}${hostWindow.location.search}${hostWindow.location.hash}`;

        const notify = (reason) => {
            const nextKey = `${hostWindow.location.pathname}${hostWindow.location.search}${hostWindow.location.hash}`;
            if (reason !== 'init' && nextKey === currentKey) return;
            const previousKey = currentKey;
            currentKey = nextKey;
            listeners.forEach((listener) => {
                try {
                    listener({
                        reason,
                        previous: previousKey,
                        current: nextKey,
                        pathname: hostWindow.location.pathname
                    });
                } catch (error) {
                    console.error('[NTRouteHelper] listener error', error);
                }
            });
            helper.currentKey = currentKey;
        };

        if (!helper.__ntRouteHelperWrapped) {
            const wrapHistoryMethod = (methodName) => {
                const current = hostWindow.history[methodName];
                if (typeof current !== 'function' || current.__ntRouteHelperWrapped) return;
                const wrapped = function () {
                    const result = current.apply(this, arguments);
                    queueMicrotask(() => notify(methodName));
                    return result;
                };
                wrapped.__ntRouteHelperWrapped = true;
                wrapped.__ntRouteHelperOriginal = current;
                hostWindow.history[methodName] = wrapped;
            };

            wrapHistoryMethod('pushState');
            wrapHistoryMethod('replaceState');
            hostWindow.addEventListener('popstate', () => queueMicrotask(() => notify('popstate')));
            helper.__ntRouteHelperWrapped = true;
        }

        helper.listeners = listeners;
        helper.currentKey = currentKey;
        helper.version = '1.0.0';
        helper.__ntRouteHelperReady = true;
        helper.subscribe = function (listener, options = {}) {
            if (typeof listener !== 'function') return () => { };
            listeners.add(listener);
            if (options.immediate !== false) {
                try {
                    listener({
                        reason: 'init',
                        previous: currentKey,
                        current: currentKey,
                        pathname: hostWindow.location.pathname
                    });
                } catch (error) {
                    console.error('[NTRouteHelper] immediate listener error', error);
                }
            }
            return () => listeners.delete(listener);
        };
        helper.notify = notify;

        hostWindow.NTRouteHelper = helper;
        return helper;
    }

    const toggleField = (key, label, defaultValue, help) => ({
        type: 'toggle',
        key,
        label,
        default: defaultValue,
        help
    });

    const numberField = (key, label, defaultValue, help, min = null, max = null, step = '1') => ({
        type: 'number',
        key,
        label,
        default: defaultValue,
        help,
        min,
        max,
        step
    });

    const selectField = (key, label, defaultValue, options, help) => ({
        type: 'select',
        key,
        label,
        default: defaultValue,
        options,
        help
    });

    const textField = (key, label, defaultValue, help, placeholder = '') => ({
        type: 'text',
        key,
        label,
        default: defaultValue,
        help,
        placeholder
    });

    const colorField = (key, label, defaultValue, help) => ({
        type: 'color',
        key,
        label,
        default: defaultValue,
        help
    });

    const noteField = (message, tone = 'info') => ({
        type: 'note',
        message,
        tone
    });

    const actionField = (key, label, style = 'primary', help = '') => ({
        type: 'action',
        key,
        label,
        style,
        help
    });

    const MODULE_LIBRARY = [
        {
            id: 'race-options',
            label: 'Race Options',
            shortLabel: 'RO',
            accent: '#1c99f4',
            description: 'Race overlays, pacing tools, and theme controls for the race screen.',
            installUrl: 'https://greasyfork.org/en/scripts/567849-nitro-type-race-options',
            previewSections: [
                {
                    id: 'general',
                    title: 'General',
                    subtitle: 'Visual cleanup, overlays, and race helpers.',
                    items: [
                        toggleField('hide_track', 'Hide Track', false, 'Removes the large race track art to keep the page cleaner.'),
                        toggleField('hide_notifications', 'Hide Notifications', true, 'Suppresses popups while a race is active.'),
                        toggleField('enable_mini_map', 'Enable Mini Map', false, 'Shows a compact lane map near the race text.'),
                        selectField('mini_map_position', 'Mini Map Position', 'bottom', [
                            { value: 'bottom', label: 'Bottom' },
                            { value: 'top', label: 'Top' }
                        ], 'Choose where the mini map sits relative to the race UI.'),
                        { ...toggleField('enable_racer_badges_in_race', 'Enable Racer Badges (In-Race)', true, 'Lets racer badges inject race nameplate icons.'), storageKey: 'ntcfg:racer-badges:ENABLE_RACE_BADGES', emitModule: 'racer-badges', emitKey: 'ENABLE_RACE_BADGES' },
                        noteField('This section is a visual shell right now. The live bridge will replace these preview keys with shared mod settings.', 'info')
                    ]
                },
                {
                    id: 'pacing',
                    title: 'Pacing',
                    subtitle: 'Alt WPM and reload flow.',
                    items: [
                        toggleField('enable_alt_wpm_counter', 'Enable Alt. WPM / Countdown', true, 'Displays the draggable pace helper during the race.'),
                        numberField('target_wpm', 'Target WPM', 79.5, 'Target pace used by the helper.', 1, 300, '0.1'),
                        numberField('indicate_wpm_within', 'Yellow Threshold', 2, 'Highlight the helper when pace is within this range.', 0, 30, '0.1'),
                        toggleField('reload_on_stats', 'Enable Auto Reload', true, 'Refresh after the results screen updates.'),
                        numberField('greedy_stats_reload_int', 'Fast Reload Interval (ms)', 50, 'Low values reload sooner but can be heavier.', 25, 500, '5'),
                        actionField('preview_reload_action', 'Preview Save and Reload', 'primary', 'A placeholder action so the footer/button styling can be reviewed.')
                    ]
                },
                {
                    id: 'theme',
                    title: 'Theme',
                    subtitle: 'Text colors, backgrounds, and typing feel.',
                    items: [
                        toggleField('theme_enable_dark_mode', 'Enable Dark Mode', false, 'Uses the darker theme palette on the race page.'),
                        colorField('theme_color_background', 'Background Color', '#0A121E', 'Main background color used for text theming.'),
                        colorField('theme_color_background_active', 'Active Word Background', '#1C99F4', 'Background color for the active word.'),
                        colorField('theme_color_foreground', 'Foreground Color', '#E7EEF8', 'Main text color for upcoming characters.'),
                        selectField('theme_font_family_preset', 'Font Family', 'roboto_mono', [
                            { value: 'roboto_mono', label: 'Roboto Mono' },
                            { value: 'montserrat', label: 'Montserrat' },
                            { value: 'space_mono', label: 'Space Mono' }
                        ], 'Preview typography for the race text.'),
                        toggleField('theme_enable_rainbow_typed_text', 'Rainbow Typed Text', false, 'Applies animated color cycling to typed text.')
                    ]
                }
            ]
        },
        {
            id: 'racer-badges',
            label: 'Racer Badges',
            shortLabel: 'RB',
            accent: '#ffd166',
            description: 'Top racer and top team badge placement, in-race badge rules, and badge presentation.',
            installUrl: 'https://greasyfork.org/en/scripts/555617-nitro-type-top-racer-team-badges',
            previewSections: [
                {
                    id: 'general',
                    title: 'General',
                    subtitle: 'Global badge and banner visibility controls.',
                    items: [
                        toggleField('SHOW_RACER_BADGES', 'Show Racer Badges', true, 'Show small inline top-racer ranking icons next to names in lists.'),
                        toggleField('SHOW_RACER_BANNERS', 'Show Racer Banners', true, 'Show large top-racer ranking images on profile and garage pages.'),
                        toggleField('SHOW_TEAM_BADGES', 'Show Team Badges', true, 'Show small inline top-team ranking icons next to names in lists.'),
                        toggleField('SHOW_TEAM_BANNERS', 'Show Team Banners', true, 'Show large top-team ranking images on team, profile, and garage pages.'),
                        noteField('Install the Racer Badges script for these settings to take effect.', 'info')
                    ]
                },
                {
                    id: 'pages',
                    title: 'Pages',
                    subtitle: 'Per-page badge visibility.',
                    items: [
                        toggleField('ENABLE_RACE_BADGES', 'Enable Race Page', true, 'Show badge overlays inside the race view on racer nameplates.'),
                        toggleField('ENABLE_PROFILE_BADGES', 'Enable Profile Pages', true, 'Show badges and banners on racer profile pages.'),
                        toggleField('ENABLE_TEAM_PAGE_BADGES', 'Enable Team Pages', true, 'Show badges and banners on team pages.'),
                        toggleField('ENABLE_GARAGE_BADGES', 'Enable Garage Pages', true, 'Show badges and banners on garage pages.'),
                        toggleField('ENABLE_FRIENDS_BADGES', 'Enable Friends Page', true, 'Show badges on the friends page.'),
                        toggleField('ENABLE_LEAGUES_BADGES', 'Enable Leagues Page', true, 'Show badges inside league tables.'),
                        toggleField('ENABLE_LEAGUES_TEAM_BADGES', 'Enable Team Tab League Badges', true, 'Show top-team badges on the Team tab of the leagues page, even if global team badges are disabled.'),
                        toggleField('ENABLE_HEADER_BADGE', 'Enable Header Badge', true, 'Display your own badge in the site header.'),
                        noteField('Install the Racer Badges script for these settings to take effect.', 'info')
                    ]
                },
                {
                    id: 'advanced',
                    title: 'Advanced',
                    subtitle: 'Debug and diagnostic controls.',
                    items: [
                        toggleField('DEBUG_LOGGING', 'Debug Logging', false, 'Enable verbose console logging for troubleshooting.'),
                        noteField('Install the Racer Badges script for these settings to take effect.', 'info')
                    ]
                }
            ]
        },
        {
            id: 'leaderboards',
            label: 'Leaderboards',
            shortLabel: 'LB',
            accent: '#6c8cff',
            description: 'StarTrack leaderboard routing, cached timeframes, and background refresh behavior.',
            installUrl: 'https://greasyfork.org/en/scripts/555066-nitro-type-startrack-leaderboard-integration',
            previewSections: [
                {
                    id: 'views',
                    title: 'Views',
                    subtitle: 'Top-level presentation and default landing behavior.',
                    items: [
                        selectField('default_view', 'Default View', 'individual', [
                            { value: 'individual', label: 'Top Racers' },
                            { value: 'team', label: 'Top Teams' }
                        ], 'Controls which leaderboard view opens first.'),
                        selectField('default_timeframe', 'Default Timeframe', 'season', [
                            { value: 'season', label: 'Season' },
                            { value: '24hr', label: 'Last 24 Hours' },
                            { value: '7day', label: 'Last 7 Days' }
                        ], 'Preview the default timeframe selection.'),
                        toggleField('highlight_position_change', 'Highlight Position Change', true, 'Show movement arrows beside leaderboard entries.'),
                        toggleField('manual_refresh_button', 'Show Manual Refresh Button', true, 'Keep the refresh control visible in the page header.')
                    ]
                },
                {
                    id: 'sync',
                    title: 'Sync',
                    subtitle: 'Refresh cadence and cache freshness.',
                    items: [
                        toggleField('daily_background_sync', 'Daily Background Sync', true, 'Warm cache data for daily-style ranges.'),
                        toggleField('hourly_background_sync', 'Hourly Background Sync', true, 'Refresh fast-moving windows in the background.'),
                        numberField('cache_duration_minutes', 'Cache Duration (minutes)', 15, 'General cache freshness for rolling windows.', 1, 120, '1'),
                        actionField('preview_resync', 'Preview Sync All Tabs', 'primary', 'Placeholder for a future cross-tab refresh action.')
                    ]
                },
                {
                    id: 'presentation',
                    title: 'Presentation',
                    subtitle: 'Spacing, motion, and accent treatment.',
                    items: [
                        colorField('accent_color', 'Accent Color', '#1C99F4', 'Used by route highlights and selected pills.'),
                        toggleField('anti_flicker_mode', 'Anti-Flicker Route Mode', true, 'Hide the default page shell until the custom page is ready.'),
                        toggleField('show_route_tab', 'Show Leaderboards Nav Tab', true, 'Controls the custom nav insertion.'),
                        noteField('The mods page is borrowing this script\'s route takeover pattern for its own shell.', 'info')
                    ]
                }
            ]
        },
        {
            id: 'bot-flag',
            label: 'Bot Flag',
            shortLabel: 'BF',
            accent: '#ff9f1c',
            description: 'StarTrack + NTL flag display controls, cache behavior, and status indicators.',
            installUrl: 'https://greasyfork.org/en/scripts/529360-nitro-type-flag-check-startrack-ntl-legacy',
            previewSections: [
                {
                    id: 'display',
                    title: 'Display',
                    subtitle: 'Visual placement and on-page behavior.',
                    items: [
                        toggleField('show_team_page_flags', 'Show Team Page Flags', true, 'Render badges on team rosters and member lists.'),
                        toggleField('show_friends_page_flags', 'Show Friends Page Flags', true, 'Keep friend page annotations enabled.'),
                        toggleField('show_league_page_flags', 'Show League Page Flags', true, 'Display status icons inside league tables.'),
                        selectField('tooltip_style', 'Tooltip Style', 'rich', [
                            { value: 'rich', label: 'Rich Tooltip' },
                            { value: 'compact', label: 'Compact Tooltip' }
                        ], 'Preview how detailed each hover card should be.')
                    ]
                },
                {
                    id: 'sources',
                    title: 'Sources',
                    subtitle: 'Data source preferences and fetch policy.',
                    items: [
                        toggleField('use_startrack', 'Use StarTrack Data', true, 'Primary flag source for status checks.'),
                        toggleField('use_ntl_legacy', 'Use NTL Legacy Data', true, 'Fallback source for additional history.'),
                        numberField('network_concurrency_limit', 'Network Concurrency Limit', 6, 'Soft cap for simultaneous user lookups.', 1, 20, '1'),
                        toggleField('debug_logging', 'Debug Logging', false, 'Enable verbose logging for troubleshooting.')
                    ]
                },
                {
                    id: 'cache',
                    title: 'Cache',
                    subtitle: 'How long flag results should be kept around.',
                    items: [
                        numberField('startrack_cache_days', 'StarTrack Cache (days)', 7, 'How long StarTrack lookups should be considered fresh.', 1, 30, '1'),
                        toggleField('cross_tab_sync', 'Cross-Tab Sync', true, 'Broadcast fresh data to other open Nitro Type tabs.'),
                        actionField('clear_flag_cache', 'Preview Clear Cache', 'danger', 'Placeholder action button for the maintenance area.'),
                        noteField('Cache maintenance actions are visual-only in this shell pass.', 'warning')
                    ]
                }
            ]
        },
        {
            id: 'music-player',
            label: 'Music Player',
            shortLabel: 'MP',
            accent: '#1db954',
            description: 'Universal music playback shell for Spotify, YouTube, and Apple Music sources.',
            installUrl: 'https://greasyfork.org/en/scripts/567896-nitro-type-universal-music-player',
            previewSections: [
                {
                    id: 'source',
                    title: 'Source',
                    subtitle: 'Where the queue comes from.',
                    items: [
                        textField('source_url', 'Source URL', '', 'Spotify, YouTube, or Apple Music playlist/album/track URL.', 'Paste playlist, album, or track URL'),
                        selectField('default_platform', 'Default Platform', 'spotify', [
                            { value: 'spotify', label: 'Spotify' },
                            { value: 'youtube', label: 'YouTube' },
                            { value: 'apple-music', label: 'Apple Music' }
                        ], 'Controls the label styling and initial queue hint when no source is configured.'),
                        toggleField('session_resume', 'Resume Last Session', true, 'Restores queue position and shuffle mode after reload.'),
                        toggleField('auto_activate', 'Auto Activate on Race Load', true, 'Immediately bring the player shell online on race pages.')
                    ]
                },
                {
                    id: 'playback',
                    title: 'Playback',
                    subtitle: 'Queue behavior and in-race control feel.',
                    items: [
                        selectField('queue_mode', 'Queue Mode', 'shuffle', [
                            { value: 'shuffle', label: 'Shuffle' },
                            { value: 'sequential', label: 'Sequential' }
                        ], 'Controls how tracks are ordered during playback.'),
                        toggleField('show_album_art', 'Show Album Art', true, 'Display album cover artwork in the inline now-playing card.'),
                        toggleField('mute_native_station', 'Mute Native Nitro Type Station', true, 'Silence the built-in race station when this player is active.'),
                        numberField('progress_tick_ms', 'Progress Tick (ms)', 1000, 'How often the inline player UI refreshes progress text.', 250, 5000, '250')
                    ]
                },
                {
                    id: 'advanced',
                    title: 'Advanced',
                    subtitle: 'Debug and diagnostic controls.',
                    items: [
                        toggleField('debug_logging', 'Debug Logging', false, 'Enable verbose console logging for troubleshooting.')
                    ]
                }
            ]
        },
        {
            id: 'bot-hunter',
            label: 'Bot Hunter',
            shortLabel: 'BH',
            accent: '#d62f3a',
            description: 'Bot Hunter observation controls, observer identity, and pattern-analysis presentation.',
            hiddenUnlessInstalled: true,
            previewSections: [
                {
                    id: 'general',
                    title: 'General',
                    subtitle: 'Main enablement and capture behavior.',
                    items: [
                        toggleField('enabled', 'Enable Bot Hunter', true, 'Temporarily toggles Bot Hunter capture on and off.'),
                        toggleField('self_observe_mode', 'Self Observe Mode', true, 'Includes your own races in the payload for testing.'),
                        toggleField('show_floating_toggle', 'Show Floating Toggle', true, 'Keeps the in-page Bot Hunter button visible.'),
                        toggleField('enable_logging', 'Enable Logging', true, 'Writes Bot Hunter diagnostics to the console.')
                    ]
                },
                {
                    id: 'observer',
                    title: 'Observer',
                    subtitle: 'Observer identity and payload metadata.',
                    items: [
                        textField('observer_id', 'Observer ID', 'generated-on-demand', 'Visual placeholder for the observer identity field.', 'Observer UUID'),
                        textField('api_endpoint', 'API Endpoint', 'https://ntstartrack.org/api/bot-observations', 'Current destination for submitted observations.'),
                        toggleField('use_keepalive', 'Use Keepalive on Exit', true, 'Send pending data during page unload or route exit.'),
                        noteField('This tab is ideal for a later "copy observer ID" button once behavior wiring starts.', 'info')
                    ]
                },
                {
                    id: 'analysis',
                    title: 'Analysis',
                    subtitle: 'Pattern heuristics and suspicious-race review.',
                    items: [
                        toggleField('detect_1000ms_pattern', 'Detect 1000ms Pattern', true, 'Flag repeated speed changes near one-second boundaries.'),
                        toggleField('detect_excessive_smoothing', 'Detect Excessive Smoothing', true, 'Flag unnaturally smooth speed transitions.'),
                        numberField('boundary_tolerance_ms', 'Boundary Tolerance (ms)', 100, 'Tolerance used when checking one-second boundaries.', 25, 250, '5'),
                        actionField('preview_recent_observations', 'Preview Recent Observations', 'secondary', 'Layout-only action card for a future review drawer.')
                    ]
                }
            ]
        },
        {
            id: 'toolkit',
            label: 'Toolkit',
            shortLabel: 'TK',
            accent: '#36d399',
            description: 'Quality-of-life utilities: garage organizer, shop leaks, cash hiding, keyboard shortcuts & more.',
            installUrl: 'https://greasyfork.org/en/scripts/570637-nitro-type-toolkit',
            previewSections: [
                {
                    id: 'garage',
                    title: 'Garage',
                    subtitle: 'Garage section management and display options.',
                    items: [
                        toggleField('ENABLE_GARAGE_ORGANIZER', 'Garage Section Organizer', true, 'Add a button on the garage page to set the number of visible garage sections (pages of 30 cars).'),
                        actionField('GARAGE_ORGANIZER_ACTION', 'Open Garage Organizer', 'primary', 'Set the number of garage sections directly from the mod menu.'),
                        toggleField('HIDE_BUY_CASH', 'Hide Buy Cash Button', false, 'Hide the buy cash button in the Garage.')
                    ]
                },
                {
                    id: 'profile',
                    title: 'Profile',
                    subtitle: 'Profile icon, cash formatting, and donation links.',
                    items: [
                        toggleField('ENABLE_CAR_ICON', 'Car Profile Icon', false, 'Replace the green profile icon in the top-right with your current car.'),
                        toggleField('ENABLE_SEND_CASH_FORMAT', 'Readable Cash Amounts', true, 'Format large numbers with commas in the Send Cash modal.'),
                        selectField('DONATION_LINK_STYLE', 'Donation Profile Link', 'username', [
                            { value: 'off', label: 'Off' },
                            { value: 'username', label: 'Username Only' },
                            { value: 'full_url', label: 'Full URL (nitrotype.com/racer/username)' }
                        ], 'Show a clickable link to the donor\'s profile in cash gift modals.'),
                        toggleField('DONATION_LINK_NEW_TAB', 'Open Donation Link in New Tab', false, 'Open the donor profile link in a new browser tab instead of redirecting.')
                    ]
                },
                {
                    id: 'team',
                    title: 'Team',
                    subtitle: 'Team member labels and MOTD tools.',
                    items: [
                        toggleField('ENABLE_BANNED_LABELS', 'Banned/Warned Labels', true, 'Show ban/warn status badges on team member lists and the team header.'),
                        toggleField('ENABLE_MOTD_FILTER', 'MOTD Offensive Filter', true, 'Pre-check MOTD text for possibly offensive words before saving.'),
                        textField('CUSTOM_MOTD_WORDS', 'Custom Filter Words', '', 'Additional words to flag, separated by commas.', 'word1, word2, word3')
                    ]
                },
                {
                    id: 'shop',
                    title: 'Shop',
                    subtitle: 'Shop page previews and features.',
                    items: [
                        toggleField('ENABLE_SHOP_LEAKS', 'Shop Leaks', true, 'Show a preview of tomorrow\'s featured and daily shop items on the shop page.'),
                        noteField('Shop notification options (Hide Shop Notifications, Smart Shop Notifications) are available in the Notifications tab.', 'info')
                    ]
                },
                {
                    id: 'appearance',
                    title: 'Appearance',
                    subtitle: 'Hide or show page elements.',
                    items: [
                        toggleField('HIDE_SEASON_BANNER', 'Hide Season Banner', false, 'Hide the seasonal event banner at the top of the page.'),
                        toggleField('HIDE_ADS', 'Hide Ads', false, 'Hide ad containers on the page.'),
                        toggleField('HIDE_FOOTER', 'Hide Footer', false, 'Hide the page footer.'),
                        toggleField('HIDE_ALT_LOGINS', 'Hide Alternative Login Options', false, 'Hide third-party login options on the login and race pages.'),
                        toggleField('HIDE_CASH_DISPLAY', 'Hide Cash Display', false, 'Hide your Nitro Cash balance across all pages.'),
                        selectField('HIDE_CASH_MODE', 'Cash Display Mode', 'hidden', [
                            { value: 'hidden', label: 'Hidden (blank space)' },
                            { value: 'redacted', label: 'REDACTED' },
                            { value: 'stars', label: '*******' },
                            { value: 'custom', label: 'Custom Message' }
                        ], 'How to hide the cash amount.'),
                        toggleField('HIDE_CASH_TOTAL_SPENT', 'Hide Total Spent', false, 'Also hide the Total Spent value on the stats page.')
                    ]
                },
                {
                    id: 'notifications',
                    title: 'Notifications',
                    subtitle: 'Control which navigation bar notification badges are visible.',
                    items: [
                        toggleField('HIDE_NOTIFY_ALL', 'Disable All Notifications', false, 'Hide all notification badges on the navigation bar.'),
                        toggleField('HIDE_NOTIFY_SHOP', 'Hide Shop Notifications', false, 'Hide the notification badge on the Shop nav item.'),
                        toggleField('SMART_SHOP_NOTIFY', 'Smart Shop Notifications', false, 'Only show the Shop notification when there are unowned items in the current daily rotation.'),
                        toggleField('HIDE_NOTIFY_FRIENDS', 'Hide Friends Notifications', false, 'Hide the notification badge on the Friends nav item.'),
                        toggleField('HIDE_NOTIFY_TEAM', 'Hide Team Notifications', false, 'Hide the notification badge on the Team nav item.'),
                        toggleField('HIDE_NOTIFY_NEWS', 'Hide News Notifications', false, 'Hide the notification badge on the News nav item.'),
                        toggleField('HIDE_NOTIFY_ACHIEVEMENTS', 'Hide Achievements Notifications', false, 'Hide the notification badge on the Achievements nav item.')
                    ]
                },
                {
                    id: 'shortcuts',
                    title: 'Keyboard Shortcuts',
                    subtitle: 'Quick navigation with customizable shortcuts.',
                    items: [
                        toggleField('ENABLE_KEYBOARD_SHORTCUTS', 'Keyboard Shortcuts', true, 'Quick page navigation with modifier+key shortcuts.'),
                        noteField('Keyboard shortcuts do not work on the race page due to Nitro Type\'s keyboard lockdown. They work on all other pages.', 'info'),
                        noteField('Install the Toolkit script to customize shortcut key mappings.', 'info')
                    ]
                }
            ]
        },
        {
            id: 'enhanced-stats',
            label: 'Stats',
            shortLabel: 'ES',
            accent: '#f3a81b',
            description: 'Race and racer data analytics: hidden stats, WPM curves, enhanced racelog, league calculators, and more.',
            installUrl: 'https://greasyfork.org/en/scripts/570658-nitro-type-enhanced-stats',
            previewSections: [
                {
                    id: 'racer',
                    title: 'Racer Profile',
                    subtitle: 'Session tracking, hidden stats, and garage info.',
                    items: [
                        toggleField('ENABLE_SESSION_COUNTER', 'Session Race Counter', false, 'Show a persistent race counter next to your profile dropdown.'),
                        numberField('SESSION_INACTIVITY_MINUTES', 'Inactivity Reset (minutes)', 30, 'Auto-reset session counter after this many minutes of inactivity.', 5, 120, '5'),
                        toggleField('ENABLE_HIDDEN_STATS', 'Hidden Racer Stats', true, 'Show hidden stats (Nitros Used, Cars Owned, Longest Session) on racer profiles.'),
                        toggleField('ENABLE_GARAGE_CAR_COUNT', 'Garage Car Count', true, 'Show total car count next to the "Cars" heading on the garage page.')
                    ]
                },
                {
                    id: 'race',
                    title: 'Race',
                    subtitle: 'In-race analytics and post-race breakdowns.',
                    items: [
                        toggleField('ENABLE_RACE_ENHANCEMENTS', 'Race Result Enhancements', true, 'Show season points and skipped characters on the post-race scoreboard.'),
                        toggleField('ENABLE_WPM_CURVE', 'Post-Race Graph', true, 'Show a Graph button on race results to view WPM, accuracy, and nitro usage over time.')
                    ]
                },
                {
                    id: 'stats',
                    title: 'Stats Page',
                    subtitle: 'Enhanced overview, racelog, and hidden account data.',
                    items: [
                        toggleField('ENABLE_ENHANCED_STATS_PAGE', 'Enhanced Stats Overview', true, 'Inject additional statistics from your account data into the stats page overview boxes and summary table.'),
                        toggleField('ENABLE_ENHANCED_RACELOG', 'Enhanced Racelog', true, 'Add extra columns, corrected stats, and session time estimates to the racelog.')
                    ]
                },
                {
                    id: 'leagues',
                    title: 'Leagues',
                    subtitle: 'League calculators and tournament stats.',
                    items: [
                        toggleField('ENABLE_LEAGUE_CALCULATOR', 'XP-Races Calculator', true, 'Show how many races needed to beat each league player.'),
                        toggleField('ENABLE_TOURNAMENT_WINS', 'Tournament Wins', true, 'Show personal and team tournament win counts on the leagues page.')
                    ]
                }
            ]
        }
    ];

    const MODULE_META = Object.create(null);
    MODULE_LIBRARY.forEach((module) => {
        MODULE_META[module.id] = module;
    });

    const MODULE_SECTION_OVERRIDES = {
        'race-options': {
            'perfect-nitros': {
                preview: { type: 'perfect-nitro' }
            },
            'theme': {
                layout: 'compact',
                preview: { type: 'theme' }
            }
        }
    };

    function applyModuleSectionOverrides(moduleId, sections) {
        const sectionOverrides = MODULE_SECTION_OVERRIDES[moduleId];
        if (!sectionOverrides || !Array.isArray(sections) || sections.length === 0) return sections;
        return sections.map((section) => {
            const override = sectionOverrides[section.id];
            if (!override) return section;
            return Object.assign({}, section, override);
        });
    }

    const RACE_OPTIONS_THEME_FONT_FAMILY_DEFAULT_CSS = '"Roboto Mono", "Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace';
    const RACE_OPTIONS_THEME_FONT_WEIGHT_DEFAULT = 400;
    const RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND = '#E7EEF8';
    const RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND_ACTIVE = '#101623';
    const RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND_TYPED = '#8FA3BA';
    const RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND = '#0A121E';
    const RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND_ACTIVE = '#1C99F4';
    const RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND_INCORRECT = '#D62F3A';
    const RACE_OPTIONS_THEME_FONT_FAMILY_PRESETS = [
        { value: '__default__', label: 'Default', css: RACE_OPTIONS_THEME_FONT_FAMILY_DEFAULT_CSS },
        { value: 'roboto_mono', label: 'Roboto Mono', css: '"Roboto Mono", "Courier New", Courier, monospace' },
        { value: 'montserrat', label: 'Montserrat', css: '"Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif' },
        { value: 'poppins', label: 'Poppins', css: '"Poppins", "Segoe UI", Tahoma, sans-serif' },
        { value: 'nunito', label: 'Nunito', css: '"Nunito", "Trebuchet MS", sans-serif' },
        { value: 'oswald', label: 'Oswald', css: '"Oswald", "Arial Narrow", sans-serif' },
        { value: 'space_mono', label: 'Space Mono', css: '"Space Mono", "Courier New", monospace' }
    ];
    const RACE_OPTIONS_THEME_FONT_SIZE_PRESETS = [
        { value: '__default__', label: 'Default', px: 18 },
        { value: '8', label: '8px', px: 8 },
        { value: '10', label: '10px', px: 10 },
        { value: '12', label: '12px', px: 12 },
        { value: '14', label: '14px', px: 14 },
        { value: '16', label: '16px', px: 16 },
        { value: '18', label: '18px', px: 18 },
        { value: '20', label: '20px', px: 20 },
        { value: '24', label: '24px', px: 24 },
        { value: '28', label: '28px', px: 28 },
        { value: '32', label: '32px', px: 32 },
        { value: '36', label: '36px', px: 36 }
    ];
    // Section defaults are now computed from item metadata by mountGenericSection.

    function normalizePath(pathname) {
        if (!pathname || pathname === '/') return '/';
        return pathname.replace(/\/+$/, '') || '/';
    }

    function isModMenuRoute(pathname = window.location.pathname) {
        return normalizePath(pathname) === MOD_MENU_PATH;
    }

    function escapeHtml(value) {
        return String(value ?? '')
            .replace(/&/g, '&')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    function titleCaseKey(value) {
        return String(value || '')
            .replace(/[_-]+/g, ' ')
            .replace(/\s+/g, ' ')
            .trim()
            .replace(/\b\w/g, (m) => m.toUpperCase());
    }

    function slugify(value) {
        return String(value || '')
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, '-')
            .replace(/^-+|-+$/g, '') || 'section';
    }

    function readJson(key, fallback = null) {
        try {
            const raw = localStorage.getItem(key);
            if (raw == null) return fallback;
            return JSON.parse(raw);
        } catch {
            return fallback;
        }
    }

    function readCanonicalValue(key) {
        if (typeof GM_getValue !== 'function') return undefined;
        try {
            return GM_getValue(key);
        } catch {
            return undefined;
        }
    }

    function writeCanonicalValue(key, value) {
        if (typeof GM_setValue !== 'function') return;
        try {
            GM_setValue(key, value);
        } catch {
            // ignore storage errors
        }
    }

    function deleteCanonicalValue(key) {
        if (typeof GM_deleteValue !== 'function') return;
        try {
            GM_deleteValue(key);
        } catch {
            // ignore storage errors
        }
    }

    function readPersistentJson(key, fallback = null) {
        const canonical = readCanonicalValue(key);
        if (canonical !== undefined) return canonical;
        const legacy = readJson(key, fallback);
        if (legacy !== null && legacy !== undefined) {
            writeCanonicalValue(key, legacy);
        }
        return legacy;
    }

    function writePersistentJson(key, value) {
        writeCanonicalValue(key, value);
        writeJson(key, value);
    }

    function removePersistentJson(key) {
        deleteCanonicalValue(key);
        try {
            localStorage.removeItem(key);
        } catch {
            // ignore storage errors
        }
    }

    function writeJson(key, value) {
        try {
            localStorage.setItem(key, JSON.stringify(value));
        } catch {
            // ignore storage errors
        }
    }

    // Returns: 'alive' | 'outdated' | 'stale'
    // Simple 2-outcome status: 'alive' or 'outdated'.
    // Alive = alive key was written during THIS page session (timestamp >= PAGE_SESSION_START).
    // Outdated = no key, old key from previous session, or unparseable.
    // This avoids race conditions — no need to clear keys on load.
    function getScriptStatus(moduleId) {
        try {
            const raw = localStorage.getItem(ALIVE_PREFIX + moduleId);
            if (raw == null) return 'outdated';
            const ts = parseInt(raw, 10);
            if (isNaN(ts)) return 'outdated';
            // Key must be from this page session (with 5s tolerance for scripts
            // that may have initialized slightly before the mod menu)
            return ts >= (PAGE_SESSION_START - 5000) ? 'alive' : 'outdated';
        } catch {
            return 'outdated';
        }
    }

    function extractGreasyforkId(installUrl) {
        if (!installUrl) return null;
        const match = installUrl.match(/greasyfork\.org\/[^/]*\/scripts\/(\d+)/);
        return match ? match[1] : null;
    }

    function readVersionCache() {
        return readJson(VERSION_CACHE_KEY, {});
    }

    function writeVersionCache(cache) {
        writeJson(VERSION_CACHE_KEY, cache);
    }

    function getCachedLatestVersion(moduleId) {
        const cache = readVersionCache();
        const entry = cache[moduleId];
        if (!entry) return null;
        if (Date.now() - (entry.ts || 0) > VERSION_CACHE_TTL_MS) return null;
        return entry.version || null;
    }

    function setCachedLatestVersion(moduleId, version) {
        const cache = readVersionCache();
        cache[moduleId] = { version, ts: Date.now() };
        writeVersionCache(cache);
    }

    function fetchLatestVersion(moduleId, installUrl, forceRefresh) {
        if (!forceRefresh) {
            const cached = getCachedLatestVersion(moduleId);
            if (cached) return Promise.resolve(cached);
        }
        const gfId = extractGreasyforkId(installUrl);
        if (!gfId) return Promise.resolve(null);
        if (typeof GM_xmlhttpRequest === 'undefined') return Promise.resolve(null);
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://greasyfork.org/en/scripts/${gfId}.json`,
                responseType: 'json',
                timeout: 8000,
                onload(response) {
                    try {
                        const data = typeof response.response === 'string'
                            ? JSON.parse(response.response)
                            : response.response;
                        const version = data?.version || null;
                        if (version) setCachedLatestVersion(moduleId, version);
                        resolve(version);
                    } catch {
                        resolve(null);
                    }
                },
                onerror() { resolve(null); },
                ontimeout() { resolve(null); }
            });
        });
    }

    function getInstalledModulesForVersionCheck() {
        const modulesToCheck = [];
        Object.keys(localStorage).forEach((key) => {
            if (!key.startsWith(MANIFEST_PREFIX)) return;
            const manifest = readJson(key, null);
            if (!manifest?.id) return;
            const meta = MODULE_META[manifest.id];
            if (!meta?.installUrl) return;
            modulesToCheck.push({ id: manifest.id, installUrl: meta.installUrl });
        });
        return modulesToCheck;
    }

    async function refreshInstalledModuleVersions({ forceRefresh = false, stampGlobalCheck = false } = {}) {
        const modulesToCheck = getInstalledModulesForVersionCheck();
        if (modulesToCheck.length === 0) {
            updateDropdownDot();
            return 0;
        }
        if (stampGlobalCheck) {
            try {
                localStorage.setItem('ntmods:version-check-ts', String(Date.now()));
            } catch {
                // ignore storage errors
            }
        }
        for (const mod of modulesToCheck) {
            await fetchLatestVersion(mod.id, mod.installUrl, forceRefresh);
        }
        updateDropdownDot();
        return modulesToCheck.length;
    }

    function isVersionOutdated(installed, latest) {
        if (!installed || !latest) return false;
        const parse = (v) => String(v).split('.').map(Number);
        const a = parse(installed);
        const b = parse(latest);
        const len = Math.max(a.length, b.length);
        for (let i = 0; i < len; i++) {
            const x = a[i] || 0;
            const y = b[i] || 0;
            if (x < y) return true;
            if (x > y) return false;
        }
        return false;
    }

    function getPreviewStorageKey(moduleId, fieldKey) {
        return `${PREVIEW_VALUE_PREFIX}${moduleId}:${fieldKey}`;
    }

    function getManifestStorageKey(moduleId, fieldKey) {
        return `ntcfg:${moduleId}:${fieldKey}`;
    }

    // ── Field value cache ──
    // Batch-reads all field values for a module in one pass over localStorage keys,
    // so individual getFieldValue() calls are served from memory instead of N storage reads.
    let _fieldCacheModuleId = '';
    const _fieldValueCache = new Map();

    function preloadFieldValues(moduleId, source) {
        if (_fieldCacheModuleId === moduleId && _fieldValueCache.size > 0) return;
        _fieldValueCache.clear();
        _fieldCacheModuleId = moduleId;
        const manifestPrefix = `ntcfg:${moduleId}:`;
        const previewPrefix = `${PREVIEW_VALUE_PREFIX}${moduleId}:`;
        try {
            const keys = Object.keys(localStorage);
            for (let i = 0; i < keys.length; i++) {
                const key = keys[i];
                if (key.startsWith(manifestPrefix) || key.startsWith(previewPrefix)) {
                    try {
                        _fieldValueCache.set(key, JSON.parse(localStorage.getItem(key)));
                    } catch { /* skip malformed */ }
                }
            }
        } catch { /* ignore */ }
    }

    function invalidateFieldCache() {
        _fieldCacheModuleId = '';
        _fieldValueCache.clear();
    }

    function emitManifestChange(moduleId, fieldKey, value) {
        try {
            document.dispatchEvent(new CustomEvent('ntcfg:change', {
                detail: {
                    script: moduleId,
                    key: fieldKey,
                    value
                }
            }));
        } catch {
            // ignore custom event failures
        }
    }

    function convertManifestSetting(fieldKey, meta) {
        const inputType = String(meta?.type || 'text').toLowerCase();
        const common = {
            key: fieldKey,
            label: meta?.label || titleCaseKey(fieldKey),
            default: meta?.default,
            help: meta?.description || meta?.help || ''
        };

        // Pass through extended metadata when present
        if (meta?.visibleWhen) common.visibleWhen = meta.visibleWhen;
        if (meta?.disabledWhen) common.disabledWhen = meta.disabledWhen;
        if (meta?.compound) common.compound = meta.compound;
        if (meta?.warn) common.warn = meta.warn;
        if (meta?.presets) common.presets = meta.presets;
        if (meta?.normalize) common.normalize = meta.normalize;
        if (meta?.layout) common.layout = meta.layout;
        if (meta?.section) common.section = meta.section;

        if (inputType === 'note') {
            return { type: 'note', message: meta?.message || meta?.description || '', tone: meta?.tone || 'info' };
        }
        if (inputType === 'boolean') {
            return Object.assign({}, common, { type: 'toggle', default: Boolean(meta?.default) });
        }
        if (inputType === 'color') {
            return Object.assign({}, common, { type: 'color', default: meta?.default || '#1C99F4' });
        }
        if (inputType === 'number' || inputType === 'range') {
            return Object.assign({}, common, {
                type: 'number',
                min: meta?.min ?? null,
                max: meta?.max ?? null,
                step: meta?.step ?? '1'
            });
        }
        if (inputType === 'select') {
            return Object.assign({}, common, {
                type: 'select',
                options: Array.isArray(meta?.options) ? meta.options.map((option) => ({
                    value: option?.value,
                    label: option?.label ?? String(option?.value ?? '')
                })) : []
            });
        }
        if (inputType === 'keymap') {
            return Object.assign({}, common, {
                type: 'keymap',
                default: Array.isArray(meta?.default) ? meta.default : []
            });
        }
        if (inputType === 'action') {
            return Object.assign({}, common, {
                type: 'action',
                style: meta?.style || 'primary'
            });
        }
        return Object.assign({}, common, {
            type: 'text',
            placeholder: meta?.placeholder || ''
        });
    }

    function moduleFromManifest(manifest) {
        const base = MODULE_META[manifest.id] || {
            id: manifest.id,
            label: manifest.name || titleCaseKey(manifest.id),
            shortLabel: titleCaseKey(manifest.id).slice(0, 2).toUpperCase(),
            accent: '#1c99f4',
            description: manifest.description || `${manifest.name || titleCaseKey(manifest.id)} settings.`
        };

        const grouped = new Map();
        const settings = manifest?.settings && typeof manifest.settings === 'object' ? manifest.settings : {};

        // Collect compound child keys so they are not rendered as standalone items
        const compoundChildKeys = new Set();
        Object.entries(settings).forEach(([, meta]) => {
            if (Array.isArray(meta?.compound)) {
                meta.compound.forEach((child) => {
                    if (child?.key) compoundChildKeys.add(child.key);
                });
            }
        });

        Object.entries(settings).forEach(([fieldKey, meta]) => {
            if (compoundChildKeys.has(fieldKey)) return; // skip: rendered inline by its parent
            const groupName = meta?.group || 'General';
            if (!grouped.has(groupName)) grouped.set(groupName, []);
            grouped.get(groupName).push(convertManifestSetting(fieldKey, meta));
        });

        // Build a lookup from manifest-provided section metadata (subtitle, ordering)
        const sectionMeta = new Map();
        if (Array.isArray(manifest.sections)) {
            manifest.sections.forEach((sec, i) => sectionMeta.set(sec.id || slugify(sec.title || ''), { index: i, ...sec }));
        }

        const sections = Array.from(grouped.entries()).map(([groupName, items]) => {
            const sectionId = slugify(groupName);
            const meta = sectionMeta.get(sectionId);
            const sec = {
                id: sectionId,
                title: meta?.title || groupName,
                subtitle: meta?.subtitle || `${items.length} synced ${items.length === 1 ? 'setting' : 'settings'} from ${manifest.name || base.label}.`,
                items
            };
            // Pass through section-level metadata
            if (meta?.layout) sec.layout = meta.layout;
            if (meta?.preview) sec.preview = meta.preview;
            if (meta?.resetButton != null) sec.resetButton = meta.resetButton;
            return sec;
        });

        // Sort sections by manifest-defined order if available
        if (sectionMeta.size > 0) {
            sections.sort((a, b) => {
                const ai = sectionMeta.has(a.id) ? sectionMeta.get(a.id).index : Number.MAX_SAFE_INTEGER;
                const bi = sectionMeta.has(b.id) ? sectionMeta.get(b.id).index : Number.MAX_SAFE_INTEGER;
                return ai - bi;
            });
        }

        if (!sections.length) {
            sections.push({
                id: 'general',
                title: 'General',
                subtitle: `${manifest.name || base.label} is registered, but no grouped settings were provided yet.`,
                items: [
                    noteField('This manifest was detected, but it did not expose any structured settings yet.', 'warning')
                ]
            });
        }

        const normalizedSections = applyModuleSectionOverrides(manifest.id, sections);

        const result = Object.assign({}, base, {
            source: 'manifest',
            manifestVersion: manifest.scriptVersion || '',
            sections: normalizedSections
        });

        // Allow script authors to set a custom tab icon via manifest.icon (SVG inner content)
        if (manifest.icon) result.icon = manifest.icon;

        return result;
    }

    function readManifestModules() {
        const modules = [];
        try {
            Object.keys(localStorage).forEach((key) => {
                if (!key.startsWith(MANIFEST_PREFIX)) return;
                const manifest = readJson(key, null);
                if (!manifest || !manifest.id) return;
                const mod = moduleFromManifest(manifest);
                // Always check real heartbeat status — tabs start dimmed and light up as heartbeats arrive
                mod.status = getScriptStatus(manifest.id);
                modules.push(mod);
            });
        } catch {
            return [];
        }

        const orderIndex = new Map(MODULE_LIBRARY.map((module, index) => [module.id, index]));
        modules.sort((a, b) => {
            const left = orderIndex.has(a.id) ? orderIndex.get(a.id) : Number.MAX_SAFE_INTEGER;
            const right = orderIndex.has(b.id) ? orderIndex.get(b.id) : Number.MAX_SAFE_INTEGER;
            if (left !== right) return left - right;
            return a.label.localeCompare(b.label);
        });

        return modules;
    }

    function buildPreviewModule(module) {
        return {
            id: module.id,
            label: module.label,
            shortLabel: module.shortLabel,
            accent: module.accent,
            description: module.description,
            source: 'preview',
            sections: module.previewSections
        };
    }

    function buildVisibleModules() {
        const manifestModules = readManifestModules();
        if (!PREVIEW_FALLBACK_ENABLED) return applyCustomTabOrder(manifestModules);

        const manifestById = new Map(manifestModules.map((module) => [module.id, module]));
        const modules = [];

        MODULE_LIBRARY.forEach((module) => {
            const manifest = manifestById.get(module.id);
            if (manifest && (manifest.status === 'alive' || module.hiddenUnlessInstalled)) {
                // Script is alive, or exempt from heartbeat (e.g. manually distributed scripts)
                modules.push(manifest);
            } else if (manifest) {
                // Manifest exists but no fresh heartbeat — needs update or is disabled
                const outdated = buildPreviewModule(module);
                outdated.source = 'outdated';
                outdated.manifestVersion = manifest.manifestVersion || '';
                modules.push(outdated);
            } else if (!module.hiddenUnlessInstalled) {
                modules.push(buildPreviewModule(module));
            }
        });

        manifestModules.forEach((module) => {
            if (!MODULE_META[module.id]) {
                if (module.status === 'alive') {
                    modules.push(module);
                }
                // Unknown stale/outdated scripts are silently dropped
            }
        });

        return applyCustomTabOrder(modules);
    }

    function applyCustomTabOrder(modules) {
        try {
            const savedOrder = readPersistentJson(TAB_ORDER_KEY, null);
            if (Array.isArray(savedOrder) && savedOrder.length) {
                const byId = new Map(modules.map((m) => [m.id, m]));
                const ordered = [];
                savedOrder.forEach((id) => {
                    const m = byId.get(id);
                    if (m) {
                        ordered.push(m);
                        byId.delete(id);
                    }
                });
                byId.forEach((m) => ordered.push(m));
                return ordered;
            }
        } catch { /* ignore */ }
        return modules;
    }

    function getRenderableSections(module) {
        return Array.isArray(module?.sections) ? module.sections : [];
    }

    function readUiState() {
        return readPersistentJson(UI_STATE_KEY, {}) || {};
    }

    function writeUiState(nextState) {
        writePersistentJson(UI_STATE_KEY, nextState);
    }

    function parseHashSelection() {
        const hash = String(window.location.hash || '').replace(/^#/, '').trim();
        if (!hash) return null;
        const [moduleId, sectionId] = hash.split('/');
        if (!moduleId) return null;
        return { moduleId, sectionId: sectionId || '' };
    }

    function getInitialSelection(modules) {
        const stored = readUiState();
        const fromHash = parseHashSelection();
        const candidates = [fromHash, stored].filter(Boolean);

        for (const candidate of candidates) {
            const module = modules.find((entry) => entry.id === candidate.moduleId);
            if (!module) continue;
            const moduleSections = getRenderableSections(module);
            const section = moduleSections.find((entry) => entry.id === candidate.sectionId) || moduleSections[0];
            return {
                activeModuleId: module.id,
                activeSectionId: section ? section.id : ''
            };
        }

        const firstModule = modules[0] || null;
        const firstSections = getRenderableSections(firstModule);
        return {
            activeModuleId: firstModule ? firstModule.id : '',
            activeSectionId: firstSections[0] ? firstSections[0].id : ''
        };
    }

    function getFieldValue(module, item) {
        if (!item || !item.key) return item?.default ?? '';
        const storageKey = item.storageKey
            || (module.source === 'manifest'
                ? getManifestStorageKey(module.id, item.key)
                : getPreviewStorageKey(module.id, item.key));

        // If the batch cache is loaded for this module, trust it completely.
        // Keys not in the cache don't exist in localStorage — return the default
        // without hitting storage. This avoids N individual reads for uncustomized fields.
        if (_fieldCacheModuleId === module.id) {
            return _fieldValueCache.has(storageKey) ? _fieldValueCache.get(storageKey) : item.default;
        }

        try {
            const raw = localStorage.getItem(storageKey);
            if (raw == null) return item.default;
            return JSON.parse(raw);
        } catch {
            return item.default;
        }
    }

    function setFieldValue(module, item, value) {
        if (!item || !item.key) return;
        const storageKey = item.storageKey
            || (module.source === 'manifest'
                ? getManifestStorageKey(module.id, item.key)
                : getPreviewStorageKey(module.id, item.key));

        try {
            localStorage.setItem(storageKey, JSON.stringify(value));
            // Keep field cache in sync
            if (_fieldCacheModuleId === module.id) _fieldValueCache.set(storageKey, value);
        } catch {
            // ignore storage errors
        }

        if (module.source === 'manifest') {
            emitManifestChange(module.id, item.key, value);
        }
        // Linked fields: emit change event for the target module so the badge script picks it up.
        if (item.emitModule && item.emitKey) {
            emitManifestChange(item.emitModule, item.emitKey, value);
        }
    }

    function coerceInputValue(element, item) {
        if (!element || !item) return null;
        if (item.type === 'toggle') return !!element.checked;
        if (item.type === 'number') {
            const parsed = Number(element.value);
            return Number.isFinite(parsed) ? parsed : (item.default ?? 0);
        }
        return String(element.value ?? '');
    }

    function createElement(tagName, className = '', textContent = null) {
        const element = document.createElement(tagName);
        if (className) element.className = className;
        if (textContent != null) element.textContent = textContent;
        return element;
    }

    function getModuleFieldMap(module) {
        const fieldMap = new Map();
        (module?.sections || []).forEach((section) => {
            (section?.items || []).forEach((item) => {
                if (item?.key) fieldMap.set(item.key, item);
            });
        });
        return fieldMap;
    }

    function resolveModuleFieldItem(fieldMap, key, fallback = {}) {
        return fieldMap.get(key) || Object.assign({
            key,
            label: titleCaseKey(key),
            default: '',
            type: 'text',
            help: ''
        }, fallback);
    }

    function readModuleFieldValue(module, fieldMap, key, fallback = {}) {
        return getFieldValue(module, resolveModuleFieldItem(fieldMap, key, fallback));
    }

    function writeModuleFieldValue(module, fieldMap, key, value, fallback = {}) {
        setFieldValue(module, resolveModuleFieldItem(fieldMap, key, fallback), value);
    }

    function normalizeRaceOptionsHexColorValue(value, fallback = '#FFFFFF') {
        const normalizedFallback = /^#[0-9A-Fa-f]{6}$/.test(String(fallback || '').trim())
            ? String(fallback).toUpperCase()
            : '#FFFFFF';
        const raw = String(value || '').trim();
        if (/^#[0-9A-Fa-f]{6}$/.test(raw)) return raw.toUpperCase();
        if (/^#[0-9A-Fa-f]{3}$/.test(raw)) {
            return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`.toUpperCase();
        }
        return normalizedFallback;
    }

    function hexToRgba(hex, alpha = 1) {
        const h = normalizeRaceOptionsHexColorValue(hex, '#FFFFFF');
        const r = parseInt(h.slice(1, 3), 16);
        const g = parseInt(h.slice(3, 5), 16);
        const b = parseInt(h.slice(5, 7), 16);
        return `rgba(${r}, ${g}, ${b}, ${Math.min(1, Math.max(0, alpha))})`;
    }

    function hexToRgbChannels(hex) {
        const h = normalizeRaceOptionsHexColorValue(hex, '#FFFFFF');
        return [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];
    }

    function blendedLuminance(fgHex, bgHex, alpha) {
        const [fr, fg, fb] = hexToRgbChannels(fgHex);
        const [br, bg, bb] = hexToRgbChannels(bgHex);
        const a = Math.min(1, Math.max(0, alpha));
        const r = (fr * a + br * (1 - a)) / 255;
        const g = (fg * a + bg * (1 - a)) / 255;
        const b = (fb * a + bb * (1 - a)) / 255;
        const linear = (ch) => (ch <= 0.03928 ? ch / 12.92 : Math.pow((ch + 0.055) / 1.055, 2.4));
        return (0.2126 * linear(r)) + (0.7152 * linear(g)) + (0.0722 * linear(b));
    }

    function normalizeRaceOptionsHighlightOpacity(value, fallback = 0.5) {
        const normalizedFallback = Number.isFinite(Number(fallback)) ? Number(fallback) : 0.5;
        const parsed = Number(value);
        if (!Number.isFinite(parsed)) return normalizedFallback;
        return Math.min(1, Math.max(0.05, Math.round(parsed * 100) / 100));
    }

    function normalizeRaceOptionsRainbowSpeedSeconds(value, fallback = 10) {
        const normalizedFallback = Number.isFinite(Number(fallback)) ? Number(fallback) : 10;
        const parsed = Number(value);
        if (!Number.isFinite(parsed)) return normalizedFallback;
        return Math.min(60, Math.max(1, parsed));
    }

    function normalizeRaceOptionsThemeFontSizePx(value, fallback = 18) {
        const normalizedFallback = Number.isFinite(Number(fallback)) ? Number(fallback) : 18;
        const parsed = Number(value);
        if (!Number.isFinite(parsed)) return normalizedFallback;
        return Math.min(72, Math.max(8, Math.round(parsed)));
    }

    function normalizeRaceOptionsThemeFontFamilyValue(value, fallback = RACE_OPTIONS_THEME_FONT_FAMILY_DEFAULT_CSS) {
        const raw = String(value ?? '').trim();
        if (!raw) return String(fallback);
        const cleaned = raw.replace(/[{}<>;\r\n]/g, '').trim().slice(0, 180);
        return cleaned || String(fallback);
    }

    function normalizeRaceOptionsThemePresetValue(value, presets, fallback = '__default__') {
        const raw = String(value ?? '').trim();
        if (Array.isArray(presets) && presets.some((preset) => preset?.value === raw)) {
            return raw;
        }
        return fallback;
    }

    function normalizeRaceOptionsDarkModeSyncSelectionValue(value, fallback = 'always') {
        if (value === true) return 'system';
        if (value === false) return 'always';
        const raw = String(value ?? '').trim().toLowerCase();
        if (raw === 'system') return 'system';
        if (raw === 'always') return 'always';
        return fallback;
    }

    function normalizeRaceOptionsDarkModeSyncStoredValue(value) {
        return normalizeRaceOptionsDarkModeSyncSelectionValue(value) === 'system';
    }

    function getRaceOptionsThemeOptions(module, fieldMap) {
        const fontFamilyPreset = normalizeRaceOptionsThemePresetValue(
            readModuleFieldValue(module, fieldMap, 'THEME_FONT_FAMILY_PRESET', { default: '__default__' }),
            RACE_OPTIONS_THEME_FONT_FAMILY_PRESETS
        );
        const fontSizePreset = normalizeRaceOptionsThemePresetValue(
            readModuleFieldValue(module, fieldMap, 'THEME_FONT_SIZE_PRESET', { default: '__default__' }),
            RACE_OPTIONS_THEME_FONT_SIZE_PRESETS
        );
        const singleLineFontSizePreset = normalizeRaceOptionsThemePresetValue(
            readModuleFieldValue(module, fieldMap, 'THEME_SINGLE_LINE_FONT_SIZE_PRESET', { default: '__default__' }),
            RACE_OPTIONS_THEME_FONT_SIZE_PRESETS
        );
        const fontFamilyPresetOption = RACE_OPTIONS_THEME_FONT_FAMILY_PRESETS.find((preset) => preset.value === fontFamilyPreset) || RACE_OPTIONS_THEME_FONT_FAMILY_PRESETS[0];
        const fontSizePresetOption = RACE_OPTIONS_THEME_FONT_SIZE_PRESETS.find((preset) => preset.value === fontSizePreset) || RACE_OPTIONS_THEME_FONT_SIZE_PRESETS[0];
        const singleLineFontSizePresetOption = RACE_OPTIONS_THEME_FONT_SIZE_PRESETS.find((preset) => preset.value === singleLineFontSizePreset) || RACE_OPTIONS_THEME_FONT_SIZE_PRESETS[0];
        const darkModeEnabled = !!readModuleFieldValue(module, fieldMap, 'THEME_ENABLE_DARK_MODE', { default: false, type: 'toggle' });
        const darkModeSyncSystem = normalizeRaceOptionsDarkModeSyncStoredValue(
            readModuleFieldValue(module, fieldMap, 'THEME_DARK_MODE_SYNC_SYSTEM', { default: false, type: 'toggle' })
        );
        const darkModeEffective = darkModeSyncSystem
            ? !!(typeof window.matchMedia === 'function' && window.matchMedia('(prefers-color-scheme: dark)').matches)
            : darkModeEnabled;

        return {
            darkModeEnabled,
            darkModeSyncSystem,
            darkModeEffective,
            foreground: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_FOREGROUND', { default: '#FFFFFF' }), '#FFFFFF'),
            foregroundActive: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_FOREGROUND_ACTIVE', { default: '#000000' }), '#000000'),
            foregroundTyped: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_FOREGROUND_TYPED', { default: '#5B5B5B' }), '#5B5B5B'),
            background: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_BACKGROUND', { default: '#000000' }), '#000000'),
            backgroundActive: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_BACKGROUND_ACTIVE', { default: '#FFFFFF' }), '#FFFFFF'),
            backgroundIncorrect: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'THEME_COLOR_BACKGROUND_INCORRECT', { default: '#FF0000' }), '#FF0000'),
            overrideForeground: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_FOREGROUND', { default: false, type: 'toggle' }),
            overrideForegroundActive: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_FOREGROUND_ACTIVE', { default: false, type: 'toggle' }),
            overrideForegroundTyped: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_FOREGROUND_TYPED', { default: false, type: 'toggle' }),
            hideTypedText: !!readModuleFieldValue(module, fieldMap, 'THEME_HIDE_TYPED_TEXT', { default: false, type: 'toggle' }),
            overrideBackground: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_BACKGROUND', { default: false, type: 'toggle' }),
            overrideBackgroundActive: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_BACKGROUND_ACTIVE', { default: false, type: 'toggle' }),
            overrideBackgroundIncorrect: !!readModuleFieldValue(module, fieldMap, 'THEME_OVERRIDE_BACKGROUND_INCORRECT', { default: false, type: 'toggle' }),
            fontFamilyPreset,
            fontFamilyCss: fontFamilyPreset !== '__default__' && typeof fontFamilyPresetOption?.css === 'string'
                ? normalizeRaceOptionsThemeFontFamilyValue(fontFamilyPresetOption.css, RACE_OPTIONS_THEME_FONT_FAMILY_DEFAULT_CSS)
                : null,
            fontSizePreset,
            fontSizePx: fontSizePreset !== '__default__' && Number.isFinite(Number(fontSizePresetOption?.px))
                ? normalizeRaceOptionsThemeFontSizePx(fontSizePresetOption.px, 18)
                : null,
            singleLineFontSizePreset,
            singleLineFontSizePx: singleLineFontSizePreset !== '__default__' && Number.isFinite(Number(singleLineFontSizePresetOption?.px))
                ? normalizeRaceOptionsThemeFontSizePx(singleLineFontSizePresetOption.px, 18)
                : null,
            fontBold: !!readModuleFieldValue(module, fieldMap, 'THEME_FONT_BOLD', { default: false, type: 'toggle' }),
            fontItalic: !!readModuleFieldValue(module, fieldMap, 'THEME_FONT_ITALIC', { default: false, type: 'toggle' }),
            fontWeight: !!readModuleFieldValue(module, fieldMap, 'THEME_FONT_BOLD', { default: false, type: 'toggle' })
                ? Math.min(900, Math.max(100, Math.round(Number(readModuleFieldValue(module, fieldMap, 'THEME_FONT_WEIGHT', { default: 700, type: 'select' })) || 700)))
                : RACE_OPTIONS_THEME_FONT_WEIGHT_DEFAULT,
            enableRainbowTypedText: !!readModuleFieldValue(module, fieldMap, 'THEME_ENABLE_RAINBOW_TYPED_TEXT', { default: false, type: 'toggle' }),
            rainbowTypedTextSpeedSeconds: normalizeRaceOptionsRainbowSpeedSeconds(
                readModuleFieldValue(module, fieldMap, 'THEME_RAINBOW_TYPED_TEXT_SPEED_SECONDS', { default: 10, type: 'number' }),
                10
            )
        };
    }

    function getEffectiveRaceOptionsThemeBackground(module, fieldMap) {
        const themeOptions = getRaceOptionsThemeOptions(module, fieldMap);
        if (themeOptions.overrideBackground) return normalizeRaceOptionsHexColorValue(themeOptions.background, '#FFFFFF');
        if (themeOptions.darkModeEffective) return RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND;
        return '#E9EAEB';
    }

    function getPerfectNitroTextStyle(module, fieldMap, highlightColor, opacity = 0.5) {
        const bgHex = getEffectiveRaceOptionsThemeBackground(module, fieldMap);
        const fgHex = normalizeRaceOptionsHexColorValue(highlightColor, '#FFFFFF');
        const luminance = blendedLuminance(fgHex, bgHex, opacity);
        const color = luminance > 0.5 ? '#101623' : '#E7EEF8';
        return {
            color,
            shadow: color === '#101623' ? '0 1px 1px rgba(255, 255, 255, 0.25)' : '0 1px 1px rgba(0, 0, 0, 0.35)'
        };
    }

    function getRaceOptionsPerfectNitroOptions(module, fieldMap) {
        return {
            enabled: !!readModuleFieldValue(module, fieldMap, 'ENABLE_PERFECT_NITROS', { default: true, type: 'toggle' }),
            highlightColor: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_HIGHLIGHT_COLOR', { default: '#FFFFFF' }), '#FFFFFF'),
            enableHighlight: !!readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_ENABLE_HIGHLIGHT', { default: true, type: 'toggle' }),
            italic: !!readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_ITALIC', { default: false, type: 'toggle' }),
            rainbow: !!readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_RAINBOW', { default: false, type: 'toggle' }),
            highlightOpacity: normalizeRaceOptionsHighlightOpacity(readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_HIGHLIGHT_OPACITY', { default: 0.5, type: 'select' }), 0.5),
            overrideTextColor: !!readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_OVERRIDE_TEXT_COLOR', { default: false, type: 'toggle' }),
            textColor: normalizeRaceOptionsHexColorValue(readModuleFieldValue(module, fieldMap, 'PERFECT_NITRO_TEXT_COLOR', { default: '#FFFFFF' }), '#FFFFFF')
        };
    }

    function renderPerfectNitroPreview(preview, module, fieldMap) {
        const previewText = 'the quick brown fox jumps';
        const highlightWord = 'jumps';
        const highlightStart = previewText.indexOf(highlightWord);
        const highlightEnd = highlightStart + highlightWord.length;
        const options = getRaceOptionsPerfectNitroOptions(module, fieldMap);
        const themeOptions = getRaceOptionsThemeOptions(module, fieldMap);
        preview.innerHTML = '';

        if (themeOptions.darkModeEffective) {
            preview.style.background = RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND;
            preview.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND;
        } else if (themeOptions.overrideBackground) {
            preview.style.background = themeOptions.background;
            preview.style.color = '';
        } else {
            preview.style.background = '';
            preview.style.color = '';
        }

        preview.style.fontFamily = themeOptions.fontFamilyCss || '';

        const bgRgba = options.enableHighlight ? hexToRgba(options.highlightColor, options.highlightOpacity) : null;
        const adaptiveTextStyle = options.enableHighlight
            ? getPerfectNitroTextStyle(module, fieldMap, options.highlightColor, options.highlightOpacity)
            : { color: null, shadow: null };
        const textStyle = options.overrideTextColor
            ? { color: options.textColor, shadow: null }
            : options.rainbow
                ? { color: null, shadow: adaptiveTextStyle.shadow }
                : adaptiveTextStyle;

        Array.from(previewText).forEach((char, index) => {
            const span = createElement('span');
            span.textContent = char;

            const isHighlighted = index >= highlightStart && index < highlightEnd;
            if (isHighlighted) {
                if (bgRgba) span.style.backgroundColor = bgRgba;
                if (textStyle.color) span.style.color = textStyle.color;
                if (textStyle.shadow) span.style.textShadow = textStyle.shadow;
                if (options.italic) span.style.fontStyle = 'italic';
                if (options.rainbow) span.classList.add('ntcfg-pn-preview-rainbow');
            } else if (themeOptions.overrideForeground) {
                span.style.color = themeOptions.foreground;
            } else if (themeOptions.darkModeEffective) {
                span.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND;
            }

            preview.appendChild(span);
        });
    }

    function renderThemePreview(preview, cursorIndex, incorrect, module, fieldMap) {
        const options = getRaceOptionsThemeOptions(module, fieldMap);
        const previewText = 'The Quick Brown Fox is afraid of The Big Black';
        preview.innerHTML = '';

        if (options.darkModeEffective) {
            preview.style.background = RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND;
            preview.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND;
        } else if (options.overrideBackground) {
            preview.style.background = options.background;
            preview.style.color = '';
        } else {
            preview.style.background = '';
            preview.style.color = '';
        }

        preview.style.fontFamily = options.fontFamilyCss || '';
        preview.style.fontWeight = String(options.fontWeight || RACE_OPTIONS_THEME_FONT_WEIGHT_DEFAULT);
        preview.style.fontStyle = options.fontItalic ? 'italic' : '';

        Array.from(previewText).forEach((char, index) => {
            const span = createElement('span');
            if (index === cursorIndex) {
                span.classList.add(incorrect ? 'ntcfg-theme-char-incorrect' : 'ntcfg-theme-char-active');
            } else if (index < cursorIndex) {
                span.classList.add('ntcfg-theme-char-typed');
                if (options.enableRainbowTypedText && !options.hideTypedText) {
                    span.classList.add('ntcfg-theme-char-rainbow');
                    span.style.animationDuration = `${options.rainbowTypedTextSpeedSeconds}s`;
                    span.style.webkitAnimationDuration = `${options.rainbowTypedTextSpeedSeconds}s`;
                }
            }

            span.textContent = char;

            if (index > cursorIndex && options.overrideForeground) {
                span.style.color = options.foreground;
            } else if (index > cursorIndex && options.darkModeEffective) {
                span.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND;
            }
            if (index === cursorIndex && options.overrideForegroundActive) {
                span.style.color = options.foregroundActive;
            } else if (index === cursorIndex && options.darkModeEffective) {
                span.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND_ACTIVE;
            }
            if (index < cursorIndex && options.overrideForegroundTyped && !options.enableRainbowTypedText) {
                span.style.color = options.foregroundTyped;
                span.style.opacity = '1';
            } else if (index < cursorIndex && options.darkModeEffective && !options.enableRainbowTypedText) {
                span.style.color = RACE_OPTIONS_THEME_DARK_MODE_FOREGROUND_TYPED;
                span.style.opacity = '1';
            }
            if (index < cursorIndex && options.hideTypedText) {
                span.style.color = 'transparent';
                span.style.webkitTextFillColor = 'transparent';
                span.style.textShadow = 'none';
                span.style.opacity = '0';
            }
            if (index === cursorIndex && !incorrect && options.overrideBackgroundActive) {
                span.style.background = options.backgroundActive;
            } else if (index === cursorIndex && !incorrect && options.darkModeEffective) {
                span.style.background = RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND_ACTIVE;
            }
            if (index === cursorIndex && incorrect && options.overrideBackgroundIncorrect) {
                span.style.background = options.backgroundIncorrect;
            } else if (index === cursorIndex && incorrect && options.darkModeEffective) {
                span.style.background = RACE_OPTIONS_THEME_DARK_MODE_BACKGROUND_INCORRECT;
            }
            if (Number.isFinite(options.fontSizePx)) {
                span.style.fontSize = `${options.fontSizePx}px`;
            }
            span.style.fontWeight = String(options.fontWeight || RACE_OPTIONS_THEME_FONT_WEIGHT_DEFAULT);
            if (options.fontItalic) span.style.fontStyle = 'italic';
            preview.appendChild(span);
        });
    }

    // ── Extracted helpers (previously closures inside mountRaceOptionsSection) ──

    function createSwitch(checked, onChange) {
        const switchRoot = createElement('span', 'ntcfg-switch');
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.checked = !!checked;
        input.addEventListener('change', () => onChange(!!input.checked));
        const track = createElement('span', 'ntcfg-switch-track');
        switchRoot.append(input, track);
        return { root: switchRoot, input, track };
    }

    function createCompactColorControl(initialValue, onChange, wrapperClass = 'ntcfg-theme-color-compact') {
        const colorWrap = createElement('span', wrapperClass);
        const colorInput = document.createElement('input');
        colorInput.type = 'color';
        colorInput.className = 'ntcfg-color-picker';
        const hexInput = document.createElement('input', 'ntcfg-input');
        hexInput.type = 'text';
        hexInput.maxLength = 7;
        hexInput.placeholder = '#FFFFFF';
        const setValue = (value) => {
            const normalized = normalizeRaceOptionsHexColorValue(value, '#FFFFFF');
            colorInput.value = normalized;
            hexInput.value = normalized;
        };
        setValue(initialValue);
        colorInput.addEventListener('input', () => {
            const normalized = normalizeRaceOptionsHexColorValue(colorInput.value, colorInput.value);
            setValue(normalized);
            onChange(normalized);
        });
        const commitHex = () => {
            const normalized = normalizeRaceOptionsHexColorValue(hexInput.value, colorInput.value || '#FFFFFF');
            setValue(normalized);
            onChange(normalized);
        };
        hexInput.addEventListener('change', commitHex);
        hexInput.addEventListener('blur', commitHex);
        colorWrap.append(colorInput, hexInput);
        return { root: colorWrap, colorInput, hexInput, setValue };
    }

    function genericAppendCheckbox(root, opts, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const { labelText, key, defaultValue, helpText = '', onChange = null } = opts;
        const row = createElement('label', 'ntcfg-checkbox');
        const copy = createElement('span', 'ntcfg-checkbox-copy');
        copy.appendChild(createElement('span', 'ntcfg-checkbox-label', labelText));
        if (helpText) {
            copy.appendChild(createElement('span', 'ntcfg-checkbox-help', helpText));
        }
        const switchControl = createSwitch(
            readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'toggle' }),
            (checked) => {
                writeModuleFieldValue(module, fieldMap, key, checked, { default: defaultValue, type: 'toggle' });
                if (typeof onChange === 'function') onChange(checked, row);
                refreshPreviews();
            }
        );
        switchControl.input.setAttribute('data-field-key', key);
        switchControl.input.setAttribute('data-field-type', 'toggle');
        row.append(copy, switchControl.root);
        root.appendChild(row);
        return { row, input: switchControl.input };
    }

    function genericAppendNumberField(root, opts, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const { labelText, key, defaultValue, helpText = '', minRecommended = null, warningText = '', min = null, max = null, step = '1', onChange = null } = opts;
        const field = createElement('label', 'ntcfg-field');
        field.appendChild(createElement('div', 'ntcfg-field-title', labelText));

        const input = document.createElement('input');
        input.className = 'ntcfg-input';
        input.type = 'number';
        input.setAttribute('data-field-key', key);
        input.setAttribute('data-field-type', 'number');
        if (min != null) input.min = String(min);
        if (max != null) input.max = String(max);
        if (step != null) input.step = String(step);
        input.value = String(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'number' }));

        const warning = createElement('div', 'ntcfg-field-help ntcfg-field-warning');
        warning.hidden = true;
        const updateWarningState = (value) => {
            const shouldWarn = Number.isFinite(minRecommended) && Number.isFinite(value) && value < minRecommended;
            warning.hidden = !shouldWarn;
            warning.textContent = shouldWarn ? (warningText || `Warning: values below ${minRecommended} are not recommended.`) : '';
            input.classList.toggle('ntcfg-input-warning', shouldWarn);
        };

        input.addEventListener('input', () => updateWarningState(parseFloat(input.value)));
        input.addEventListener('change', () => {
            const parsed = parseFloat(input.value);
            if (!Number.isFinite(parsed)) return;
            writeModuleFieldValue(module, fieldMap, key, parsed, { default: defaultValue, type: 'number' });
            updateWarningState(parsed);
            if (typeof onChange === 'function') onChange(parsed, field);
            refreshPreviews();
        });

        field.appendChild(input);
        if (helpText) field.appendChild(createElement('div', 'ntcfg-field-help', helpText));
        if (Number.isFinite(minRecommended)) {
            field.appendChild(warning);
            updateWarningState(parseFloat(input.value));
        }
        root.appendChild(field);
        return { field, input, warning };
    }

    function genericAppendSelectField(root, opts, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const { labelText, key, defaultValue, options = [], helpText = '', onChange = null } = opts;
        const field = createElement('label', 'ntcfg-field');
        field.appendChild(createElement('div', 'ntcfg-field-title', labelText));
        const select = createElement('select', 'ntcfg-input');
        select.setAttribute('data-field-key', key);
        select.setAttribute('data-field-type', 'select');

        const safeOptions = Array.isArray(options) ? options.filter(Boolean) : [];
        const normalizeSelectedValue = (value) => {
            const raw = String(value ?? '').trim();
            const matched = safeOptions.find((option) => String(option.value) === raw);
            if (matched) return matched.value;
            const fallback = safeOptions.find((option) => String(option.value) === String(defaultValue).trim());
            return fallback ? fallback.value : (safeOptions[0]?.value ?? defaultValue);
        };

        safeOptions.forEach((optionDef) => {
            const option = document.createElement('option');
            option.value = String(optionDef.value);
            option.textContent = String(optionDef.label ?? optionDef.value);
            select.appendChild(option);
        });
        select.value = String(normalizeSelectedValue(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'select', options })));
        select.addEventListener('change', () => {
            const normalized = normalizeSelectedValue(select.value);
            select.value = String(normalized);
            writeModuleFieldValue(module, fieldMap, key, normalized, { default: defaultValue, type: 'select', options });
            if (typeof onChange === 'function') onChange(normalized, field);
            refreshPreviews();
        });

        field.appendChild(select);
        if (helpText) field.appendChild(createElement('div', 'ntcfg-field-help', helpText));
        root.appendChild(field);
        return { field, select };
    }

    function genericAppendTextField(root, opts, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const { labelText, key, defaultValue = '', helpText = '', placeholder = '', onChange = null } = opts;
        const field = createElement('label', 'ntcfg-field');
        field.appendChild(createElement('div', 'ntcfg-field-title', labelText));

        const inputRow = createElement('div', 'ntcfg-text-input-row');
        inputRow.style.cssText = 'display:flex;gap:6px;align-items:center;';

        const input = document.createElement('input');
        input.className = 'ntcfg-input';
        input.type = 'text';
        input.setAttribute('data-field-key', key);
        input.setAttribute('data-field-type', 'text');
        input.placeholder = placeholder || '';
        input.spellcheck = false;
        input.value = String(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'text' }) ?? '');

        const saveBtn = createElement('button', 'ntcfg-action ntcfg-action--primary ntcfg-text-save-btn', 'Save');
        saveBtn.type = 'button';
        saveBtn.style.cssText = 'padding:6px 14px;font-size:12px;white-space:nowrap;flex-shrink:0;';
        const clearBtn = createElement('button', 'ntcfg-action ntcfg-action--secondary ntcfg-text-clear-btn', 'Clear');
        clearBtn.type = 'button';
        clearBtn.style.cssText = 'padding:6px 10px;font-size:12px;white-space:nowrap;flex-shrink:0;';

        const commit = (value) => {
            writeModuleFieldValue(module, fieldMap, key, value, { default: defaultValue, type: 'text' });
            if (typeof onChange === 'function') onChange(value, field);
            refreshPreviews();
        };

        saveBtn.addEventListener('click', () => {
            commit(input.value.trim());
            showToast('Saved.');
        });

        clearBtn.addEventListener('click', () => {
            input.value = '';
            commit('');
            showToast('Cleared.');
        });

        // Also save on Enter key
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                commit(input.value.trim());
                showToast('Saved.');
            }
        });

        inputRow.append(input, saveBtn, clearBtn);
        field.appendChild(inputRow);
        if (helpText) field.appendChild(createElement('div', 'ntcfg-field-help', helpText));
        root.appendChild(field);
        return { field, input, saveBtn, clearBtn };
    }

    const IS_MAC = navigator.userAgentData?.platform === 'macOS' || /Mac|iPhone|iPad|iPod/i.test(navigator.platform || '');

    const MODIFIER_LABELS = IS_MAC
        ? { alt: '\u2325 Option +', ctrl: '\u2303 Ctrl +', shift: '\u21E7 Shift +', meta: '\u2318 Cmd +' }
        : { alt: 'Alt +', ctrl: 'Ctrl +', shift: 'Shift +', meta: 'Win +' };

    const MODIFIER_OPTIONS = IS_MAC
        ? [
            { value: 'alt', label: '\u2325 Option' },
            { value: 'ctrl', label: '\u2303 Ctrl' },
            { value: 'shift', label: '\u21E7 Shift' },
            { value: 'meta', label: '\u2318 Cmd' }
        ]
        : [
            { value: 'alt', label: 'Alt' },
            { value: 'ctrl', label: 'Ctrl' },
            { value: 'shift', label: 'Shift' },
            { value: 'meta', label: 'Win' }
        ];
    const MODIFIER2_OPTIONS = [
        { value: 'none', label: 'None' },
        ...MODIFIER_OPTIONS
    ];

    const SHORTCUT_CODE_TO_KEY = Object.freeze({
        Backquote: '`',
        Minus: '-',
        Equal: '=',
        BracketLeft: '[',
        BracketRight: ']',
        Backslash: '\\',
        Semicolon: ';',
        Quote: '\'',
        Comma: ',',
        Period: '.',
        Slash: '/',
        Space: ' ',
        NumpadDecimal: '.',
        NumpadDivide: '/',
        NumpadMultiply: '*',
        NumpadSubtract: '-',
        NumpadAdd: '+'
    });

    const SHORTCUT_CODE_LABELS = Object.freeze({
        Space: 'Space',
        Enter: 'Enter',
        NumpadEnter: 'Numpad Enter',
        Escape: 'Esc',
        Tab: 'Tab',
        Backspace: 'Backspace',
        Delete: 'Delete',
        Insert: 'Insert',
        Home: 'Home',
        End: 'End',
        PageUp: 'Page Up',
        PageDown: 'Page Down',
        ArrowUp: 'Up',
        ArrowDown: 'Down',
        ArrowLeft: 'Left',
        ArrowRight: 'Right'
    });

    const SHORTCUT_KEY_TO_CODE = Object.freeze({
        '`': 'Backquote',
        '-': 'Minus',
        '=': 'Equal',
        '[': 'BracketLeft',
        ']': 'BracketRight',
        '\\': 'Backslash',
        ';': 'Semicolon',
        '\'': 'Quote',
        ',': 'Comma',
        '.': 'Period',
        '/': 'Slash',
        ' ': 'Space',
        '*': 'NumpadMultiply',
        '+': 'NumpadAdd',
        ESC: 'Escape',
        ESCAPE: 'Escape',
        TAB: 'Tab',
        ENTER: 'Enter',
        RETURN: 'Enter',
        SPACE: 'Space',
        BACKSPACE: 'Backspace',
        DELETE: 'Delete',
        DEL: 'Delete',
        INSERT: 'Insert',
        HOME: 'Home',
        END: 'End',
        PAGEUP: 'PageUp',
        PAGEDOWN: 'PageDown',
        UP: 'ArrowUp',
        ARROWUP: 'ArrowUp',
        DOWN: 'ArrowDown',
        ARROWDOWN: 'ArrowDown',
        LEFT: 'ArrowLeft',
        ARROWLEFT: 'ArrowLeft',
        RIGHT: 'ArrowRight',
        ARROWRIGHT: 'ArrowRight'
    });

    const SHORTCUT_MODIFIER_CODES = new Set([
        'ShiftLeft', 'ShiftRight',
        'ControlLeft', 'ControlRight',
        'AltLeft', 'AltRight',
        'MetaLeft', 'MetaRight'
    ]);

    function getShortcutDisplayLabelFromCode(code) {
        const normalized = String(code || '').trim();
        if (!normalized) return '';
        if (/^Key[A-Z]$/i.test(normalized)) return normalized.slice(3).toUpperCase();
        if (/^Digit[0-9]$/.test(normalized)) return normalized.slice(5);
        if (/^Numpad[0-9]$/.test(normalized)) return normalized.slice(6);
        if (SHORTCUT_CODE_TO_KEY[normalized]) {
            return normalized === 'Space' ? 'Space' : SHORTCUT_CODE_TO_KEY[normalized];
        }
        if (/^F\d{1,2}$/i.test(normalized)) return normalized.toUpperCase();
        return SHORTCUT_CODE_LABELS[normalized] || normalized;
    }

    function inferShortcutCodeFromStoredKey(value) {
        const raw = String(value ?? '');
        if (!raw && raw !== ' ') return '';
        if (raw === ' ') return 'Space';

        const trimmed = raw.trim();
        if (!trimmed) return '';
        if (/^[A-Z]$/i.test(trimmed)) return `Key${trimmed.toUpperCase()}`;
        if (/^[0-9]$/.test(trimmed)) return `Digit${trimmed}`;
        if (/^F\d{1,2}$/i.test(trimmed)) return trimmed.toUpperCase();
        if (SHORTCUT_KEY_TO_CODE[trimmed]) return SHORTCUT_KEY_TO_CODE[trimmed];

        const upper = trimmed.toUpperCase();
        return SHORTCUT_KEY_TO_CODE[upper] || '';
    }

    function normalizeShortcutCodeValue(code, fallbackKey = '') {
        const rawCode = String(code || '').trim();
        if (!rawCode) return inferShortcutCodeFromStoredKey(fallbackKey);
        if (/^Key[A-Z]$/i.test(rawCode)) return `Key${rawCode.slice(3).toUpperCase()}`;
        if (/^Digit[0-9]$/.test(rawCode)) return rawCode;
        if (/^Numpad[0-9]$/.test(rawCode)) return rawCode;
        if (/^F\d{1,2}$/i.test(rawCode)) return rawCode.toUpperCase();
        return rawCode;
    }

    function normalizeShortcutStoredKey(value, fallbackCode = '') {
        const fallbackLabel = getShortcutDisplayLabelFromCode(fallbackCode);
        if (fallbackLabel) return fallbackLabel;

        const raw = String(value ?? '');
        if (!raw && raw !== ' ') return '';
        if (raw === ' ') return 'Space';

        const trimmed = raw.trim();
        if (!trimmed) return '';
        if (trimmed.length === 1) {
            return /^[a-z]$/i.test(trimmed) ? trimmed.toUpperCase() : trimmed;
        }

        const inferredCode = inferShortcutCodeFromStoredKey(trimmed);
        if (inferredCode) return getShortcutDisplayLabelFromCode(inferredCode);
        if (/^Key[A-Z]$/i.test(trimmed) || /^Digit[0-9]$/.test(trimmed) || /^Numpad[0-9]$/.test(trimmed) || /^F\d{1,2}$/i.test(trimmed)) {
            return getShortcutDisplayLabelFromCode(trimmed);
        }
        return trimmed;
    }

    function normalizeShortcutBindingEntry(entry = {}) {
        const path = String(entry.path || '');
        const action = String(entry.action || (path.startsWith('toggle:') ? 'toggle' : path.startsWith('action:') ? 'trigger' : 'navigate'));
        const code = normalizeShortcutCodeValue(entry.code, entry.key);
        const key = normalizeShortcutStoredKey(entry.key, code);
        return {
            code,
            key,
            path,
            mod1: String(entry.mod1 || 'alt'),
            mod2: String(entry.mod2 || 'none'),
            action,
            targetScriptId: String(entry.targetScriptId || ''),
            targetValue: String(entry.targetValue || '')
        };
    }

    function getShortcutBindingId(entry = {}) {
        const normalized = normalizeShortcutBindingEntry(entry);
        const primary = normalized.code || normalized.key;
        return primary ? [normalized.mod1, normalized.mod2, primary].join('::') : '';
    }

    function getShortcutCaptureLabel(entry = {}) {
        const normalized = normalizeShortcutBindingEntry(entry);
        return normalized.key || 'Press Key';
    }

    function parseShortcutTarget(path) {
        const raw = String(path || '');
        if (!raw) return { mode: 'navigate', scriptId: '', value: '' };
        if (raw.startsWith('toggle:') || raw.startsWith('action:')) {
            const parts = raw.split(':');
            if (parts.length >= 3) {
                return {
                    mode: raw.startsWith('toggle:') ? 'toggle' : 'trigger',
                    scriptId: parts[1],
                    value: `${parts[0]}:${parts[1]}:${parts.slice(2).join(':')}`
                };
            }
        }
        return { mode: 'navigate', scriptId: '', value: '' };
    }

    /** Gather all shortcut-capable targets grouped by script. */
    function getShortcutTargetsByScript() {
        const scripts = [];
        try {
            Object.keys(localStorage).forEach((lsKey) => {
                if (!lsKey.startsWith(MANIFEST_PREFIX)) return;
                const manifest = readJson(lsKey, null);
                if (!manifest || !manifest.id || !manifest.settings) return;
                const modLabel = manifest.name || manifest.id;
                const toggles = [];
                const actions = [];
                Object.entries(manifest.settings).forEach(([settingKey, meta]) => {
                    if (meta?.type === 'boolean') {
                        toggles.push({
                            key: settingKey,
                            label: meta.label || settingKey,
                            value: `toggle:${manifest.id}:${settingKey}`
                        });
                    }
                    if (meta?.type === 'action') {
                        actions.push({
                            key: settingKey,
                            label: meta.label || settingKey,
                            value: `action:${manifest.id}:${settingKey}`
                        });
                    }
                });
                if (toggles.length > 0 || actions.length > 0) {
                    scripts.push({ id: manifest.id, label: modLabel, toggles, actions });
                }
            });
        } catch { /* ignore */ }
        scripts.sort((a, b) => a.label.localeCompare(b.label));
        return scripts;
    }

    function genericAppendKeymapField(root, opts, ctx) {
        const { module, fieldMap, refreshPreviewFns, refreshPreviews } = ctx;
        const parentRefresh = refreshPreviews || (() => {});
        const { labelText, key, defaultValue = [], helpText = '', onChange = null } = opts;

        const field = createElement('label', 'ntcfg-field');
        field.appendChild(createElement('div', 'ntcfg-field-title', labelText));

        const container = createElement('div', 'ntcfg-keymap-container');

        const currentValue = (() => {
            const raw = readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'keymap' });
            return Array.isArray(raw) ? raw : defaultValue;
        })();

        const state = {
            entries: currentValue.map((entry) => {
                const normalized = normalizeShortcutBindingEntry(entry);
                const target = parseShortcutTarget(normalized.path);
                normalized.action = target.mode === 'navigate' ? normalized.action : target.mode;
                normalized.targetScriptId = target.scriptId;
                normalized.targetValue = target.value;
                return normalized;
            })
        };

        const commit = () => {
            const normalizedEntries = state.entries.map((entry) => {
                const normalized = normalizeShortcutBindingEntry(entry);
                normalized.path = normalized.action === 'navigate'
                    ? String(entry.path || '').trim()
                    : String(entry.targetValue || entry.path || '');
                return normalized;
            });

            const bindingCounts = new Map();
            normalizedEntries.forEach((entry) => {
                const bindingId = getShortcutBindingId(entry);
                if (!bindingId) return;
                bindingCounts.set(bindingId, (bindingCounts.get(bindingId) || 0) + 1);
            });

            const hasDuplicates = normalizedEntries.some((entry) => {
                const bindingId = getShortcutBindingId(entry);
                return bindingId && bindingCounts.get(bindingId) > 1;
            });
            if (hasDuplicates) {
                showToast('Resolve duplicate shortcut bindings before saving.');
                return;
            }

            const clean = normalizedEntries
                .filter((entry) => (entry.code || entry.key) && entry.path)
                .map((entry) => ({
                    code: entry.code,
                    key: entry.key,
                    path: entry.path,
                    mod1: entry.mod1,
                    mod2: entry.mod2,
                    action: entry.action
                }));
            writeModuleFieldValue(module, fieldMap, key, clean, { default: defaultValue, type: 'keymap' });
            if (typeof onChange === 'function') onChange(clean, field);
            parentRefresh();
        };

        const buildModSelect = (options, value, onChangeHandler) => {
            const select = createElement('select', 'ntcfg-input ntcfg-keymap-mod-select');
            options.forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.value;
                option.textContent = opt.label;
                select.appendChild(option);
            });
            select.value = value;
            select.addEventListener('change', () => onChangeHandler(select.value));
            return select;
        };

        const getTargetsForMode = (scriptData, scriptId, mode) => {
            const script = scriptData.find((entry) => entry.id === scriptId);
            if (!script) return [];
            if (mode === 'toggle') return script.toggles || [];
            if (mode === 'trigger') return script.actions || [];
            return [];
        };

        const renderRows = () => {
            container.innerHTML = '';
            const scriptData = getShortcutTargetsByScript();
            const normalizedEntries = state.entries.map((entry) => {
                const normalized = normalizeShortcutBindingEntry(entry);
                normalized.path = normalized.action === 'navigate'
                    ? String(entry.path || '')
                    : String(entry.targetValue || entry.path || '');
                return normalized;
            });
            const bindingCounts = new Map();
            normalizedEntries.forEach((entry) => {
                const bindingId = getShortcutBindingId(entry);
                if (!bindingId) return;
                bindingCounts.set(bindingId, (bindingCounts.get(bindingId) || 0) + 1);
            });
            let hasDuplicateBindings = false;

            state.entries.forEach((entry, idx) => {
                entry.code = normalizeShortcutCodeValue(entry.code, entry.key);
                entry.key = normalizeShortcutStoredKey(entry.key, entry.code);

                // Main row: modifiers + key + action mode
                const row = createElement('div', 'ntcfg-keymap-row');

                const mod1Select = buildModSelect(MODIFIER_OPTIONS, entry.mod1, (val) => {
                    entry.mod1 = val;
                    renderRows();
                });
                const plus1 = createElement('span', 'ntcfg-keymap-plus', '+');
                const mod2Select = buildModSelect(MODIFIER2_OPTIONS, entry.mod2, (val) => {
                    entry.mod2 = val;
                    renderRows();
                });
                const plus2 = createElement('span', 'ntcfg-keymap-plus', '+');

                const keyButton = createElement('button', 'ntcfg-input ntcfg-keymap-key ntcfg-keymap-keycapture', getShortcutCaptureLabel(entry));
                keyButton.type = 'button';
                keyButton.title = entry.code
                    ? `Primary key: ${entry.code}. Press Backspace/Delete to clear.`
                    : 'Click and press any non-modifier key. Press Backspace/Delete to clear.';
                const syncKeyButtonLabel = (message = '') => {
                    keyButton.textContent = message || getShortcutCaptureLabel(entry);
                };
                keyButton.addEventListener('click', () => {
                    keyButton.classList.add('is-capturing');
                    syncKeyButtonLabel('Press Key');
                    keyButton.focus();
                });
                keyButton.addEventListener('blur', () => {
                    keyButton.classList.remove('is-capturing');
                    syncKeyButtonLabel();
                });
                keyButton.addEventListener('keydown', (event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    const pressedCode = String(event.code || '');
                    if (SHORTCUT_MODIFIER_CODES.has(pressedCode)) {
                        syncKeyButtonLabel('Pick Non-Modifier');
                        return;
                    }
                    if (pressedCode === 'Backspace' || pressedCode === 'Delete') {
                        entry.code = '';
                        entry.key = '';
                        renderRows();
                        return;
                    }
                    const normalizedCode = normalizeShortcutCodeValue(pressedCode, event.key);
                    if (!normalizedCode) {
                        syncKeyButtonLabel('Unsupported Key');
                        return;
                    }
                    entry.code = normalizedCode;
                    entry.key = normalizeShortcutStoredKey(event.key, normalizedCode);
                    renderRows();
                });

                // Action mode selector
                const actionSelect = createElement('select', 'ntcfg-input ntcfg-keymap-action-select');
                [
                    { value: 'navigate', label: 'URL' },
                    { value: 'toggle', label: 'Toggle' },
                    { value: 'trigger', label: 'Action' }
                ].forEach(opt => {
                    const option = document.createElement('option');
                    option.value = opt.value;
                    option.textContent = opt.label;
                    actionSelect.appendChild(option);
                });
                actionSelect.value = entry.action;

                // Path input (for navigate mode)
                const pathInput = document.createElement('input');
                pathInput.className = 'ntcfg-input ntcfg-keymap-path';
                pathInput.type = 'text';
                pathInput.value = entry.action === 'navigate' ? entry.path : '';
                pathInput.placeholder = '/page-url';
                pathInput.spellcheck = false;

                // Script selector (for toggle mode)
                const scriptSelect = createElement('select', 'ntcfg-input ntcfg-keymap-script-select');
                const emptyScriptOpt = document.createElement('option');
                emptyScriptOpt.value = '';
                emptyScriptOpt.textContent = 'Select script...';
                scriptSelect.appendChild(emptyScriptOpt);
                scriptData.forEach(s => {
                    const option = document.createElement('option');
                    option.value = s.id;
                    option.textContent = s.label;
                    scriptSelect.appendChild(option);
                });

                // Setting selector (for toggle mode)
                const settingSelect = createElement('select', 'ntcfg-input ntcfg-keymap-setting-select');

                const targetInfo = parseShortcutTarget(entry.path);
                if (!entry.targetScriptId && targetInfo.scriptId) entry.targetScriptId = targetInfo.scriptId;
                if (!entry.targetValue && targetInfo.value) entry.targetValue = targetInfo.value;
                scriptSelect.value = entry.targetScriptId || '';

                const populateSettings = (scriptId, mode) => {
                    settingSelect.innerHTML = '';
                    const emptyOpt = document.createElement('option');
                    emptyOpt.value = '';
                    emptyOpt.textContent = mode === 'trigger' ? 'Select action...' : 'Select setting...';
                    settingSelect.appendChild(emptyOpt);
                    const targets = getTargetsForMode(scriptData, scriptId, mode);
                    targets.forEach((target) => {
                        const option = document.createElement('option');
                        option.value = target.value;
                        option.textContent = target.label;
                        settingSelect.appendChild(option);
                    });
                };

                Array.from(scriptSelect.options).forEach((option, optionIndex) => {
                    if (optionIndex === 0) return;
                    option.disabled = getTargetsForMode(scriptData, option.value, entry.action).length === 0;
                });
                populateSettings(entry.targetScriptId || '', entry.action);
                if (entry.targetValue) settingSelect.value = entry.targetValue;

                scriptSelect.addEventListener('change', () => {
                    entry.targetScriptId = scriptSelect.value;
                    entry.targetValue = '';
                    entry.path = '';
                    renderRows();
                });

                settingSelect.addEventListener('change', () => {
                    entry.targetValue = settingSelect.value;
                    entry.path = settingSelect.value;
                    renderRows();
                });

                // Toggle visibility based on action mode
                const updateActionMode = () => {
                    if (entry.action === 'navigate') {
                        pathInput.style.display = '';
                        scriptSelect.style.display = 'none';
                        settingSelect.style.display = 'none';
                    } else {
                        pathInput.style.display = 'none';
                        scriptSelect.style.display = '';
                        settingSelect.style.display = '';
                    }
                };
                updateActionMode();

                actionSelect.addEventListener('change', () => {
                    entry.action = actionSelect.value;
                    entry.path = '';
                    entry.targetScriptId = '';
                    entry.targetValue = '';
                    pathInput.value = '';
                    renderRows();
                });

                const removeBtn = createElement('button', 'ntcfg-action ntcfg-action--danger ntcfg-keymap-remove', '\u00D7');
                removeBtn.type = 'button';
                removeBtn.title = 'Remove shortcut';

                pathInput.addEventListener('input', () => {
                    entry.path = pathInput.value;
                });
                pathInput.addEventListener('change', () => {
                    entry.path = pathInput.value.trim();
                    renderRows();
                });
                removeBtn.addEventListener('click', () => {
                    state.entries.splice(idx, 1);
                    renderRows();
                });

                const equals = createElement('span', 'ntcfg-keymap-plus', '=');
                row.append(mod1Select, plus1, mod2Select, plus2, keyButton, equals, actionSelect, pathInput, scriptSelect, settingSelect, removeBtn);
                const issues = [];
                const bindingId = getShortcutBindingId(entry);
                if (bindingId && (bindingCounts.get(bindingId) || 0) > 1) {
                    hasDuplicateBindings = true;
                    issues.push('Duplicate binding. Choose a different modifier or key.');
                }
                if (entry.action === 'navigate' && entry.path && !String(entry.path).trim().startsWith('/')) {
                    issues.push('Navigation targets should usually start with /.');
                }
                if (entry.action !== 'navigate' && entry.targetScriptId && getTargetsForMode(scriptData, entry.targetScriptId, entry.action).length === 0) {
                    issues.push('This script does not expose a compatible shortcut target for that action type.');
                }
                if (issues.length > 0) {
                    row.classList.add('is-invalid');
                }
                container.appendChild(row);
                if (issues.length > 0) {
                    container.appendChild(createElement('div', 'ntcfg-keymap-warning', issues.join(' ')));
                }
            });

            // Add button row
            const addRow = createElement('div', 'ntcfg-keymap-add-row');
            const addBtn = createElement('button', 'ntcfg-action ntcfg-action--secondary ntcfg-keymap-add', '+ Add Shortcut');
            addBtn.type = 'button';
            addBtn.addEventListener('click', () => {
                state.entries.push({
                    code: '',
                    key: '',
                    path: '',
                    mod1: 'alt',
                    mod2: 'none',
                    action: 'navigate',
                    targetScriptId: '',
                    targetValue: ''
                });
                renderRows();
            });

            const saveBtn = createElement('button', 'ntcfg-action ntcfg-action--primary ntcfg-keymap-save', 'Save');
            saveBtn.type = 'button';
            saveBtn.disabled = hasDuplicateBindings;
            saveBtn.addEventListener('click', () => {
                if (hasDuplicateBindings) {
                    showToast('Resolve duplicate shortcut bindings before saving.');
                    return;
                }
                commit();
                showToast('Shortcuts saved.');
            });

            addRow.append(addBtn, saveBtn);
            container.appendChild(addRow);
            if (hasDuplicateBindings) {
                container.appendChild(createElement('div', 'ntcfg-field-help ntcfg-keymap-summary', 'Duplicate bindings were found. Fix them before saving.'));
            }
        };

        renderRows();
        field.appendChild(container);
        if (helpText) field.appendChild(createElement('div', 'ntcfg-field-help', helpText));
        root.appendChild(field);

        return { field, container };
    }

    function genericAppendResetButton(root, labelText, defaults, ctx) {
        const { module, fieldMap, rerender } = ctx;
        const resetButton = createElement('button', 'ntcfg-action ntcfg-inline-action', labelText);
        resetButton.type = 'button';
        resetButton.addEventListener('click', () => {
            Object.entries(defaults).forEach(([settingKey, value]) => {
                writeModuleFieldValue(module, fieldMap, settingKey, value);
            });
            rerender();
        });
        root.appendChild(resetButton);
    }

    function genericAppendThemeSettingToggle(root, labelText, key, defaultValue, ctx, onChange = null) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const row = createElement('div', 'ntcfg-theme-setting');
        row.appendChild(createElement('div', 'ntcfg-theme-setting-title', labelText));
        const controls = createElement('div', 'ntcfg-theme-setting-controls');
        const switchControl = createSwitch(
            readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'toggle' }),
            (checked) => {
                writeModuleFieldValue(module, fieldMap, key, checked, { default: defaultValue, type: 'toggle' });
                if (typeof onChange === 'function') onChange(checked, row);
                refreshPreviews();
            }
        );
        controls.appendChild(switchControl.root);
        row.appendChild(controls);
        root.appendChild(row);
        return { row, input: switchControl.input, controls };
    }

    function genericAppendThemeSettingColor(root, labelText, toggleKey, toggleDefault, colorKey, colorDefault, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const row = createElement('div', 'ntcfg-theme-setting');
        row.appendChild(createElement('div', 'ntcfg-theme-setting-title', labelText));
        const controls = createElement('div', 'ntcfg-theme-setting-controls');
        const colorControl = createCompactColorControl(
            readModuleFieldValue(module, fieldMap, colorKey, { default: colorDefault, type: 'color' }),
            (value) => {
                writeModuleFieldValue(module, fieldMap, colorKey, value, { default: colorDefault, type: 'color' });
                refreshPreviews();
            }
        );
        const switchControl = createSwitch(
            readModuleFieldValue(module, fieldMap, toggleKey, { default: toggleDefault, type: 'toggle' }),
            (checked) => {
                writeModuleFieldValue(module, fieldMap, toggleKey, checked, { default: toggleDefault, type: 'toggle' });
                refreshPreviews();
            }
        );
        controls.append(colorControl.root, switchControl.root);
        row.append(controls);
        root.appendChild(row);
    }

    function genericAppendThemeSettingSelect(root, labelText, key, defaultValue, options, ctx, className = 'ntcfg-input ntcfg-theme-input-compact ntcfg-theme-input-select', onChange = null) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const row = createElement('div', 'ntcfg-theme-setting');
        row.appendChild(createElement('div', 'ntcfg-theme-setting-title', labelText));
        const controls = createElement('div', 'ntcfg-theme-setting-controls');
        const select = createElement('select', className);
        const safeOptions = Array.isArray(options) ? options.filter(Boolean) : [];
        const normalizeSelectedValue = (value) => {
            const raw = String(value ?? '').trim();
            const match = safeOptions.find((option) => String(option.value) === raw);
            if (match) return match.value;
            const fallback = safeOptions.find((option) => String(option.value) === String(defaultValue).trim());
            return fallback ? fallback.value : (safeOptions[0]?.value ?? defaultValue);
        };
        safeOptions.forEach((optionDef) => {
            const option = document.createElement('option');
            option.value = String(optionDef.value);
            option.textContent = String(optionDef.label ?? optionDef.value);
            select.appendChild(option);
        });
        select.value = String(normalizeSelectedValue(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'select', options })));
        select.addEventListener('change', () => {
            const normalized = normalizeSelectedValue(select.value);
            select.value = String(normalized);
            writeModuleFieldValue(module, fieldMap, key, normalized, { default: defaultValue, type: 'select', options });
            if (typeof onChange === 'function') onChange(normalized, row, select);
            refreshPreviews();
        });
        controls.appendChild(select);
        row.appendChild(controls);
        root.appendChild(row);
        return { row, select, controls };
    }

    function genericAppendThemeSettingNumber(root, labelText, key, defaultValue, config, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const { min = 1, max = 60, step = 1, presets = [], normalizeValue = (value, fallback) => {
            const parsed = Number(value);
            return Number.isFinite(parsed) ? parsed : Number(fallback);
        } } = config || {};

        const row = createElement('div', 'ntcfg-theme-setting');
        row.appendChild(createElement('div', 'ntcfg-theme-setting-title', labelText));
        const controls = createElement('div', 'ntcfg-theme-setting-controls');

        const input = createElement('input', 'ntcfg-input ntcfg-theme-input-compact ntcfg-theme-input-number');
        input.type = 'number';
        input.min = String(min);
        input.max = String(max);
        input.step = String(step);
        input.value = String(normalizeValue(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'number' }), defaultValue));

        const presetSelect = createElement('select', 'ntcfg-input ntcfg-theme-input-compact ntcfg-theme-input-preset');
        const customPresetValue = '__custom__';
        const customOption = document.createElement('option');
        customOption.value = customPresetValue;
        customOption.textContent = 'Custom';
        presetSelect.appendChild(customOption);

        const normalizedPresets = Array.isArray(presets)
            ? presets.map((preset) => {
                if (!preset || typeof preset !== 'object') return null;
                const value = normalizeValue(preset.value, defaultValue);
                return { value, label: String(preset.label || `${value}s`).trim() };
            }).filter(Boolean)
            : [];

        normalizedPresets.forEach((preset) => {
            const option = document.createElement('option');
            option.value = String(preset.value);
            option.textContent = `${preset.label} (${preset.value}s)`;
            presetSelect.appendChild(option);
        });

        const syncPresetFromValue = (value) => {
            const matched = normalizedPresets.find((preset) => preset.value === value);
            presetSelect.value = matched ? String(matched.value) : customPresetValue;
        };

        const save = () => {
            const normalized = normalizeValue(input.value, defaultValue);
            input.value = String(normalized);
            syncPresetFromValue(normalized);
            writeModuleFieldValue(module, fieldMap, key, normalized, { default: defaultValue, type: 'number' });
            refreshPreviews();
        };

        input.addEventListener('change', save);
        input.addEventListener('blur', save);
        presetSelect.addEventListener('change', () => {
            if (presetSelect.value === customPresetValue) return;
            const normalized = normalizeValue(presetSelect.value, defaultValue);
            input.value = String(normalized);
            writeModuleFieldValue(module, fieldMap, key, normalized, { default: defaultValue, type: 'number' });
            refreshPreviews();
        });

        controls.appendChild(input);
        if (normalizedPresets.length) {
            controls.appendChild(presetSelect);
            syncPresetFromValue(normalizeValue(input.value, defaultValue));
        }

        row.appendChild(controls);
        root.appendChild(row);
        return { row, input, presetSelect };
    }

    // ── Preview registry ──
    const applyStickyTop = (el) => {
        requestAnimationFrame(() => {
            const header = document.querySelector('.site-header, .structure-header, header');
            const headerBottom = header ? header.getBoundingClientRect().bottom : 0;
            el.style.top = Math.max(0, headerBottom) + 'px';
        });
    };

    function mountHtmlStickyPreview(fields, refreshPreviewFns, buildHtml, labelText = 'Preview:') {
        const stickyWrapper = createElement('div', 'ntcfg-sticky-preview');
        applyStickyTop(stickyWrapper);
        stickyWrapper.appendChild(createElement('div', 'ntcfg-theme-preview-label', labelText));
        const previewHost = createElement('div', 'ntcfg-preview-host');
        stickyWrapper.appendChild(previewHost);
        fields.appendChild(stickyWrapper);
        refreshPreviewFns.push(() => {
            previewHost.innerHTML = buildHtml();
        });
    }

    function buildPreviewPillHtml(label, active = true, tone = '') {
        const toneClass = tone ? ` ntcfg-preview-pill--${tone}` : '';
        return `<span class="ntcfg-preview-pill${active ? ' is-active' : ' is-off'}${toneClass}">${escapeHtml(label)}</span>`;
    }

    function buildPreviewTileHtml(label, value, active = true, tone = '') {
        const toneClass = tone ? ` ntcfg-preview-tile--${tone}` : '';
        return `
            <div class="ntcfg-preview-tile${active ? '' : ' is-off'}${toneClass}">
                <span class="ntcfg-preview-tile-label">${escapeHtml(label)}</span>
                <strong class="ntcfg-preview-tile-value">${escapeHtml(value)}</strong>
            </div>
        `;
    }

    function truncatePreviewText(value, maxLength = 56) {
        const text = String(value || '').trim();
        if (!text) return '';
        if (text.length <= maxLength) return text;
        return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
    }

    function readPreviewToggle(module, fieldMap, key, defaultValue = false) {
        return !!readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'toggle' });
    }

    function readPreviewText(module, fieldMap, key, defaultValue = '') {
        return String(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'text' }) ?? defaultValue);
    }

    function readPreviewSelect(module, fieldMap, key, defaultValue = '', options = []) {
        return String(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'select', options }) ?? defaultValue);
    }

    function readPreviewNumber(module, fieldMap, key, defaultValue = 0) {
        const parsed = Number(readModuleFieldValue(module, fieldMap, key, { default: defaultValue, type: 'number' }));
        return Number.isFinite(parsed) ? parsed : Number(defaultValue);
    }

    function getPreviewUrlHost(url) {
        try {
            return new URL(String(url || '').trim()).host;
        } catch {
            return truncatePreviewText(url, 44) || 'Not configured';
        }
    }

    function getMaskedPreviewObserverId() {
        try {
            const observerId = String(localStorage.getItem('startrack-observer-id') || '').trim();
            if (!observerId) return 'Generated on first send';
            if (observerId.length <= 12) return observerId;
            return `${observerId.slice(0, 8)}...${observerId.slice(-4)}`;
        } catch {
            return 'Generated on first send';
        }
    }

    function getMusicPreviewPlatform(url, fallbackPlatform = 'spotify') {
        const raw = String(url || '').toLowerCase();
        if (raw.includes('youtube') || raw.includes('youtu.be')) return 'youtube';
        if (raw.includes('apple.com') || raw.includes('music.apple')) return 'apple-music';
        if (raw.includes('spotify')) return 'spotify';
        return fallbackPlatform || 'spotify';
    }

    function getMusicPreviewPlatformLabel(platform) {
        if (platform === 'youtube') return 'YouTube';
        if (platform === 'apple-music') return 'Apple Music';
        return 'Spotify';
    }

    function getMusicPreviewSourceType(url) {
        const raw = String(url || '').toLowerCase();
        if (!raw) return 'Source';
        if (raw.includes('playlist') || raw.includes('list=')) return 'Playlist';
        if (raw.includes('/album')) return 'Album';
        if (raw.includes('/track') || raw.includes('/song') || raw.includes('/video') || raw.includes('watch?v=')) return 'Track';
        return 'Source';
    }

    function getSectionPreviewRenderer(module, section) {
        const previewType = String(section?.preview?.type || '').trim();
        if (!previewType) return null;
        const moduleScopedKey = previewType.includes(':') ? previewType : `${module.id}:${previewType}`;
        return PREVIEW_RENDERERS[moduleScopedKey] || PREVIEW_RENDERERS[previewType] || null;
    }

    const PREVIEW_RENDERERS = {
        'race-options:theme': function (fields, refreshPreviewFns, module, fieldMap) {
            const stickyWrapper = createElement('div', 'ntcfg-sticky-preview');
            applyStickyTop(stickyWrapper);
            stickyWrapper.appendChild(createElement('div', 'ntcfg-theme-preview-label', 'Preview:'));
            const previewGrid = createElement('div', 'ntcfg-theme-preview-grid');
            const normalPreview = createElement('div', 'ntcfg-theme-preview');
            const incorrectPreview = createElement('div', 'ntcfg-theme-preview');
            previewGrid.append(normalPreview, incorrectPreview);
            stickyWrapper.appendChild(previewGrid);
            fields.appendChild(stickyWrapper);
            refreshPreviewFns.push(() => renderThemePreview(normalPreview, 7, false, module, fieldMap));
            refreshPreviewFns.push(() => renderThemePreview(incorrectPreview, 7, true, module, fieldMap));
        },
        'race-options:perfect-nitro': function (fields, refreshPreviewFns, module, fieldMap) {
            const stickyWrapper = createElement('div', 'ntcfg-sticky-preview');
            applyStickyTop(stickyWrapper);
            stickyWrapper.appendChild(createElement('div', 'ntcfg-theme-preview-label', 'Preview:'));
            const preview = createElement('div', 'ntcfg-pn-preview');
            stickyWrapper.appendChild(preview);
            fields.appendChild(stickyWrapper);
            refreshPreviewFns.push(() => renderPerfectNitroPreview(preview, module, fieldMap));
        },
        'racer-badges:general': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const showRacerBadges = readPreviewToggle(module, fieldMap, 'SHOW_RACER_BADGES', true);
                const showTeamBadges = readPreviewToggle(module, fieldMap, 'SHOW_TEAM_BADGES', true);
                const showRacerBanners = readPreviewToggle(module, fieldMap, 'SHOW_RACER_BANNERS', true);
                const showTeamBanners = readPreviewToggle(module, fieldMap, 'SHOW_TEAM_BANNERS', true);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Inline placements</div>
                            <div class="ntcfg-badge-inline-preview">
                                <div class="ntcfg-badge-name-chip">
                                    <span class="ntcfg-badge-name-text">Captain.Loveridge</span>
                                    ${showRacerBadges ? '<span class="ntcfg-badge-chip ntcfg-badge-chip--racer">Top 10</span>' : buildPreviewPillHtml('Racer badges off', false)}
                                </div>
                                <div class="ntcfg-badge-name-chip">
                                    <span class="ntcfg-badge-name-text">STAR</span>
                                    ${showTeamBadges ? '<span class="ntcfg-badge-chip ntcfg-badge-chip--team">Top 50 Team</span>' : buildPreviewPillHtml('Team badges off', false)}
                                </div>
                            </div>
                        </div>
                        <div class="ntcfg-preview-grid ntcfg-preview-grid--two">
                            <div class="ntcfg-preview-card${showRacerBanners ? '' : ' is-off'}">
                                <div class="ntcfg-preview-card-title">Racer banner</div>
                                <div class="ntcfg-badge-banner ntcfg-badge-banner--racer">Top Racer Banner</div>
                            </div>
                            <div class="ntcfg-preview-card${showTeamBanners ? '' : ' is-off'}">
                                <div class="ntcfg-preview-card-title">Team banner</div>
                                <div class="ntcfg-badge-banner ntcfg-badge-banner--team">Top Team Banner</div>
                            </div>
                        </div>
                        <div class="ntcfg-preview-pill-row">
                            ${buildPreviewPillHtml('Racer badges', showRacerBadges, 'blue')}
                            ${buildPreviewPillHtml('Team badges', showTeamBadges, 'gold')}
                            ${buildPreviewPillHtml('Racer banners', showRacerBanners, 'blue')}
                            ${buildPreviewPillHtml('Team banners', showTeamBanners, 'gold')}
                        </div>
                    </div>
                `;
            });
        },
        'racer-badges:pages': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const pageStates = [
                    ['Race', readPreviewToggle(module, fieldMap, 'ENABLE_RACE_BADGES', true)],
                    ['Profile', readPreviewToggle(module, fieldMap, 'ENABLE_PROFILE_BADGES', true)],
                    ['Team', readPreviewToggle(module, fieldMap, 'ENABLE_TEAM_PAGE_BADGES', true)],
                    ['Garage', readPreviewToggle(module, fieldMap, 'ENABLE_GARAGE_BADGES', true)],
                    ['Friends', readPreviewToggle(module, fieldMap, 'ENABLE_FRIENDS_BADGES', true)],
                    ['Leagues', readPreviewToggle(module, fieldMap, 'ENABLE_LEAGUES_BADGES', true)],
                    ['Header', readPreviewToggle(module, fieldMap, 'ENABLE_HEADER_BADGE', true)]
                ];
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Page coverage</div>
                            <div class="ntcfg-preview-matrix">
                                ${pageStates.map(([label, enabled]) => buildPreviewTileHtml(label, enabled ? 'Enabled' : 'Hidden', enabled, enabled ? 'blue' : '')).join('')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'racer-badges:advanced': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const debugLogging = readPreviewToggle(module, fieldMap, 'DEBUG_LOGGING', false);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Diagnostics</div>
                            <div class="ntcfg-preview-card-copy">Use debug logging when checking why a badge did or did not appear on a page.</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Debug logging', debugLogging, 'danger')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-hunter:general': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const enabled = readPreviewToggle(module, fieldMap, 'ENABLED', true);
                const showFloatingToggle = readPreviewToggle(module, fieldMap, 'SHOW_FLOATING_TOGGLE', true);
                const loggingEnabled = readPreviewToggle(module, fieldMap, 'ENABLE_LOGGING', true);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-grid ntcfg-preview-grid--two">
                            <div class="ntcfg-preview-card">
                                <div class="ntcfg-preview-card-title">Floating control</div>
                                ${showFloatingToggle
                                    ? `<button type="button" class="ntcfg-bot-toggle-preview${enabled ? ' is-on' : ' is-off'}">Bot Hunter: ${enabled ? 'ON' : 'OFF'}</button>`
                                    : '<div class="ntcfg-preview-empty">Floating toggle hidden.</div>'}
                            </div>
                            <div class="ntcfg-preview-card">
                                <div class="ntcfg-preview-card-title">Runtime state</div>
                                <div class="ntcfg-preview-card-copy">${enabled ? 'Race tracking is active and listening for race events.' : 'Race tracking is paused until you turn it back on.'}</div>
                                <div class="ntcfg-preview-pill-row">
                                    ${buildPreviewPillHtml('Tracking enabled', enabled, 'blue')}
                                    ${buildPreviewPillHtml('Floating toggle', showFloatingToggle, 'blue')}
                                    ${buildPreviewPillHtml('Console logging', loggingEnabled, 'gold')}
                                </div>
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-hunter:observer': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const endpoint = readPreviewText(module, fieldMap, 'API_ENDPOINT', 'https://ntstartrack.org/api/bot-observations');
                const keepalive = readPreviewToggle(module, fieldMap, 'USE_KEEPALIVE', true);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-grid ntcfg-preview-grid--two">
                            <div class="ntcfg-preview-card">
                                <div class="ntcfg-preview-card-title">Destination</div>
                                <div class="ntcfg-preview-card-copy">${escapeHtml(getPreviewUrlHost(endpoint))}</div>
                                <div class="ntcfg-preview-card-subcopy">${escapeHtml(truncatePreviewText(endpoint, 64))}</div>
                            </div>
                            <div class="ntcfg-preview-card">
                                <div class="ntcfg-preview-card-title">Observer identity</div>
                                <div class="ntcfg-preview-card-copy">${escapeHtml(getMaskedPreviewObserverId())}</div>
                                <div class="ntcfg-preview-pill-row">
                                    ${buildPreviewPillHtml('Keepalive delivery', keepalive, 'blue')}
                                </div>
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-hunter:analysis': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const detect1000Ms = readPreviewToggle(module, fieldMap, 'DETECT_1000MS_PATTERN', true);
                const detectSmoothing = readPreviewToggle(module, fieldMap, 'DETECT_EXCESSIVE_SMOOTHING', true);
                const tolerance = readPreviewNumber(module, fieldMap, 'BOUNDARY_TOLERANCE_MS', 100);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Pattern detection</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('1000ms boundary detection', detect1000Ms, 'danger')}
                                ${buildPreviewPillHtml('Smoothing detection', detectSmoothing, 'gold')}
                            </div>
                            <div class="ntcfg-preview-matrix">
                                ${buildPreviewTileHtml('Tolerance', `${tolerance} ms`, true, 'blue')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-flag:display': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const showNotFlagged = readPreviewToggle(module, fieldMap, 'SHOW_NOT_FLAGGED', true);
                const showUntracked = readPreviewToggle(module, fieldMap, 'SHOW_UNTRACKED', true);
                const useRobotIcon = readPreviewToggle(module, fieldMap, 'USE_ROBOT_ICON', false);
                const flaggedLabel = useRobotIcon ? 'BOT' : 'FLAG';
                const legend = [
                    { label: 'Flagged racer', indicator: flaggedLabel, tone: 'danger', enabled: true },
                    { label: 'Not flagged', indicator: 'OK', tone: 'success', enabled: showNotFlagged },
                    { label: 'Untracked', indicator: '?', tone: 'warning', enabled: showUntracked }
                ].filter((entry) => entry.enabled);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Legend preview</div>
                            <div class="ntcfg-flag-preview-row">
                                ${legend.map((entry) => `
                                    <div class="ntcfg-flag-sample">
                                        <span class="ntcfg-flag-indicator ntcfg-flag-indicator--${escapeHtml(entry.tone)}">${escapeHtml(entry.indicator)}</span>
                                        <span>${escapeHtml(entry.label)}</span>
                                    </div>
                                `).join('')}
                            </div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Robot icon', useRobotIcon, 'danger')}
                                ${buildPreviewPillHtml('Show not flagged', showNotFlagged, 'success')}
                                ${buildPreviewPillHtml('Show untracked', showUntracked, 'warning')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-flag:sources': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const useStartrack = readPreviewToggle(module, fieldMap, 'USE_STARTRACK', true);
                const useLegacy = readPreviewToggle(module, fieldMap, 'USE_NTL_LEGACY', true);
                const concurrency = readPreviewNumber(module, fieldMap, 'NETWORK_CONCURRENCY_LIMIT', 6);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Lookup sources</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('NT StarTrack', useStartrack, 'blue')}
                                ${buildPreviewPillHtml('NTL Legacy', useLegacy, 'gold')}
                            </div>
                            <div class="ntcfg-preview-matrix">
                                ${buildPreviewTileHtml('Concurrency', `${concurrency} requests`, true, 'blue')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-flag:cache': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const cacheDays = readPreviewNumber(module, fieldMap, 'STARTRACK_CACHE_DAYS', 7);
                const crossTab = readPreviewToggle(module, fieldMap, 'CROSS_TAB_SYNC', true);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Cache behavior</div>
                            <div class="ntcfg-preview-matrix">
                                ${buildPreviewTileHtml('StarTrack cache', `${cacheDays} day${cacheDays === 1 ? '' : 's'}`, true, 'blue')}
                            </div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Cross-tab sync', crossTab, 'blue')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'bot-flag:advanced': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const debugLogging = readPreviewToggle(module, fieldMap, 'DEBUG_LOGGING', false);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Diagnostics</div>
                            <div class="ntcfg-preview-card-copy">Verbose logging helps trace source lookups, cache hits, and icon placement.</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Debug logging', debugLogging, 'danger')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'leaderboards:views': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const view = readPreviewSelect(module, fieldMap, 'DEFAULT_VIEW', 'individual');
                const timeframe = readPreviewSelect(module, fieldMap, 'DEFAULT_TIMEFRAME', 'season');
                const showManualRefresh = readPreviewToggle(module, fieldMap, 'SHOW_MANUAL_REFRESH', true);
                const highlightPositionChange = readPreviewToggle(module, fieldMap, 'HIGHLIGHT_POSITION_CHANGE', true);
                const timeframeButtons = [
                    ['season', 'Season'],
                    ['24hr', 'Last 24 Hours'],
                    ['7day', 'Last 7 Days']
                ];
                return `
                    <div class="ntcfg-preview-surface ntcfg-preview-surface--leaderboards">
                        <div class="ntcfg-leader-preview-tabs">
                            <div class="ntcfg-leader-preview-tab${view === 'individual' ? ' is-active' : ''}">Top Racers</div>
                            <div class="ntcfg-leader-preview-tab${view === 'team' ? ' is-active' : ''}">Top Teams</div>
                        </div>
                        <div class="ntcfg-leader-preview-panel">
                            <div class="ntcfg-leader-preview-row">
                                ${timeframeButtons.map(([value, label]) => `<span class="ntcfg-leader-preview-btn${timeframe === value ? ' is-active' : ''}">${escapeHtml(label)}</span>`).join('')}
                                ${showManualRefresh ? '<span class="ntcfg-leader-preview-refresh">Refresh</span>' : ''}
                            </div>
                            <div class="ntcfg-preview-card">
                                <div class="ntcfg-preview-card-title">Sample entry</div>
                                <div class="ntcfg-leader-preview-entry">
                                    ${highlightPositionChange ? '<span class="ntcfg-leader-preview-change">+2</span>' : ''}
                                    <span class="ntcfg-preview-name-text">Captain.Loveridge</span>
                                    <span class="ntcfg-preview-entry-meta">178 WPM</span>
                                </div>
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'leaderboards:sync': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const cacheMinutes = readPreviewNumber(module, fieldMap, 'CACHE_DURATION_MINUTES', 60);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Refresh window</div>
                            <div class="ntcfg-preview-card-copy">Leaderboard responses stay warm for the configured cache window before a fresh fetch is needed.</div>
                            <div class="ntcfg-preview-matrix">
                                ${buildPreviewTileHtml('Cache duration', `${cacheMinutes} min`, true, 'blue')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'leaderboards:presentation': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const antiFlicker = readPreviewToggle(module, fieldMap, 'ANTI_FLICKER_MODE', true);
                const showRouteTab = readPreviewToggle(module, fieldMap, 'SHOW_ROUTE_TAB', true);
                const showDropdownLink = readPreviewToggle(module, fieldMap, 'SHOW_DROPDOWN_LINK', false);
                const hideClassTab = readPreviewToggle(module, fieldMap, 'HIDE_CLASS_TAB', false);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Navigation + page behavior</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Anti-flicker mode', antiFlicker, 'blue')}
                                ${buildPreviewPillHtml('Nav tab', showRouteTab, 'blue')}
                                ${buildPreviewPillHtml('Dropdown link', showDropdownLink, 'gold')}
                                ${buildPreviewPillHtml('Hide class tab', hideClassTab, 'danger')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'leaderboards:advanced': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const debugLogging = readPreviewToggle(module, fieldMap, 'DEBUG_LOGGING', false);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Diagnostics</div>
                            <div class="ntcfg-preview-card-copy">Enable debug logging when checking cache refreshes, season metadata, and SPA route takeover behavior.</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Debug logging', debugLogging, 'danger')}
                            </div>
                        </div>
                    </div>
                `;
            });
        },
        'music-player:source': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const sourceUrl = readPreviewText(module, fieldMap, 'SOURCE_URL', '');
                const defaultPlatform = readPreviewSelect(module, fieldMap, 'DEFAULT_PLATFORM', 'spotify');
                const sessionResume = readPreviewToggle(module, fieldMap, 'SESSION_RESUME', true);
                const autoActivate = readPreviewToggle(module, fieldMap, 'AUTO_ACTIVATE', true);
                const platform = getMusicPreviewPlatform(sourceUrl, defaultPlatform);
                const platformLabel = getMusicPreviewPlatformLabel(platform);
                const sourceType = getMusicPreviewSourceType(sourceUrl);
                const sourceConfigured = !!String(sourceUrl || '').trim();
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-music-preview-card">
                            <div class="ntcfg-music-preview-cover">${escapeHtml(platformLabel.charAt(0))}</div>
                            <div class="ntcfg-music-preview-meta">
                                <div class="ntcfg-music-preview-title">${escapeHtml(sourceConfigured ? `${platformLabel} ${sourceType}` : 'No source configured')}</div>
                                <div class="ntcfg-music-preview-subtitle">${escapeHtml(sourceConfigured ? truncatePreviewText(sourceUrl, 68) : 'Save a Spotify, YouTube, or Apple Music URL to prime the player.')}</div>
                            </div>
                        </div>
                        <div class="ntcfg-preview-pill-row">
                            ${buildPreviewPillHtml(platformLabel, true, 'blue')}
                            ${buildPreviewPillHtml('Session resume', sessionResume, 'gold')}
                            ${buildPreviewPillHtml('Auto activate', autoActivate, 'blue')}
                        </div>
                    </div>
                `;
            });
        },
        'music-player:playback': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const queueMode = readPreviewSelect(module, fieldMap, 'QUEUE_MODE', 'shuffle');
                const showAlbumArt = readPreviewToggle(module, fieldMap, 'SHOW_ALBUM_ART', true);
                const muteNative = readPreviewToggle(module, fieldMap, 'MUTE_NATIVE_STATION', true);
                const tickMs = readPreviewNumber(module, fieldMap, 'PROGRESS_TICK_MS', 1000);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-music-preview-card">
                            ${showAlbumArt ? '<div class="ntcfg-music-preview-cover ntcfg-music-preview-cover--art">NT</div>' : ''}
                            <div class="ntcfg-music-preview-meta">
                                <div class="ntcfg-music-preview-title">Now Playing</div>
                                <div class="ntcfg-music-preview-subtitle">${escapeHtml(queueMode === 'shuffle' ? 'Shuffle queue ready for race start.' : 'Sequential queue ready for race start.')}</div>
                            </div>
                        </div>
                        <div class="ntcfg-music-control-row">
                            <span class="ntcfg-music-control-pill">Prev</span>
                            <span class="ntcfg-music-control-pill">Play</span>
                            <span class="ntcfg-music-control-pill">Next</span>
                            <span class="ntcfg-music-control-pill${queueMode === 'shuffle' ? ' is-active' : ''}">${escapeHtml(queueMode === 'shuffle' ? 'Shuffle' : 'Sequential')}</span>
                        </div>
                        <div class="ntcfg-preview-pill-row">
                            ${buildPreviewPillHtml('Album art', showAlbumArt, 'blue')}
                            ${buildPreviewPillHtml('Mute native station', muteNative, 'danger')}
                            ${buildPreviewPillHtml(`Progress tick ${tickMs} ms`, true, 'gold')}
                        </div>
                    </div>
                `;
            });
        },
        'music-player:advanced': function (fields, refreshPreviewFns, module, fieldMap) {
            mountHtmlStickyPreview(fields, refreshPreviewFns, () => {
                const debugLogging = readPreviewToggle(module, fieldMap, 'DEBUG_LOGGING', false);
                return `
                    <div class="ntcfg-preview-surface">
                        <div class="ntcfg-preview-card">
                            <div class="ntcfg-preview-card-title">Diagnostics</div>
                            <div class="ntcfg-preview-card-copy">Verbose music logs help when checking queue construction, source parsing, and player handoff behavior.</div>
                            <div class="ntcfg-preview-pill-row">
                                ${buildPreviewPillHtml('Debug logging', debugLogging, 'danger')}
                            </div>
                        </div>
                    </div>
                `;
            });
        }
    };

    // ── Generic section mount ──

    // Cache rendered section DOM nodes so tab switches just re-attach instead of rebuilding.
    // Key: "moduleId::sectionId", Value: { root: DOMNode, refreshPreviews: Function }
    const _sectionDomCache = new Map();
    const SECTION_DOM_CACHE_MAX = 12;

    function clearSectionDomCache() {
        _sectionDomCache.clear();
    }

    function mountGenericSection(module, section) {
        const host = document.querySelector('[data-ntmods-generic-stage]');
        if (!host || !module || !section) return;

        const cacheKey = `${module.id}::${section.id}`;
        const cached = _sectionDomCache.get(cacheKey);
        if (cached) {
            // Re-attach cached DOM — instant, no rebuilding
            host.innerHTML = '';
            host.appendChild(cached.root);
            // Refresh previews to pick up any value changes
            if (cached.refreshPreviews) cached.refreshPreviews();
            return;
        }

        const fieldMap = getModuleFieldMap(module);
        const rerender = () => {
            _sectionDomCache.delete(cacheKey);
            mountGenericSection(module, section);
        };
        const refreshPreviewFns = [];
        const refreshPreviews = () => { refreshPreviewFns.forEach((fn) => fn()); };
        const ctx = { module, fieldMap, refreshPreviewFns, refreshPreviews, rerender };

        const sectionRoot = createElement('div', 'ntmods-ro-panel');
        const title = createElement('h2', 'ntcfg-panel-title', section.title);
        const subtitle = createElement('p', 'ntcfg-panel-subtitle', section.subtitle || '');
        const fields = createElement('div', 'ntcfg-fields');
        sectionRoot.append(title, subtitle, fields);
        host.innerHTML = '';
        host.appendChild(sectionRoot);

        // Render preview widget if section declares one
        if (section.preview && typeof section.preview === 'object' && section.preview.type) {
            const renderer = getSectionPreviewRenderer(module, section);
            if (typeof renderer === 'function') {
                renderer(fields, refreshPreviewFns, module, fieldMap);
            }
        }

        const isCompact = section.layout === 'compact';

        // Container for compact layout items
        let compactRoot = null;
        if (isCompact) {
            compactRoot = createElement('div', 'ntcfg-theme-settings');
            fields.appendChild(compactRoot);
        }

        // Track rendered elements for visibleWhen / disabledWhen
        const visibilityMap = new Map();
        const disabledMap = new Map();
        const renderedElements = new Map();

        const items = Array.isArray(section.items) ? section.items : [];
        let currentSubSection = null;

        items.forEach((item) => {
            // Render section sub-headers when the section property changes
            if (item.section && item.section !== currentSubSection) {
                currentSubSection = item.section;
                const subHeader = createElement('h3', 'ntcfg-subsection-title', item.section);
                subHeader.style.cssText = 'font-size:14px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:#8e9aaf;margin:20px 0 8px;padding-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.06);';
                if (currentSubSection !== items[0]?.section) {
                    subHeader.style.marginTop = '28px';
                }
                (isCompact ? compactRoot : fields).appendChild(subHeader);
            }
            if (item.type === 'note') {
                const noteEl = createElement('div', `ntmods-note ntmods-note--${item.tone || 'info'}`, item.message || '');
                (isCompact ? compactRoot : fields).appendChild(noteEl);
                return;
            }

            const targetRoot = isCompact ? compactRoot : fields;

            // Handle compound items (toggle with inline child controls)
            if (item.compound && Array.isArray(item.compound) && item.compound.length) {
                renderCompoundRow(targetRoot, item, ctx);
                return;
            }

            let rendered = null;

            if (isCompact) {
                // Compact layout uses ntcfg-theme-setting rows
                if (item.type === 'toggle') {
                    rendered = genericAppendThemeSettingToggle(targetRoot, item.label, item.key, item.default, ctx);
                } else if (item.type === 'select') {
                    rendered = genericAppendThemeSettingSelect(targetRoot, item.label, item.key, item.default, item.options || [], ctx);
                } else if (item.type === 'number') {
                    const numConfig = {
                        min: item.min,
                        max: item.max,
                        step: item.step,
                        presets: item.presets || [],
                        normalizeValue: item.normalize || undefined
                    };
                    rendered = genericAppendThemeSettingNumber(targetRoot, item.label, item.key, item.default, numConfig, ctx);
                } else if (item.type === 'color') {
                    // Render as a compact theme color row (toggle always on)
                    const row = createElement('div', 'ntcfg-theme-setting');
                    row.appendChild(createElement('div', 'ntcfg-theme-setting-title', item.label));
                    const controls = createElement('div', 'ntcfg-theme-setting-controls');
                    const colorControl = createCompactColorControl(
                        readModuleFieldValue(module, fieldMap, item.key, { default: item.default, type: 'color' }),
                        (value) => {
                            writeModuleFieldValue(module, fieldMap, item.key, value, { default: item.default, type: 'color' });
                            refreshPreviews();
                        }
                    );
                    controls.appendChild(colorControl.root);
                    row.appendChild(controls);
                    targetRoot.appendChild(row);
                    rendered = { row };
                }
            } else {
                // Standard layout
                if (item.type === 'toggle') {
                    rendered = genericAppendCheckbox(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default,
                        helpText: item.help || ''
                    }, ctx);
                } else if (item.type === 'number') {
                    const warnOpts = item.warn || {};
                    rendered = genericAppendNumberField(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default,
                        helpText: item.help || '',
                        min: item.min,
                        max: item.max,
                        step: item.step || '1',
                        minRecommended: warnOpts.below != null ? warnOpts.below : null,
                        warningText: warnOpts.message || ''
                    }, ctx);
                } else if (item.type === 'select') {
                    rendered = genericAppendSelectField(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default,
                        options: item.options || [],
                        helpText: item.help || ''
                    }, ctx);
                } else if (item.type === 'color') {
                    rendered = genericAppendCheckbox(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default,
                        helpText: item.help || ''
                    }, ctx);
                } else if (item.type === 'text') {
                    rendered = genericAppendTextField(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default || '',
                        helpText: item.help || '',
                        placeholder: item.placeholder || ''
                    }, ctx);
                } else if (item.type === 'keymap') {
                    rendered = genericAppendKeymapField(targetRoot, {
                        labelText: item.label,
                        key: item.key,
                        defaultValue: item.default || [],
                        helpText: item.help || ''
                    }, ctx);
                } else if (item.type === 'action') {
                    const btn = createElement('button', `ntcfg-action ntcfg-inline-action ntcfg-action--${item.style || 'secondary'}`, item.label);
                    btn.type = 'button';
                    btn.addEventListener('click', () => {
                        document.dispatchEvent(new CustomEvent('ntcfg:action', {
                            detail: { script: module.id, key: item.key }
                        }));
                    });
                    targetRoot.appendChild(btn);
                    rendered = { row: btn };
                }
            }

            // Store for visibility tracking
            if (item.key && rendered) {
                const el = rendered.row || rendered.field || null;
                if (el) {
                    renderedElements.set(item.key, el);
                }
            }

            // Handle visibleWhen
            if (item.visibleWhen && item.key && rendered) {
                const el = rendered.row || rendered.field || null;
                if (el) {
                    visibilityMap.set(item.key, { element: el, condition: item.visibleWhen });
                }
            }

            // Handle disabledWhen
            if (item.disabledWhen && item.key && rendered) {
                const el = rendered.row || rendered.field || null;
                if (el) {
                    disabledMap.set(item.key, { element: el, condition: item.disabledWhen, toggleKey: item.key });
                }
            }
        });

        // Helper to evaluate a condition against a field value
        const evalCondition = (val, condition) =>
            condition.eq != null ? val === condition.eq : condition.neq != null ? val !== condition.neq : !!val;

        // Apply/refresh disabledWhen state
        const applyDisabledState = () => {
            disabledMap.forEach(({ element, condition, toggleKey }) => {
                const depItem = fieldMap.get(condition.key);
                if (!depItem) return;
                const val = readModuleFieldValue(module, fieldMap, condition.key, { default: depItem.default, type: depItem.type });
                const shouldDisable = evalCondition(val, condition);
                element.style.opacity = shouldDisable ? '0.4' : '';
                element.style.pointerEvents = shouldDisable ? 'none' : '';

                // When disabled (master is on), visually force the toggle on
                if (shouldDisable) {
                    const toggle = element.querySelector('.ntcfg-toggle-track, input[type="checkbox"]');
                    if (toggle && toggle.classList.contains('ntcfg-toggle-track') && !toggle.classList.contains('is-on')) {
                        toggle.classList.add('is-on');
                    }
                } else {
                    // Restore actual saved state
                    const toggle = element.querySelector('.ntcfg-toggle-track');
                    if (toggle) {
                        const savedVal = readModuleFieldValue(module, fieldMap, toggleKey, { default: fieldMap.get(toggleKey)?.default, type: 'toggle' });
                        toggle.classList.toggle('is-on', !!savedVal);
                    }
                }
            });
        };
        applyDisabledState();

        // Apply initial visibleWhen state and wire onChange cascades
        // Note: use style.display instead of element.hidden because author CSS
        // (.ntcfg-field, .ntcfg-checkbox) overrides the UA [hidden] rule.
        visibilityMap.forEach(({ element, condition }) => {
            const depItem = fieldMap.get(condition.key);
            if (!depItem) return;
            const val = readModuleFieldValue(module, fieldMap, condition.key, { default: depItem.default, type: depItem.type });
            element.style.display = evalCondition(val, condition) ? '' : 'none';
        });

        // Wire up onChange cascades: find items that other items depend on
        const depKeys = new Set();
        visibilityMap.forEach(({ condition }) => depKeys.add(condition.key));

        // For each dependency key, we need to re-check visibility on change
        // This is already handled via rerender on toggle changes, but we need to also
        // hook into the initial item rendering. Since all toggle changes trigger refreshPreviews,
        // we hook into refreshPreviews to also update visibility.
        const originalRefreshPreviews = refreshPreviews;
        const augmentedRefreshPreviews = () => {
            originalRefreshPreviews();
            visibilityMap.forEach(({ element, condition }) => {
                const depItem = fieldMap.get(condition.key);
                if (!depItem) return;
                const val = readModuleFieldValue(module, fieldMap, condition.key, { default: depItem.default, type: depItem.type });
                element.style.display = evalCondition(val, condition) ? '' : 'none';
            });
            applyDisabledState();
        };
        // Patch the ctx refresh so all child controls use the augmented version
        ctx.refreshPreviews = augmentedRefreshPreviews;
        refreshPreviewFns.push(() => {
            visibilityMap.forEach(({ element, condition }) => {
                const depItem = fieldMap.get(condition.key);
                if (!depItem) return;
                const val = readModuleFieldValue(module, fieldMap, condition.key, { default: depItem.default, type: depItem.type });
                element.style.display = evalCondition(val, condition) ? '' : 'none';
            });
            applyDisabledState();
        });

        // Render reset button
        if (section.resetButton) {
            const label = typeof section.resetButton === 'string'
                ? section.resetButton
                : `Reset ${section.title} to Defaults`;
            // Compute defaults from item metadata
            const defaults = {};
            items.forEach((item) => {
                if (item.key && item.default !== undefined) {
                    defaults[item.key] = item.default;
                }
                // Also collect compound child defaults
                if (item.compound && Array.isArray(item.compound)) {
                    item.compound.forEach((child) => {
                        if (child.key && child.default !== undefined) {
                            defaults[child.key] = child.default;
                        }
                    });
                }
            });
            genericAppendResetButton(fields, label, defaults, ctx);
        }

        refreshPreviews();

        // Store in DOM cache for instant re-attach on future tab switches
        if (_sectionDomCache.size >= SECTION_DOM_CACHE_MAX) {
            // Evict oldest entry
            const firstKey = _sectionDomCache.keys().next().value;
            _sectionDomCache.delete(firstKey);
        }
        _sectionDomCache.set(cacheKey, { root: sectionRoot, refreshPreviews });
    }

    function renderCompoundRow(root, item, ctx) {
        const { module, fieldMap, refreshPreviews } = ctx;
        const isCompactLayout = root.classList.contains('ntcfg-theme-settings');

        if (isCompactLayout) {
            // Compound in compact layout: ntcfg-theme-setting row with inline controls
            const row = createElement('div', 'ntcfg-theme-setting');
            row.appendChild(createElement('div', 'ntcfg-theme-setting-title', item.label));
            const controls = createElement('div', 'ntcfg-theme-setting-controls');

            item.compound.forEach((child) => {
                if (child.type === 'color') {
                    const colorControl = createCompactColorControl(
                        readModuleFieldValue(module, fieldMap, child.key, { default: child.default, type: 'color' }),
                        (value) => {
                            writeModuleFieldValue(module, fieldMap, child.key, value, { default: child.default, type: 'color' });
                            refreshPreviews();
                        }
                    );
                    controls.appendChild(colorControl.root);
                } else if (child.type === 'select') {
                    const safeOptions = Array.isArray(child.options) ? child.options.filter(Boolean) : [];
                    const select = createElement('select', 'ntcfg-input ntcfg-theme-input-compact ntcfg-theme-input-select');
                    const isDarkModeSyncSelect = child.key === 'THEME_DARK_MODE_SYNC_SYSTEM';
                    safeOptions.forEach((optionDef) => {
                        const option = document.createElement('option');
                        option.value = String(optionDef.value);
                        option.textContent = String(optionDef.label ?? optionDef.value);
                        select.appendChild(option);
                    });
                    const normalizeSelectedValue = (value) => {
                        if (isDarkModeSyncSelect) {
                            return normalizeRaceOptionsDarkModeSyncSelectionValue(value, 'always');
                        }
                        const raw = String(value ?? '').trim();
                        const matched = safeOptions.find((option) => String(option.value) === raw);
                        if (matched) return matched.value;
                        const fallback = safeOptions.find((option) => String(option.value) === String(child.default).trim());
                        return fallback ? fallback.value : (safeOptions[0]?.value ?? child.default);
                    };
                    select.value = String(normalizeSelectedValue(readModuleFieldValue(module, fieldMap, child.key, { default: child.default, type: 'select', options: child.options })));
                    select.addEventListener('change', () => {
                        const normalized = normalizeSelectedValue(select.value);
                        select.value = String(normalized);
                        const storedValue = isDarkModeSyncSelect
                            ? normalizeRaceOptionsDarkModeSyncStoredValue(normalized)
                            : normalized;
                        writeModuleFieldValue(module, fieldMap, child.key, storedValue, { default: child.default, type: 'select', options: child.options });
                        if (child.onChange) child.onChange(normalized, row, select);
                        refreshPreviews();
                    });
                    controls.appendChild(select);
                } else if (child.type === 'toggle') {
                    const switchControl = createSwitch(
                        readModuleFieldValue(module, fieldMap, child.key, { default: child.default, type: 'toggle' }),
                        (checked) => {
                            writeModuleFieldValue(module, fieldMap, child.key, checked, { default: child.default, type: 'toggle' });
                            if (child.onChange) child.onChange(checked, row);
                            refreshPreviews();
                        }
                    );
                    controls.appendChild(switchControl.root);
                }
            });

            // The main item itself is the primary toggle
            if (item.type === 'toggle') {
                const mainSwitch = createSwitch(
                    readModuleFieldValue(module, fieldMap, item.key, { default: item.default, type: 'toggle' }),
                    (checked) => {
                        writeModuleFieldValue(module, fieldMap, item.key, checked, { default: item.default, type: 'toggle' });
                        refreshPreviews();
                    }
                );
                controls.appendChild(mainSwitch.root);
            }

            row.appendChild(controls);
            root.appendChild(row);
        } else {
            // Compound in standard layout: ntcfg-checkbox row with inline controls
            const row = createElement('div', 'ntcfg-checkbox');
            const copy = createElement('span', 'ntcfg-checkbox-copy');
            copy.append(
                createElement('span', 'ntcfg-checkbox-label', item.label),
                createElement('span', 'ntcfg-checkbox-help', item.help || '')
            );
            const controls = createElement('span', 'ntcfg-checkbox-controls');

            item.compound.forEach((child) => {
                if (child.type === 'color') {
                    const colorControl = createCompactColorControl(
                        readModuleFieldValue(module, fieldMap, child.key, { default: child.default, type: 'color' }),
                        (value) => {
                            writeModuleFieldValue(module, fieldMap, child.key, value, { default: child.default, type: 'color' });
                            refreshPreviews();
                        },
                        'ntcfg-checkbox-controls'
                    );
                    controls.appendChild(colorControl.root);
                } else if (child.type === 'select') {
                    const safeOptions = Array.isArray(child.options) ? child.options.filter(Boolean) : [];
                    const select = createElement('select', 'ntcfg-input ntcfg-theme-input-compact');
                    const isDarkModeSyncSelect = child.key === 'THEME_DARK_MODE_SYNC_SYSTEM';
                    safeOptions.forEach((optionDef) => {
                        const option = document.createElement('option');
                        option.value = String(optionDef.value);
                        option.textContent = String(optionDef.label ?? optionDef.value);
                        select.appendChild(option);
                    });
                    const normalizeSelectedValue = (value) => {
                        if (isDarkModeSyncSelect) {
                            return normalizeRaceOptionsDarkModeSyncSelectionValue(value, 'always');
                        }
                        const raw = String(value ?? '').trim();
                        const matched = safeOptions.find((option) => String(option.value) === raw);
                        if (matched) return matched.value;
                        const fallback = safeOptions.find((option) => String(option.value) === String(child.default).trim());
                        return fallback ? fallback.value : (safeOptions[0]?.value ?? child.default);
                    };
                    select.value = String(normalizeSelectedValue(readModuleFieldValue(module, fieldMap, child.key, { default: child.default, type: 'select', options: child.options })));
                    select.addEventListener('change', () => {
                        const normalized = normalizeSelectedValue(select.value);
                        select.value = String(normalized);
                        const storedValue = isDarkModeSyncSelect
                            ? normalizeRaceOptionsDarkModeSyncStoredValue(normalized)
                            : normalized;
                        writeModuleFieldValue(module, fieldMap, child.key, storedValue, { default: child.default, type: 'select', options: child.options });
                        refreshPreviews();
                    });
                    controls.appendChild(select);
                }
            });

            // Main toggle
            if (item.type === 'toggle') {
                const mainSwitch = createSwitch(
                    readModuleFieldValue(module, fieldMap, item.key, { default: item.default, type: 'toggle' }),
                    (checked) => {
                        writeModuleFieldValue(module, fieldMap, item.key, checked, { default: item.default, type: 'toggle' });
                        // Hide/show compound children based on toggle state
                        item.compound.forEach((child) => {
                            // Find the child control in controls and toggle visibility
                        });
                        refreshPreviews();
                    }
                );
                controls.appendChild(mainSwitch.root);
            }

            row.append(copy, controls);
            root.appendChild(row);
        }
    }

    function buildModuleCountPill(value, label) {
        return `
            <div class="ntmods-cap-stat">
                <span class="ntmods-cap-stat-value">${escapeHtml(value)}</span>
                <span class="ntmods-cap-stat-label">${escapeHtml(label)}</span>
            </div>
        `;
    }

    function getTopTabGrow(module) {
        const label = String(module?.label || '');
        return Math.max(1, Math.min(2.15, (label.length + 4) / 7.5));
    }

    function getModuleIconMarkup(module, variant = 'tab') {
        const iconClass = variant === 'chip'
            ? 'ntmods-mod-icon ntmods-mod-icon--chip'
            : 'ntmods-mod-icon ntmods-mod-icon--tab';

        // Allow script authors to set custom icons via manifest.icon (SVG path content)
        if (module?.icon) {
            return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                    ${module.icon}
                </svg>
            `;
        }

        switch (module?.id) {
            case 'race-options':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M4 6h16M4 12h16M4 18h16"></path>
                    <circle cx="9" cy="6" r="2"></circle>
                    <circle cx="15" cy="12" r="2"></circle>
                    <circle cx="11" cy="18" r="2"></circle>
                </svg>
            `;
            case 'music-player':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M10 18.5a2.5 2.5 0 1 1-2.5-2.5A2.5 2.5 0 0 1 10 18.5Zm9-2a2.5 2.5 0 1 1-2.5-2.5A2.5 2.5 0 0 1 19 16.5Z"></path>
                    <path d="M10 18.5V7.5l9-2v11"></path>
                </svg>
            `;
            case 'bot-flag':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M6 20V4"></path>
                    <path d="M6 5h11l-2.5 3 2.5 3H6"></path>
                </svg>
            `;
            case 'bot-hunter':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="4.25"></circle>
                    <circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none"></circle>
                    <path d="M12 3.5V6M12 18V20.5M3.5 12H6M18 12h2.5"></path>
                </svg>
            `;
            case 'leaderboards':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M4 20V11M10 20V6M16 20v-8M3 20h18"></path>
                </svg>
            `;
            case 'racer-badges':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="8.5" r="4.25"></circle>
                    <path d="M9.5 12.5 7.2 20l4.8-2.5 4.8 2.5-2.3-7.5"></path>
                </svg>
            `;
            case 'enhanced-stats':
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                    <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
                </svg>
            `;
            case 'toolkit':
                return `
                <svg class="${iconClass}" viewBox="0 0 200 210" aria-hidden="true" fill="currentColor" stroke="none">
                    <g transform="translate(0,210) scale(0.1,-0.1)">
                        <path d="M850 1581c-65-21-121-60-189-132-48-51-75-88-77-105-4-35-41-72-79-78-27-5-85-55-85-74 0-4 29-38 64-77l64-70 36 34c20 19 36 43 36 53 0 32 27 73 56 86 24 11 31 10 64-13 21-15 44-25 52-23 9 2 145-140 327-339 172-189 320-343 329-343 23 0 102 80 102 105 0 13-110 130-325 345-187 187-325 333-325 343 0 10-18 44-40 77l-39 58 34 27c59 44 101 57 179 53 41-2 73 1 75 7 2 6-22 23-54 40-60 31-154 43-205 26z"/>
                        <path d="M1335 1576c-62-28-115-104-115-164 0-45-45-124-99-177-28-27-51-55-51-61 0-7 25-35 57-63l56-50 61 56c66 61 116 87 188 97 54 8 120 52 143 96 18 35 32 120 19 120-5 0-36-27-69-60l-61-60-54 18c-51 18-54 21-72 72l-18 54 60 61c33 33 60 64 60 68 0 12-73 7-105-7z"/>
                        <path d="M820 934c-77-78-133-116-185-129-79-18-125-76-125-157 0-76 52-135 128-145 85-11 145 32 176 128 22 67 42 95 129 186l69 72-53 60c-29 34-56 61-59 61-3 0-39-34-80-76z"/>
                        <path d="M723 711c43-42 10-133-49-139-37-3-75 17-90 47-15 30-2 80 27 102 28 21 86 16 112-10z"/>
                    </g>
                </svg>
            `;
            default:
                return `
                <svg class="${iconClass}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M4 6h16M4 12h16M4 18h16"></path>
                    <circle cx="9" cy="6" r="2"></circle>
                    <circle cx="15" cy="12" r="2"></circle>
                    <circle cx="11" cy="18" r="2"></circle>
                </svg>
            `;
        }
    }

    function buildTopTabHtml(module, isActive) {
        const isOutdated = module.source === 'outdated';
        const isPreview = module.source === 'preview';
        const tabClass = `tab ntmods-top-tab${isActive ? ' is-active' : ''}${isPreview ? ' is-preview' : ''}${isOutdated ? ' is-outdated' : ''}`;
        const updateDot = (module._hasUpdate || isOutdated) ? '<span class="ntmods-update-dot" title="Update available"></span>' : '';
        return `
            <button
                type="button"
                class="${tabClass}"
                data-module-id="${escapeHtml(module.id)}"
                style="--ntmods-tab-grow:${getTopTabGrow(module)}"
            >
                <div class="bucket bucket--c bucket--xs">
                    <div class="bucket-media">
                        <span class="ntmods-top-tab-glyph">${getModuleIconMarkup(module, 'tab')}</span>
                    </div>
                    <div class="bucket-content">${escapeHtml(module.label)}${updateDot}</div>
                </div>
            </button>
        `;
    }

    function buildSidebarButtonHtml(section, isActive) {
        return `
            <button type="button" class="ntmods-nav-btn${isActive ? ' is-active' : ''}" data-section-id="${escapeHtml(section.id)}">
                <span class="ntmods-nav-btn-title">${escapeHtml(section.title)}</span>
            </button>
        `;
    }

    function buildToggleHtml(module, item) {
        const value = Boolean(getFieldValue(module, item));
        return `
            <label class="ntmods-toggle-card">
                <span class="ntmods-toggle-copy">
                    <span class="ntmods-field-title">${escapeHtml(item.label)}</span>
                    <span class="ntmods-field-help">${escapeHtml(item.help || '')}</span>
                </span>
                <span class="ntmods-switch">
                    <input type="checkbox" data-field-key="${escapeHtml(item.key)}" ${value ? 'checked' : ''}>
                    <span class="ntmods-switch-track"></span>
                </span>
            </label>
        `;
    }

    function buildSelectOptions(item, value) {
        return (item.options || []).map((option) => {
            const selected = String(option.value) === String(value) ? 'selected' : '';
            return `<option value="${escapeHtml(option.value)}" ${selected}>${escapeHtml(option.label)}</option>`;
        }).join('');
    }

    function buildFieldHtml(module, item) {
        const value = getFieldValue(module, item);

        if (item.type === 'toggle') {
            return buildToggleHtml(module, item);
        }

        if (item.type === 'note') {
            return `<div class="ntmods-note ntmods-note--${escapeHtml(item.tone || 'info')}">${escapeHtml(item.message || '')}</div>`;
        }

        if (item.type === 'action') {
            return `
                <div class="ntmods-action-card">
                    <button type="button" class="ntmods-action-btn ntmods-action-btn--${escapeHtml(item.style || 'secondary')}" data-action-key="${escapeHtml(item.key)}">
                        ${escapeHtml(item.label)}
                    </button>
                    ${item.help ? `<div class="ntmods-field-help">${escapeHtml(item.help)}</div>` : ''}
                </div>
            `;
        }

        if (item.type === 'color') {
            const normalized = String(value || item.default || '#1C99F4');
            return `
                <label class="ntmods-field-card">
                    <span class="ntmods-field-title">${escapeHtml(item.label)}</span>
                    <span class="ntmods-color-row">
                        <input type="color" class="ntmods-color-picker" value="${escapeHtml(normalized)}" data-field-key="${escapeHtml(item.key)}" data-field-type="color">
                        <input type="text" class="ntmods-input ntmods-color-input" value="${escapeHtml(normalized)}" data-field-key="${escapeHtml(item.key)}" data-field-type="color-text" spellcheck="false">
                    </span>
                    ${item.help ? `<span class="ntmods-field-help">${escapeHtml(item.help)}</span>` : ''}
                </label>
            `;
        }

        if (item.type === 'select') {
            return `
                <label class="ntmods-field-card">
                    <span class="ntmods-field-title">${escapeHtml(item.label)}</span>
                    <select class="ntmods-input" data-field-key="${escapeHtml(item.key)}" data-field-type="select">
                        ${buildSelectOptions(item, value)}
                    </select>
                    ${item.help ? `<span class="ntmods-field-help">${escapeHtml(item.help)}</span>` : ''}
                </label>
            `;
        }

        if (item.type === 'number') {
            const minAttr = item.min == null ? '' : `min="${escapeHtml(item.min)}"`;
            const maxAttr = item.max == null ? '' : `max="${escapeHtml(item.max)}"`;
            const stepAttr = item.step == null ? '' : `step="${escapeHtml(item.step)}"`;
            return `
                <label class="ntmods-field-card">
                    <span class="ntmods-field-title">${escapeHtml(item.label)}</span>
                    <input type="number" class="ntmods-input" value="${escapeHtml(value)}" data-field-key="${escapeHtml(item.key)}" data-field-type="number" ${minAttr} ${maxAttr} ${stepAttr}>
                    ${item.help ? `<span class="ntmods-field-help">${escapeHtml(item.help)}</span>` : ''}
                </label>
            `;
        }

        return `
            <label class="ntmods-field-card">
                <span class="ntmods-field-title">${escapeHtml(item.label)}</span>
                <input type="text" class="ntmods-input" value="${escapeHtml(value ?? '')}" data-field-key="${escapeHtml(item.key)}" data-field-type="text" placeholder="${escapeHtml(item.placeholder || '')}" spellcheck="false">
                ${item.help ? `<span class="ntmods-field-help">${escapeHtml(item.help)}</span>` : ''}
            </label>
        `;
    }

    function buildSectionHtml(module, section) {
        // Manifest modules use the generic mount (DOM rendered after innerHTML)
        if (module?.source === 'manifest') {
            return `<div class="ntmods-generic-stage" data-ntmods-generic-stage="1"></div>`;
        }

        // Preview (non-manifest) modules use the card layout fallback
        const settingItems = section.items.filter((item) => item.type !== 'note');
        const toggleCount = settingItems.filter((item) => item.type === 'toggle').length;
        const inputCount = settingItems.length - toggleCount;
        const modeLabel = 'Preview Layout';

        return `
            <div class="ntmods-stage-intro">
                <div class="ntmods-stage-intro-copy">
                    <h2 class="ntmods-stage-title">${escapeHtml(section.title)}</h2>
                    <p class="ntmods-stage-subtitle">${escapeHtml(section.subtitle || '')}</p>
                </div>
                <div class="ntmods-stage-pills">
                    <span class="ntmods-pill">${escapeHtml(modeLabel)}</span>
                    <span class="ntmods-pill">${escapeHtml(toggleCount)} toggles</span>
                    <span class="ntmods-pill">${escapeHtml(inputCount)} inputs</span>
                </div>
            </div>
            <div class="ntmods-grid">
                ${section.items.map((item) => buildFieldHtml(module, item)).join('')}
            </div>
        `;
    }

    // Build only the inner content of .ntmods-module-card for a given active module/section.
    function buildModuleCardInner(activeModule, activeSection) {
        const activeModuleSections = getRenderableSections(activeModule);
        return `
            <div class="ntmods-module-head">
                <div class="ntmods-module-head-copy">
                    <div class="ntmods-module-overline">${
                        activeModule.source === 'manifest'
                            ? `Connected Module${activeModule.manifestVersion ? ' &middot; v' + escapeHtml(activeModule.manifestVersion) : ''}`
                            : activeModule.source === 'outdated'
                            ? 'Update Required'
                            : 'Not Installed'
                    }</div>
                    <h2 class="ntmods-module-title">${escapeHtml(activeModule.label)}</h2>
                    <p class="ntmods-module-description">${escapeHtml(activeModule.description || '')}</p>
                </div>
                <div class="ntmods-module-actions">
                    <button type="button" class="ntmods-rescan-btn" data-action-key="rescan">Check for Updates</button>
                    <button type="button" class="ntmods-rescan-btn ntmods-clear-btn" data-action-key="clear-settings">Clear Settings</button>
                </div>
            </div>
            ${activeModule._hasUpdate && activeModule.manifestVersion && MODULE_META[activeModule.id]?.installUrl ? `
                <div class="ntmods-update-banner">
                    <div class="ntmods-update-banner-text">
                        <strong>Update Detected</strong>
                        <span class="ntmods-version-arrow">
                            <span class="ntmods-version-current">v${escapeHtml(activeModule.manifestVersion)}</span>
                            <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
                            <span class="ntmods-version-latest">v${escapeHtml(activeModule._latestVersion)}</span>
                        </span>
                    </div>
                    <a href="${escapeHtml(MODULE_META[activeModule.id].installUrl)}" target="_blank" rel="noopener noreferrer" class="ntmods-update-banner-link">
                        Update on Greasyfork &rarr;
                    </a>
                </div>
            ` : ''}
            ${activeModule.source === 'outdated' ? `
                <div class="ntmods-not-installed ntmods-outdated-card">
                    <div class="ntmods-not-installed-icon">
                        ${getModuleIconMarkup(activeModule, 'large')}
                    </div>
                    <h3 class="ntmods-not-installed-title ntmods-outdated-title">Update Required</h3>
                    <p class="ntmods-not-installed-copy"><strong>${escapeHtml(activeModule.label)}</strong> needs to be updated to the latest version to work with the Mod Menu.</p>
                    ${MODULE_META[activeModule.id]?.installUrl ? `
                        <a href="${escapeHtml(MODULE_META[activeModule.id].installUrl)}" target="_blank" rel="noopener noreferrer" class="ntmods-install-link ntmods-update-link">
                            Reinstall ${escapeHtml(activeModule.label)} on Greasyfork &rarr;
                        </a>
                    ` : ''}
                    <p class="ntmods-not-installed-copy" style="margin-top:12px;opacity:0.6;">Uninstalled this mod?</p>
                    <button type="button" class="ntmods-dismiss-stale-btn" data-dismiss-stale="${escapeHtml(activeModule.id)}">Remove from Mod Menu</button>
                </div>
            ` : activeModule.source === 'preview' ? `
                <div class="ntmods-not-installed">
                    <div class="ntmods-not-installed-icon">
                        ${getModuleIconMarkup(activeModule, 'large')}
                    </div>
                    <h3 class="ntmods-not-installed-title">Not Installed</h3>
                    <p class="ntmods-not-installed-copy">You don't have <strong>${escapeHtml(activeModule.label)}</strong> installed yet. Install it from Greasyfork to unlock these settings.</p>
                    ${MODULE_META[activeModule.id]?.installUrl ? `
                        <a href="${escapeHtml(MODULE_META[activeModule.id].installUrl)}" target="_blank" rel="noopener noreferrer" class="ntmods-install-link">
                            Install ${escapeHtml(activeModule.label)} on Greasyfork &rarr;
                        </a>
                    ` : ''}
                </div>
            ` : `
                <div class="ntmods-layout">
                    <aside class="ntmods-sidebar">
                        ${activeModuleSections.map((section) => buildSidebarButtonHtml(section, section.id === activeSection.id)).join('')}
                    </aside>
                    <section class="ntmods-stage">
                        ${buildSectionHtml(activeModule, activeSection)}
                    </section>
                </div>
            `}
        `;
    }

    function buildPageHtml(modules, activeModuleId, activeSectionId) {
        const activeModule = modules.find((module) => module.id === activeModuleId) || modules[0] || null;
        const activeModuleSections = getRenderableSections(activeModule);
        const activeSection = activeModule
            ? activeModuleSections.find((section) => section.id === activeSectionId) || activeModuleSections[0]
            : null;


        if (!activeModule || !activeSection) {
            return `
                <section id="ntmods-app" class="card card--b card--o card--shadow card--f card--grit well well--b well--l ntmods-shell">
                    <div class="card-cap bg--gradient ntmods-cap">
                        <h1 class="h2 tbs ntmods-cap-title">Mod Menu</h1>
                    </div>
                    <div class="well--p well--l_p">
                        <div class="ntmods-empty">No installed mods were detected yet.</div>
                    </div>
                </section>
            `;
        }

        return `
            <section id="ntmods-app" class="card card--b card--o card--shadow card--f card--grit well well--b well--l ntmods-shell">
                <div class="card-cap bg--gradient ntmods-cap">
                    <h1 class="h2 tbs ntmods-cap-title">Mod Menu</h1>
                    <button type="button" class="ntmods-reorder-btn" data-action-key="toggle-reorder">
                        <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>
                        Reorder Tabs
                    </button>
                </div>
                <div class="well--p well--l_p">
                    <div class="tabs tabs--a ntmods-top-tabs">
                        ${modules.map((module) => buildTopTabHtml(module, module.id === activeModule.id)).join('')}
                    </div>
                    <div class="ntmods-module-card">
                        ${buildModuleCardInner(activeModule, activeSection)}
                    </div>
                </div>
            </section>
        `;
    }

    function showToast(message) {
        let toast = document.getElementById('ntmods-toast');
        if (!toast) {
            toast = document.createElement('div');
            toast.id = 'ntmods-toast';
            toast.className = 'ntmods-toast';
            document.body.appendChild(toast);
        }
        toast.textContent = message;
        toast.classList.add('is-visible');
        window.clearTimeout(showToast._timer);
        showToast._timer = window.setTimeout(() => {
            toast.classList.remove('is-visible');
        }, 1800);
    }

    function clearModMenuOwnedStorage() {
        removePersistentJson(UI_STATE_KEY);
        removePersistentJson(TAB_ORDER_KEY);
        try { localStorage.removeItem(VERSION_CACHE_KEY); } catch { /* ignore */ }
        try {
            Object.keys(localStorage).forEach((key) => {
                if (key.startsWith(PREVIEW_VALUE_PREFIX)) {
                    localStorage.removeItem(key);
                }
            });
        } catch { /* ignore */ }
    }

    function createGlobalResetRequestId() {
        return `ntcfg-reset-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    }

    function finalizeGlobalReset(timedOut = false) {
        if (!globalResetState) return;
        if (globalResetState.timeoutId) {
            clearTimeout(globalResetState.timeoutId);
        }
        const responded = globalResetState.responded.size;
        const expected = globalResetState.expected.size;
        globalResetState = null;
        _versionCheckDone = false;
        invalidateModuleCache();
        renderPage('', '', true);
        showToast(timedOut
            ? `Clear finished with partial responses (${responded}/${expected}).`
            : 'Settings and caches cleared.');
    }

    function beginGlobalReset(modules) {
        if (globalResetState) {
            showToast('Clear already in progress...');
            return;
        }
        const expected = new Set(
            modules
                .filter((module) => module.source === 'manifest' && module.manifest?.supportsGlobalReset !== false)
                .map((module) => module.id)
        );
        const requestId = createGlobalResetRequestId();
        globalResetState = {
            requestId,
            expected,
            responded: new Set(),
            timeoutId: window.setTimeout(() => finalizeGlobalReset(true), 4000)
        };

        clearModMenuOwnedStorage();

        document.dispatchEvent(new CustomEvent('ntcfg:action', {
            detail: {
                script: '*',
                key: 'clear-settings',
                scope: 'prefs+caches',
                requestId
            }
        }));

        if (expected.size === 0) {
            finalizeGlobalReset(false);
            return;
        }

        showToast(`Clearing settings for ${expected.size} installed mods...`);
    }

    document.addEventListener('ntcfg:action-result', (event) => {
        const detail = event?.detail || {};
        if (!globalResetState || detail.requestId !== globalResetState.requestId) return;
        if (!globalResetState.expected.has(detail.script)) return;
        globalResetState.responded.add(detail.script);
        if (globalResetState.responded.size >= globalResetState.expected.size) {
            finalizeGlobalReset(false);
        }
    });

    function ensureStyles() {
        if (document.getElementById('ntmods-style')) return;
        const style = document.createElement('style');
        style.id = 'ntmods-style';
        style.textContent = `
.ntmods-dropdown-item.is-current .dropdown-link {
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
}
.ntmods-dropdown-item .dropdown-link .icon {
    opacity: 0.92;
}
.ntmods-dropdown-item .dropdown-link .ntmods-inline-icon {
    opacity: 1;
    color: #9fb2c6 !important;
    fill: currentColor !important;
}
.ntmods-dropdown-item .dropdown-link:hover .ntmods-inline-icon,
.ntmods-dropdown-item.is-current .dropdown-link .ntmods-inline-icon {
    color: #c8d5e2 !important;
    fill: currentColor !important;
}
.ntmods-inline-icon {
    width: 16px;
    height: 16px;
    display: inline-block;
    vertical-align: -3px;
    shape-rendering: geometricPrecision;
}
.ntmods-shell {
    color: #eef3ff;
    display: flex;
    flex-direction: column;
}
.ntmods-shell > .well--p.well--l_p {
    padding-bottom: 0 !important;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}
.ntmods-cap {
    display: flex !important;
    flex-direction: row !important;
    justify-content: flex-start !important;
    gap: 0;
    align-items: center !important;
    padding: 12px 18px;
    background: url(/dist/site/images/backgrounds/bg-noise.png) top left repeat, linear-gradient(90deg, #1c99f4 0%, #167ac3 42%, #0f4f86 100%);
    background-attachment: fixed, scroll;
}
.ntmods-cap-copy {
    min-width: 0;
    max-width: 720px;
}
.ntmods-cap-kicker {
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 11px;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.82);
}
.ntmods-cap-title {
    margin: 0;
    text-align: left !important;
}
.ntmods-cap-subtitle {
    margin: 0;
    font-size: 12px;
    line-height: 1.45;
    color: rgba(255, 255, 255, 0.92);
}
.ntmods-cap-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(96px, 1fr));
    gap: 10px;
    min-width: 290px;
}
.ntmods-cap-stat {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 12px 14px;
    border-radius: 12px;
    background: rgba(10, 18, 30, 0.24);
    backdrop-filter: blur(1px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.ntmods-cap-stat-value {
    font-family: "Montserrat", sans-serif;
    font-size: 24px;
    line-height: 1;
    font-weight: 700;
    color: #fff;
}
.ntmods-cap-stat-label {
    margin-top: 5px;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.76);
}
.ntmods-banner {
    margin-bottom: 14px;
    padding: 12px 14px;
    border-radius: 12px;
    background: linear-gradient(135deg, rgba(255, 193, 7, 0.18), rgba(255, 146, 43, 0.16));
    border: 1px solid rgba(255, 196, 79, 0.26);
    color: #ffe8b6;
}
.ntmods-banner-title {
    font-family: "Montserrat", sans-serif;
    font-size: 14px;
    font-weight: 700;
    margin-bottom: 5px;
    color: #fff0c9;
}
.ntmods-banner-copy {
    font-size: 12px;
    line-height: 1.55;
}
.ntmods-top-tabs {
    display: flex;
    flex-wrap: nowrap;
    gap: 2px;
    align-items: flex-end;
    overflow: hidden;
    margin: 0 0 0 0;
    padding: 4px 2px 0;
    width: 100%;
    min-width: 0;
    position: relative;
    z-index: 2;
}
.ntmods-top-tab {
    appearance: none;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: 8px 8px 0 0;
    background: transparent;
    color: rgba(174, 180, 199, 0.7);
    padding: 14px 0 13px;
    cursor: pointer;
    position: relative;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
    box-shadow: none;
}
.ntmods-top-tab:hover {
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.05);
    color: #eff3ff;
}
.ntmods-top-tab.is-active {
    background: linear-gradient(180deg, #3a3f5c 0%, #2e3148 100%);
    color: #fff;
    border-color: rgba(140, 160, 255, 0.18);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 0 12px rgba(100, 130, 255, 0.06);
    z-index: 3;
    margin-top: -4px;
    padding-bottom: 17px;
}
.ntmods-top-tab.is-active::after {
    content: "";
    position: absolute;
    left: -1px;
    right: -1px;
    bottom: -1px;
    height: 3px;
    background: #2a2d3d;
}
.ntmods-top-tab .bucket {
    display: flex !important;
    align-items: center !important;
    justify-content: space-evenly !important;
    width: 100% !important;
    gap: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
}
.ntmods-top-tab .bucket-media {
    margin: 0 !important;
    padding: 0 !important;
    flex: 0 0 auto;
}
.ntmods-top-tab .bucket-content {
    margin: 0 !important;
    padding: 0 !important;
    flex: 0 0 auto;
    font-family: "Montserrat", sans-serif;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}
.ntmods-top-tab-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #b8c4da;
}
.ntmods-top-tab.is-active .ntmods-top-tab-glyph {
    color: #c5d4ff;
    filter: drop-shadow(0 0 3px rgba(140, 170, 255, 0.3));
}
.ntmods-top-tab.is-active .bucket-content {
    color: #fff;
    text-shadow: 0 0 8px rgba(160, 185, 255, 0.2);
}
.ntmods-mod-icon {
    display: block;
    width: 100%;
    height: 100%;
}
.ntmods-mod-icon--tab {
    width: 15px;
    height: 15px;
}
.ntmods-mod-icon--chip {
    width: 20px;
    height: 20px;
}
.ntmods-module-card {
    display: flex;
    flex-direction: column;
    min-height: 900px;
    flex: 1 1 auto;
    border-radius: 0 0 18px 18px;
    background: url(/dist/site/images/backgrounds/bg-noise.png) top left repeat, linear-gradient(180deg, #2a2d3d 0%, #232636 100%);
    background-attachment: fixed, scroll;
    border: 1px solid rgba(255, 255, 255, 0.09);
    border-top: none;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.24);
    overflow: visible;
    margin-top: 0;
    position: relative;
    z-index: 1;
}
.ntmods-module-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    padding: 18px 20px;
    background: transparent;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.ntmods-module-head-copy {
    min-width: 0;
}
.ntmods-module-overline {
    margin-bottom: 4px;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    font-size: 11px;
    color: #8ea4d0;
}
.ntmods-module-title {
    margin: 0;
    font-family: "Montserrat", sans-serif;
    font-size: 28px;
    line-height: 1.08;
    color: #fff;
}
.ntmods-module-description {
    margin: 6px 0 0;
    font-size: 13px;
    line-height: 1.5;
    color: #adbbdb;
}
.ntmods-module-actions {
    display: flex;
    align-items: center;
    gap: 0;
    flex: 0 0 auto;
}
.ntmods-rescan-btn {
    appearance: none;
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(255, 255, 255, 0.04);
    color: #edf3ff;
    border-radius: 10px;
    padding: 10px 12px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
}
.ntmods-rescan-btn:hover {
    background: rgba(255, 255, 255, 0.08);
}
.ntmods-layout {
    display: flex;
    gap: 18px;
    padding: 18px;
    background: transparent;
}
.ntmods-sidebar {
    width: 264px;
    max-width: 264px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ntmods-nav-btn {
    box-shadow: none;
    justify-content: flex-start;
    width: 100%;
    backface-visibility: hidden;
    background: #393c50;
    border: 1px solid transparent;
    color: #a6aac1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    font-family: "Montserrat", sans-serif;
    font-size: 14px;
    overflow: hidden;
    padding: 13px 16px;
    position: relative;
    text-align: left;
    transition: all 0.12s linear;
}
.ntmods-nav-btn:first-child {
    border-radius: 5px 5px 0 0;
}
.ntmods-nav-btn:last-child {
    border-radius: 0 0 5px 5px;
}
.ntmods-nav-btn:hover {
    background: #585e7d;
    color: #e2e3eb;
}
.ntmods-nav-btn.is-active {
    background: #167ac3 !important;
    color: #fff;
    text-shadow: 0 2px 2px rgba(2, 2, 2, 0.25);
}
.ntmods-nav-btn-title {
    font-size: 13px;
    font-weight: 500;
}
.ntmods-stage {
    min-width: 0;
    flex: 1;
    border-radius: 10px;
    background: #2b2e3f;
    border: 1px solid rgba(255, 255, 255, 0.08);
    padding: 18px;
}
.ntmods-stage-intro {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    align-items: flex-start;
    margin-bottom: 18px;
}
.ntmods-stage-title {
    margin: 0;
    font-family: "Montserrat", sans-serif;
    font-size: 22px;
    line-height: 1.1;
    color: #fff;
}
.ntmods-stage-subtitle {
    margin: 6px 0 0;
    font-size: 13px;
    line-height: 1.55;
    color: #aebadc;
}
.ntmods-stage-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: flex-end;
}
.ntmods-pill {
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    padding: 7px 10px;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 11px;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: #c5d4f6;
}
.ntmods-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}
@media (max-width: 1220px) {
    .ntmods-top-tabs {
        gap: 3px;
    }
    .ntmods-top-tab {
        padding: 13px 6px 12px;
    }
    .ntmods-top-tab .bucket {
        gap: 3px;
    }
    .ntmods-top-tab .bucket-content {
        font-size: 12px;
    }
    .ntmods-mod-icon--tab {
        width: 14px;
        height: 14px;
    }
}
.ntmods-field-card,
.ntmods-action-card,
.ntmods-note,
.ntmods-toggle-card {
    box-sizing: border-box;
    min-width: 0;
}
.ntmods-field-card,
.ntmods-action-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px;
    border-radius: 14px;
    background: #2e3346;
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.ntmods-toggle-card {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    padding: 14px;
    border-radius: 14px;
    background: #2e3346;
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.ntmods-toggle-copy {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}
.ntmods-field-title {
    font-size: 13px;
    font-weight: 700;
    color: #edf3ff;
}
.ntmods-field-help {
    font-size: 12px;
    line-height: 1.45;
    color: #9aa8c9;
}
.ntmods-input {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 10px;
    background: #1d2030;
    color: #eef3ff;
    padding: 10px 11px;
    font-size: 13px;
}
.ntmods-input:focus {
    outline: none;
    border-color: #1c99f4;
    box-shadow: 0 0 0 2px rgba(28, 153, 244, 0.22);
}
.ntmods-color-row {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ntmods-color-picker {
    width: 48px;
    min-width: 48px;
    height: 42px;
    border-radius: 10px;
    background: #1d2030;
    border: 1px solid rgba(255, 255, 255, 0.14);
    padding: 0;
}
.ntmods-color-input {
    text-transform: uppercase;
}
.ntmods-switch {
    position: relative;
    width: 42px;
    height: 24px;
    flex: 0 0 auto;
}
.ntmods-switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    z-index: 2;
}
.ntmods-switch-track {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 999px;
    background: #5b627f;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
    transition: background 0.14s ease;
}
.ntmods-switch-track::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    transition: transform 0.12s ease;
}
.ntmods-switch input:checked + .ntmods-switch-track {
    background: #d62f3a;
    box-shadow: 0 4px 18px rgba(214, 47, 58, 0.28);
}
.ntmods-switch input:checked + .ntmods-switch-track::after {
    transform: translateX(18px);
}
.ntmods-action-btn {
    appearance: none;
    border: 0;
    border-radius: 10px;
    padding: 11px 14px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 700;
    color: #fff;
}
.ntmods-action-btn--primary {
    background: #167ac3;
}
.ntmods-action-btn--secondary {
    background: #434965;
}
.ntmods-action-btn--danger {
    background: #a5333d;
}
.ntmods-action-btn:hover {
    filter: brightness(1.08);
}
.ntmods-note {
    grid-column: 1 / -1;
    padding: 14px;
    border-radius: 14px;
    font-size: 12px;
    line-height: 1.55;
    border: 1px solid transparent;
}
.ntmods-note--info {
    background: rgba(28, 153, 244, 0.1);
    border-color: rgba(28, 153, 244, 0.22);
    color: #cde6ff;
}
.ntmods-note--warning {
    background: rgba(255, 176, 32, 0.12);
    border-color: rgba(255, 176, 32, 0.22);
    color: #ffe1a6;
}
.ntmods-empty {
    padding: 28px;
    text-align: center;
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.04);
    color: #d2dcf5;
}
.ntmods-race-options-stage,
.ntmods-generic-stage {
    min-width: 0;
}
.ntmods-ro-panel {
    min-width: 0;
}
.ntcfg-panel-title {
    margin: 0;
    font-size: 20px;
    font-family: "Montserrat", sans-serif;
    color: #fff;
}
.ntcfg-panel-subtitle {
    margin: 6px 0 16px;
    color: #b5bad3;
    font-size: 13px;
    line-height: 1.45;
}
.ntcfg-fields {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.ntcfg-field {
    display: block;
}
.ntcfg-field-title {
    color: #d8dcf2;
    font-size: 13px;
    margin-bottom: 6px;
}
.ntcfg-field-help {
    margin-top: 5px;
    color: #99a4c5;
    font-size: 12px;
    line-height: 1.35;
}
.ntcfg-field-warning {
    color: #ffb8b8;
}
.ntcfg-input {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 8px;
    background: #1d2030;
    color: #eef3ff;
    padding: 10px;
    font-size: 13px;
}
.ntcfg-input:focus {
    outline: none;
    border-color: #1c99f4;
    box-shadow: 0 0 0 2px rgba(28, 153, 244, 0.28);
}
.ntcfg-input.ntcfg-input-warning {
    border-color: rgba(255, 90, 90, 0.85);
    box-shadow: 0 0 0 2px rgba(255, 90, 90, 0.2);
}
.ntcfg-color-row {
    display: flex;
    gap: 8px;
}
.ntcfg-color-picker {
    width: 52px;
    min-width: 52px;
    padding: 0;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: #1d2030;
    cursor: pointer;
}
.ntcfg-color-hex {
    flex: 1;
    text-transform: uppercase;
}
.ntcfg-checkbox {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    background: #252839;
    padding: 10px 12px;
}
.ntcfg-checkbox-label {
    color: #d8dcf2;
    font-size: 13px;
}
.ntcfg-checkbox-copy {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.ntcfg-checkbox-help {
    color: #99a4c5;
    font-size: 12px;
    line-height: 1.35;
}
.ntcfg-checkbox-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.ntcfg-checkbox-controls .ntcfg-color-picker {
    width: 38px;
    min-width: 38px;
    height: 30px;
}
.ntcfg-checkbox-controls .ntcfg-input {
    width: 84px;
    padding: 6px 8px;
    font-size: 12px;
}
.ntcfg-checkbox-controls select.ntcfg-input {
    width: auto;
}
.ntcfg-switch {
    position: relative;
    width: 40px;
    height: 24px;
    flex: 0 0 auto;
    display: inline-block;
    cursor: pointer;
}
.ntcfg-switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
    margin: 0;
    z-index: 2;
}
.ntcfg-switch-track {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 999px;
    background: #585e7d;
    transition: background 0.15s ease;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
    pointer-events: none;
}
.ntcfg-switch-track::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    transition: transform 0.1s ease;
}
.ntcfg-switch input:checked + .ntcfg-switch-track {
    background: #d62f3a;
    box-shadow: 0 2px 20px rgba(214, 47, 58, 0.35);
}
.ntcfg-switch input:checked + .ntcfg-switch-track::after {
    transform: translateX(16px);
}
.ntcfg-action {
    border: 0;
    border-radius: 8px;
    padding: 10px 14px;
    font-size: 13px;
    cursor: pointer;
    color: #fff;
    background: #393c50;
}
.ntcfg-action:hover {
    filter: brightness(1.1);
}
.ntcfg-action.ntcfg-primary {
    background: #167ac3;
}
.ntcfg-inline-action {
    align-self: flex-start;
}
.ntcfg-action--primary {
    background: #167ac3;
}
.ntcfg-action--secondary {
    background: #393c50;
}
.ntcfg-action--danger {
    background: transparent;
    color: #ff6b6b;
    font-size: 18px;
    padding: 4px 8px;
    line-height: 1;
}
.ntcfg-action--danger:hover {
    color: #ff4444;
}
.ntcfg-keymap-container {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ntcfg-keymap-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ntcfg-keymap-row.is-invalid {
    padding: 6px 8px;
    border-radius: 8px;
    background: rgba(255, 107, 107, 0.08);
    border: 1px solid rgba(255, 107, 107, 0.16);
}
.ntcfg-keymap-mod-select {
    width: auto !important;
    min-width: 0 !important;
    max-width: 110px;
    padding: 8px 6px !important;
    font-size: 12px !important;
    flex-shrink: 0;
}
.ntcfg-keymap-plus {
    color: #99a4c5;
    font-size: 13px;
    flex-shrink: 0;
}
.ntcfg-keymap-action-select {
    width: auto !important;
    min-width: 0 !important;
    max-width: 96px;
    padding: 8px 6px !important;
    font-size: 12px !important;
    flex-shrink: 0;
}
.ntcfg-keymap-script-select,
.ntcfg-keymap-setting-select {
    width: auto !important;
    min-width: 0 !important;
    flex: 1;
    padding: 8px 6px !important;
    font-size: 12px !important;
}
.ntcfg-keymap-key {
    width: 110px !important;
    min-width: 110px;
    max-width: 110px;
    text-align: center;
    font-weight: 600;
    padding: 8px 4px !important;
}
.ntcfg-keymap-keycapture {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ntcfg-keymap-keycapture.is-capturing {
    border-color: #1c99f4;
    box-shadow: 0 0 0 2px rgba(28, 153, 244, 0.28);
    color: #ffffff;
}
.ntcfg-keymap-path {
    flex: 1;
    min-width: 0;
}
.ntcfg-keymap-remove {
    flex-shrink: 0;
}
.ntcfg-keymap-warning {
    margin: -2px 0 2px;
    color: #ffb3b3;
    font-size: 12px;
    line-height: 1.35;
}
.ntcfg-keymap-add-row {
    display: flex;
    gap: 6px;
    margin-top: 4px;
}
.ntcfg-keymap-add-row .ntcfg-action {
    padding: 6px 14px;
    font-size: 12px;
    white-space: nowrap;
}
.ntcfg-keymap-summary {
    color: #ffb3b3;
}
.ntcfg-theme-preview-label {
    color: #d8dcf2;
    font-size: 13px;
    margin-bottom: 4px;
}
.ntcfg-theme-preview-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 8px;
    margin-bottom: 12px;
}
.ntcfg-theme-preview {
    width: 100%;
    box-sizing: border-box;
    padding: 12px;
    margin-bottom: 0;
    border-radius: 5px;
    background: #e9eaeb;
    color: #2e3141;
}
.ntcfg-theme-preview span {
    font-family: "Roboto Mono", "Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace;
    display: inline-block;
    padding: 2px;
    font-size: 18px;
    white-space: pre;
}
.ntcfg-theme-preview span.ntcfg-theme-char-active {
    background: #1c99f4;
    color: #fff;
}
.ntcfg-theme-preview span.ntcfg-theme-char-incorrect {
    background: #d62f3a;
    color: #fff;
}
.ntcfg-theme-preview span.ntcfg-theme-char-typed {
    color: #2e3141;
    opacity: 0.5;
}
.ntcfg-theme-preview span.ntcfg-theme-char-rainbow {
    animation: ntcfg-preview-rainbow-text 10s infinite alternate;
    -webkit-animation: ntcfg-preview-rainbow-text 10s infinite alternate;
    opacity: 1;
}
@keyframes ntcfg-preview-rainbow-text {
    0% { color: blue; }
    10% { color: #ff005d; }
    20% { color: #f0f; }
    30% { color: black; }
    40% { color: #7500ff; }
    50% { color: blue; }
    60% { color: #f0f; }
    70% { color: black; }
    80% { color: black; }
    90% { color: red; }
    100% { color: red; }
}
.ntcfg-sticky-preview {
    position: sticky;
    top: 0;
    z-index: 10;
    background: #2b2e3f;
    padding-bottom: 12px;
    margin-bottom: 4px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.ntcfg-pn-preview {
    width: 100%;
    box-sizing: border-box;
    padding: 12px;
    border-radius: 5px;
    background: #e9eaeb;
    color: #2e3141;
    margin-bottom: 0;
    overflow: hidden;
    word-wrap: break-word;
}
.ntcfg-pn-preview span {
    font-family: "Roboto Mono", "Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace;
    display: inline-block;
    padding: 2px;
    font-size: 18px;
    white-space: pre;
}
.ntcfg-pn-preview-rainbow {
    animation: ntcfg-pn-preview-rainbow 3s linear infinite;
    -webkit-animation: ntcfg-pn-preview-rainbow 3s linear infinite;
}
@keyframes ntcfg-pn-preview-rainbow {
    0% { color: #FF0000; }
    14% { color: #FF8C00; }
    28% { color: #FFD700; }
    42% { color: #00CC00; }
    57% { color: #0066FF; }
    71% { color: #7B00FF; }
    85% { color: #FF00FF; }
    100% { color: #FF0000; }
}
.ntcfg-theme-settings {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ntcfg-theme-setting {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    background: #252839;
    padding: 8px 10px;
}
.ntcfg-theme-setting-title {
    color: #d8dcf2;
    font-size: 13px;
}
.ntcfg-theme-setting-controls {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ntcfg-theme-color-compact {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ntcfg-theme-color-compact .ntcfg-color-picker {
    width: 38px;
    min-width: 38px;
    height: 30px;
}
.ntcfg-theme-color-compact .ntcfg-input {
    width: 96px;
    padding: 6px 8px;
    font-size: 12px;
}
.ntcfg-theme-input-compact {
    width: auto;
    padding: 6px 8px;
    font-size: 12px;
}
.ntcfg-theme-input-select {
    width: 180px;
}
.ntcfg-theme-input-number {
    width: 96px;
}
.ntcfg-theme-input-preset {
    width: 132px;
}
.ntcfg-preview-host {
    display: block;
}
.ntcfg-preview-surface {
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
    background:
        linear-gradient(180deg, rgba(88, 95, 126, 0.18), rgba(37, 40, 57, 0.9)),
        #252839;
    padding: 12px;
    margin-bottom: 12px;
}
.ntcfg-preview-grid {
    display: grid;
    gap: 10px;
}
.ntcfg-preview-grid--two {
    grid-template-columns: 1fr;
}
.ntcfg-preview-card {
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    background: rgba(9, 12, 20, 0.22);
    padding: 10px 12px;
}
.ntcfg-preview-card.is-off {
    opacity: 0.5;
}
.ntcfg-preview-card-title {
    color: #aab2d4;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-bottom: 6px;
}
.ntcfg-preview-card-copy {
    color: #eef2ff;
    font-size: 14px;
    line-height: 1.45;
}
.ntcfg-preview-card-subcopy {
    color: #aab2d4;
    font-size: 12px;
    line-height: 1.4;
    margin-top: 4px;
}
.ntcfg-preview-empty {
    color: #aab2d4;
    font-size: 13px;
    line-height: 1.4;
}
.ntcfg-preview-pill-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}
.ntcfg-preview-pill {
    display: inline-flex;
    align-items: center;
    padding: 4px 10px;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(255, 255, 255, 0.04);
    color: #d8dcf2;
    font-size: 11px;
    line-height: 1;
    white-space: nowrap;
}
.ntcfg-preview-pill.is-off {
    opacity: 0.48;
}
.ntcfg-preview-pill--blue.is-active {
    border-color: rgba(28, 153, 244, 0.5);
    background: rgba(28, 153, 244, 0.18);
}
.ntcfg-preview-pill--gold.is-active {
    border-color: rgba(243, 168, 27, 0.45);
    background: rgba(243, 168, 27, 0.14);
}
.ntcfg-preview-pill--danger.is-active {
    border-color: rgba(214, 47, 58, 0.48);
    background: rgba(214, 47, 58, 0.16);
}
.ntcfg-preview-pill--success.is-active {
    border-color: rgba(74, 222, 128, 0.45);
    background: rgba(74, 222, 128, 0.14);
}
.ntcfg-preview-pill--warning.is-active {
    border-color: rgba(255, 194, 74, 0.42);
    background: rgba(255, 194, 74, 0.14);
}
.ntcfg-preview-matrix {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
    gap: 8px;
    margin-top: 10px;
}
.ntcfg-preview-tile {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 64px;
    padding: 10px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
}
.ntcfg-preview-tile.is-off {
    opacity: 0.5;
}
.ntcfg-preview-tile--blue {
    background: rgba(28, 153, 244, 0.12);
}
.ntcfg-preview-tile-label {
    color: #aab2d4;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.ntcfg-preview-tile-value {
    color: #ffffff;
    font-size: 15px;
    font-weight: 600;
}
.ntcfg-preview-name-text,
.ntcfg-preview-entry-meta {
    color: #eef2ff;
    font-size: 13px;
}
.ntcfg-badge-inline-preview {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}
.ntcfg-badge-name-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
}
.ntcfg-badge-name-text {
    color: #eef2ff;
    font-size: 13px;
}
.ntcfg-badge-chip {
    display: inline-flex;
    align-items: center;
    padding: 3px 8px;
    border-radius: 999px;
    color: #fff;
    font-size: 11px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.ntcfg-badge-chip--racer {
    background: linear-gradient(90deg, rgba(28, 153, 244, 0.95), rgba(86, 180, 255, 0.95));
}
.ntcfg-badge-chip--team {
    background: linear-gradient(90deg, rgba(243, 168, 27, 0.95), rgba(255, 210, 92, 0.95));
    color: #3f3210;
}
.ntcfg-badge-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 52px;
    border-radius: 8px;
    color: #ffffff;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.ntcfg-badge-banner--racer {
    background: linear-gradient(120deg, rgba(28, 153, 244, 0.75), rgba(94, 204, 255, 0.45));
}
.ntcfg-badge-banner--team {
    background: linear-gradient(120deg, rgba(243, 168, 27, 0.75), rgba(255, 221, 129, 0.48));
    color: #3f3210;
}
.ntcfg-bot-toggle-preview {
    appearance: none;
    border: none;
    border-radius: 8px;
    padding: 9px 14px;
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    cursor: default;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.ntcfg-bot-toggle-preview.is-on {
    background: #1976d2;
}
.ntcfg-bot-toggle-preview.is-off {
    background: #666;
}
.ntcfg-flag-preview-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.ntcfg-flag-sample {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
    color: #eef2ff;
    font-size: 13px;
}
.ntcfg-flag-indicator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 22px;
    padding: 0 8px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.ntcfg-flag-indicator--danger {
    background: rgba(214, 47, 58, 0.22);
    color: #ffd4d8;
}
.ntcfg-flag-indicator--success {
    background: rgba(74, 222, 128, 0.18);
    color: #c7ffd8;
}
.ntcfg-flag-indicator--warning {
    background: rgba(243, 168, 27, 0.18);
    color: #ffe2a7;
}
.ntcfg-leader-preview-tabs {
    display: flex;
    gap: 10px;
    align-items: flex-end;
    margin-bottom: 0;
}
.ntcfg-leader-preview-tab {
    min-width: 150px;
    padding: 12px 16px;
    border-radius: 8px 8px 0 0;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-bottom: none;
    background: #3b3e50;
    color: #aeb4cb;
    font-size: 13px;
    font-weight: 600;
}
.ntcfg-leader-preview-tab.is-active {
    background: #4a4f66;
    color: #fff;
}
.ntcfg-leader-preview-panel {
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0 10px 10px 10px;
    background: #4a4f66;
    padding: 12px;
}
.ntcfg-leader-preview-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}
.ntcfg-leader-preview-btn,
.ntcfg-leader-preview-refresh {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 34px;
    padding: 0 12px;
    border-radius: 6px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(0, 0, 0, 0.14);
    color: #d8dcf2;
    font-size: 12px;
    white-space: nowrap;
}
.ntcfg-leader-preview-btn.is-active {
    background: #1c99f4;
    border-color: #1c99f4;
    color: #fff;
}
.ntcfg-leader-preview-refresh {
    margin-left: auto;
}
.ntcfg-leader-preview-entry {
    display: flex;
    align-items: center;
    gap: 8px;
    color: #eef2ff;
}
.ntcfg-leader-preview-change {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 22px;
    padding: 0 8px;
    border-radius: 999px;
    background: rgba(74, 222, 128, 0.2);
    color: #c7ffd8;
    font-size: 11px;
    font-weight: 700;
}
.ntcfg-music-preview-card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(10, 12, 20, 0.22);
}
.ntcfg-music-preview-cover {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 8px;
    background: linear-gradient(135deg, rgba(29, 185, 84, 0.8), rgba(28, 153, 244, 0.8));
    color: #fff;
    font-size: 16px;
    font-weight: 700;
    flex-shrink: 0;
}
.ntcfg-music-preview-cover--art {
    background: linear-gradient(135deg, rgba(28, 153, 244, 0.78), rgba(138, 103, 255, 0.78));
}
.ntcfg-music-preview-meta {
    min-width: 0;
}
.ntcfg-music-preview-title {
    color: #eef2ff;
    font-size: 13px;
    font-weight: 700;
    margin-bottom: 3px;
}
.ntcfg-music-preview-subtitle {
    color: #aab2d4;
    font-size: 12px;
    line-height: 1.35;
    word-break: break-word;
}
.ntcfg-music-control-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}
.ntcfg-music-control-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 30px;
    padding: 0 10px;
    border-radius: 6px;
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: rgba(0, 0, 0, 0.18);
    color: #eef2ff;
    font-size: 12px;
}
.ntcfg-music-control-pill.is-active {
    border-color: rgba(29, 185, 84, 0.55);
    background: rgba(29, 185, 84, 0.18);
}
@media (min-width: 900px) {
    .ntcfg-theme-preview-grid {
        grid-template-columns: 1fr 1fr;
    }
    .ntcfg-preview-grid--two {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
.ntmods-toast {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 100002;
    padding: 12px 14px;
    border-radius: 12px;
    background: rgba(10, 16, 26, 0.96);
    color: #eff5ff;
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: 0 16px 38px rgba(0, 0, 0, 0.38);
    opacity: 0;
    transform: translateY(8px);
    pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
    font-size: 12px;
    line-height: 1.35;
}
.ntmods-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}
@media (max-width: 1100px) {
    .ntmods-cap {
        flex-direction: column;
    }
    .ntmods-cap-stats {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        min-width: 0;
    }
    .ntmods-module-card {
        min-height: 820px;
    }
}
@media (max-width: 980px) {
    .ntmods-layout {
        flex-direction: column;
    }
    .ntmods-sidebar {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        width: auto;
        max-width: none;
        gap: 8px;
    }
    .ntmods-stage-intro {
        flex-direction: column;
    }
    .ntmods-grid {
        grid-template-columns: 1fr;
    }
    .ntmods-nav-btn,
    .ntmods-nav-btn:first-child,
    .ntmods-nav-btn:last-child {
        border-radius: 8px;
    }
}
@media (max-width: 720px) {
    .ntmods-cap {
        padding: 18px;
    }
    .ntmods-cap-stats {
        grid-template-columns: 1fr;
    }
    .ntmods-module-head {
        flex-direction: column;
        align-items: stretch;
    }
    .ntmods-module-actions {
        justify-content: space-between;
    }
    .ntmods-sidebar {
        grid-template-columns: 1fr;
    }
    .ntmods-module-card {
        min-height: 700px;
    }
    .ntmods-top-tab {
        min-width: 0;
        padding: 12px 5px 11px;
    }
    .ntmods-top-tab .bucket-content {
        font-size: 11px;
    }
    .ntmods-top-tab-glyph {
    }
    .ntmods-mod-icon--tab {
        width: 13px;
        height: 13px;
    }
}

/* Reorder mode */
.ntmods-reorder-btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.8);
    padding: 7px 12px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.ntmods-reorder-btn:hover {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.3);
    color: #fff;
}
.ntmods-reorder-btn.is-active {
    background: rgba(28, 153, 244, 0.25);
    border-color: rgba(28, 153, 244, 0.5);
    color: #fff;
}
.ntmods-top-tabs.is-reordering {
    outline: 2px dashed rgba(28, 153, 244, 0.3);
    outline-offset: 2px;
    border-radius: 10px;
}
.ntmods-top-tabs.is-reordering .ntmods-top-tab {
    cursor: grab;
}
.ntmods-top-tabs.is-reordering .ntmods-top-tab:active {
    cursor: grabbing;
}
.ntmods-tab-dragging {
    opacity: 0.4;
    outline: 2px dashed rgba(255,255,255,0.2);
}
.ntmods-tab-dragover {
    background: rgba(28, 153, 244, 0.15) !important;
    border-color: rgba(28, 153, 244, 0.4) !important;
}

/* Not Installed card */
.ntmods-not-installed {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 60px 30px;
    gap: 12px;
    min-height: 300px;
}
.ntmods-not-installed-icon {
    opacity: 0.3;
    margin-bottom: 8px;
}
.ntmods-not-installed-icon svg {
    width: 64px;
    height: 64px;
}
.ntmods-not-installed-title {
    font-family: "Montserrat", sans-serif;
    font-size: 22px;
    font-weight: 700;
    color: rgba(174, 180, 199, 0.8);
    margin: 0;
}
.ntmods-not-installed-copy {
    font-size: 14px;
    color: rgba(174, 180, 199, 0.6);
    max-width: 420px;
    line-height: 1.5;
    margin: 0;
}
.ntmods-not-installed-copy strong {
    color: rgba(220, 225, 240, 0.8);
}
.ntmods-install-link {
    display: inline-block;
    margin-top: 12px;
    padding: 10px 24px;
    font-family: "Montserrat", sans-serif;
    font-size: 13px;
    font-weight: 600;
    color: #fff;
    background: linear-gradient(135deg, #1c99f4 0%, #1a7ed4 100%);
    border-radius: 6px;
    text-decoration: none;
    transition: background 0.15s ease, transform 0.1s ease;
}
.ntmods-install-link:hover {
    background: linear-gradient(135deg, #2ba6ff 0%, #1c99f4 100%);
    transform: translateY(-1px);
}

/* Preview tab styling (not installed — blue outline) */
.ntmods-top-tab.is-preview {
    opacity: 0.6;
    border-color: rgba(100, 160, 255, 0.3);
}
.ntmods-top-tab.is-preview:hover {
    opacity: 0.8;
}


/* Outdated tab styling (installed but needs update — orange outline) */
.ntmods-top-tab.is-outdated {
    opacity: 0.6;
    border-color: rgba(255, 107, 43, 0.3);
}
.ntmods-top-tab.is-outdated:hover {
    opacity: 0.8;
}
.ntmods-outdated-card .ntmods-not-installed-icon {
    opacity: 0.5;
}
.ntmods-outdated-title {
    color: rgba(255, 146, 43, 0.9) !important;
}
.ntmods-update-link {
    background: linear-gradient(135deg, #ff6b2b 0%, #e55a1b 100%) !important;
}
.ntmods-update-link:hover {
    background: linear-gradient(135deg, #ff8040 0%, #ff6b2b 100%) !important;
}


/* Dismiss stale data button */
.ntmods-dismiss-stale-btn {
    appearance: none;
    margin-top: 8px;
    padding: 8px 18px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.6);
    font-size: 12px;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
.ntmods-dismiss-stale-btn:hover {
    background: rgba(214, 47, 58, 0.2);
    border-color: rgba(214, 47, 58, 0.4);
    color: #ff9999;
}

/* Update dot on tab label */
.ntmods-update-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #ff6b2b;
    margin-left: 5px;
    vertical-align: middle;
    box-shadow: 0 0 6px rgba(255, 107, 43, 0.5);
}

/* Update banner inside module card */
.ntmods-update-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 12px 20px;
    background: linear-gradient(135deg, rgba(255, 107, 43, 0.14), rgba(255, 176, 32, 0.10));
    border-bottom: 1px solid rgba(255, 146, 43, 0.2);
    font-size: 13px;
    color: #ffe0b6;
}
.ntmods-update-banner-text {
    min-width: 0;
    line-height: 1.45;
}
.ntmods-update-banner-text strong {
    color: #fff;
}
.ntmods-update-banner-link {
    flex: 0 0 auto;
    padding: 7px 14px;
    border-radius: 6px;
    background: rgba(255, 107, 43, 0.25);
    border: 1px solid rgba(255, 146, 43, 0.35);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition: background 0.12s ease;
}
.ntmods-update-banner-link:hover {
    background: rgba(255, 107, 43, 0.4);
}
.ntmods-version-arrow {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 6px;
    font-family: "Montserrat", sans-serif;
    font-weight: 600;
}
.ntmods-version-current {
    color: rgba(255, 255, 255, 0.6);
}
.ntmods-version-arrow svg {
    color: rgba(255, 255, 255, 0.4);
    flex-shrink: 0;
}
.ntmods-version-latest {
    color: #fff;
}
        `;
        (document.head || document.documentElement).appendChild(style);
    }

    function setActiveDropdownItem() {
        document.querySelectorAll(`.${DROPDOWN_ITEM_CLASS}`).forEach((item) => {
            item.classList.toggle('is-current', isModMenuRoute());
        });
    }

    function setPageTitle() {
        if (isModMenuRoute()) {
            document.title = 'Mods | Nitro Type';
        }
    }

    function ensureEntryStyles() {
        if (document.getElementById('ntmods-entry-style')) return;
        const style = document.createElement('style');
        style.id = 'ntmods-entry-style';
        style.textContent = `
.ntmods-dropdown-item.is-current .dropdown-link {
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
}
.ntmods-dropdown-item .dropdown-link .icon {
    opacity: 0.92;
}
.ntmods-dropdown-item .dropdown-link .ntmods-inline-icon {
    opacity: 1;
    color: #9fb2c6 !important;
    fill: currentColor !important;
}
.ntmods-dropdown-item .dropdown-link:hover .ntmods-inline-icon,
.ntmods-dropdown-item.is-current .dropdown-link .ntmods-inline-icon {
    color: #c8d5e2 !important;
    fill: currentColor !important;
}
.ntmods-inline-icon {
    width: 16px;
    height: 16px;
    min-width: 16px;
    min-height: 16px;
    display: inline-block;
    vertical-align: -3px;
    flex: 0 0 16px;
    shape-rendering: geometricPrecision;
}
.ntmods-dropdown-item .dropdown-link {
    position: relative;
}
.ntmods-dropdown-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 10px;
    border: 1px solid rgba(255, 107, 43, 0.55);
    background: rgba(255, 107, 43, 0.1);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.3px;
    color: #ff9e5e;
    line-height: 1.4;
    vertical-align: middle;
    white-space: nowrap;
}
.ntmods-dropdown-pill-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #ff6b2b;
    box-shadow: 0 0 5px rgba(255, 107, 43, 0.5);
    flex-shrink: 0;
}
        `;
        (document.head || document.documentElement).appendChild(style);
    }

    function getModsMenuIconMarkup() {
        return `
            <svg class="ntmods-inline-icon mrxs" viewBox="0 0 177.5 178" aria-hidden="true">
                <g
                    transform="translate(-12,189) scale(0.1,-0.1)"
                    fill="currentColor"
                    stroke="currentColor"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                >
                    <path
                        stroke-width="102"
                        d="M867 1870 c-18 -15 -28 -37 -38 -86 -8 -42 -19 -68 -29 -71 -8 -3
                        -49 -20 -91 -37 l-76 -32 -34 24 c-59 41 -95 53 -126 41 -15 -5 -61 -42 -100
                        -82 -94 -93 -100 -116 -48 -194 35 -53 37 -59 25 -88 -7 -16 -22 -57 -35 -90
                        l-23 -60 -65 -15 c-103 -24 -107 -30 -107 -177 0 -155 4 -160 133 -187 97 -21
                        111 -17 105 30 -3 27 -8 30 -68 44 -36 8 -71 19 -77 23 -9 5 -13 35 -13 87 0
                        52 4 82 13 87 6 4 41 15 77 23 l64 15 26 75 c14 41 35 92 48 113 30 51 28 68
                        -13 133 -19 30 -35 58 -35 62 0 4 27 34 59 66 l60 59 38 -25 c90 -59 96 -60
                        150 -31 26 14 82 38 123 53 l75 26 16 75 15 74 86 3 c99 3 97 5 117 -90 14
                        -64 19 -70 81 -87 25 -7 75 -29 113 -47 75 -38 72 -38 156 19 l43 30 64 -64
                        64 -64 -42 -63 -41 -63 41 -87 c23 -48 44 -100 48 -117 10 -45 20 -53 82 -66
                        92 -18 92 -18 92 -109 0 -91 0 -91 -92 -109 -65 -14 -72 -20 -88 -81 -6 -25
                        -27 -74 -46 -110 -19 -35 -34 -69 -34 -75 0 -5 18 -37 40 -70 l40 -60 -62 -62
                        c-34 -34 -64 -61 -67 -59 -3 2 -34 20 -69 41 l-64 39 -56 -31 c-31 -17 -87
                        -41 -123 -53 l-66 -21 -19 -77 -19 -77 -78 -3 c-46 -2 -84 2 -92 8 -7 6 -18
                        38 -25 73 -13 69 -24 87 -50 87 -35 0 -44 -26 -32 -93 29 -155 32 -157 196
                        -157 140 0 150 6 172 103 13 57 20 70 42 79 15 6 55 23 89 39 l61 27 54 -36
                        c84 -57 102 -52 203 48 102 101 107 123 51 206 l-35 52 36 83 c19 46 42 89 50
                        96 8 6 37 14 64 18 27 4 59 15 72 25 21 17 22 26 22 150 0 154 -3 159 -105
                        179 -51 10 -63 16 -72 39 -6 15 -24 55 -39 90 l-29 63 35 52 c55 81 50 111
                        -30 196 -72 75 -123 106 -158 95 -12 -4 -44 -22 -70 -39 l-49 -33 -61 27 c-34
                        16 -74 33 -89 39 -22 9 -29 22 -42 79 -22 98 -31 103 -176 103 -102 0 -121 -3
                        -143 -20z"
                    />
                </g>
                <svg x="7" y="24" width="156" height="156" viewBox="0 0 256 256">
                    <g transform="translate(0,256) scale(0.1,-0.1)" fill="currentColor" stroke="none">
                        <path d="M1162 1810 c-45 -11 -105 -31 -133 -46 -78 -43 -154 -125 -197 -212
                            l-37 -77 0 -175 0 -176 -230 -209 c-376 -342 -471 -439 -495 -502 -69 -186 63
                            -377 261 -376 118 0 170 41 469 368 258 283 366 389 387 380 32 -13 157 -24
                            208 -19 61 6 147 31 197 55 71 36 162 133 204 216 l39 78 0 135 c-1 119 -4
                            143 -25 200 -30 78 -61 100 -108 76 -16 -8 -82 -66 -147 -128 -128 -123 -160
                            -148 -189 -148 -27 0 -85 47 -97 78 -13 33 1 52 146 206 134 143 150 167 135
                            195 -16 31 -80 60 -178 81 -104 23 -105 23 -210 0z"/>
                    </g>
                </svg>
            </svg>
        `;
    }

    function insertModsDropdownItem() {
        ensureEntryStyles();
        const dropdownLists = document.querySelectorAll('.dropdown--account .dropdown-items');
        if (!dropdownLists.length) return;
        const hasUpdate = checkAnyModuleHasUpdate();
        const pillHtml = hasUpdate
            ? '<span class="ntmods-dropdown-pill"><span class="ntmods-dropdown-pill-dot"></span>Update Available</span>'
            : '';

        dropdownLists.forEach((list) => {
            const existing = list.querySelector(`.${DROPDOWN_ITEM_CLASS}`);

            // If item exists, just sync the pill state and move on
            if (existing) {
                const link = existing.querySelector('.dropdown-link');
                if (link) {
                    const pill = link.querySelector('.ntmods-dropdown-pill');
                    if (hasUpdate && !pill) {
                        link.insertAdjacentHTML('beforeend', '<span class="ntmods-dropdown-pill"><span class="ntmods-dropdown-pill-dot"></span>Update Available</span>');
                    } else if (!hasUpdate && pill) {
                        pill.remove();
                    }
                }
                return;
            }

            const li = document.createElement('li');
            li.className = `list-item dropdown-item ${DROPDOWN_ITEM_CLASS}`;
            li.innerHTML = `
                <a class="dropdown-link" href="${MOD_MENU_PATH}">
                    ${getModsMenuIconMarkup()}
                    Mods
                    ${pillHtml}
                </a>
            `;

            const statsItem = Array.from(list.children).find((item) => {
                const link = item.querySelector('a[href="/stats"]');
                return !!link || item.textContent.trim().includes('My Stats');
            });

            if (statsItem) {
                statsItem.after(li);
            } else {
                list.appendChild(li);
            }
        });

        setActiveDropdownItem();
    }

    // Sync the update pill on all existing "Mods" dropdown items.
    function updateDropdownDot() {
        const hasAnyUpdate = checkAnyModuleHasUpdate();
        document.querySelectorAll(`.${DROPDOWN_ITEM_CLASS}`).forEach((li) => {
            const link = li.querySelector('.dropdown-link');
            if (!link) return;
            const existing = link.querySelector('.ntmods-dropdown-pill');
            if (hasAnyUpdate && !existing) {
                link.insertAdjacentHTML('beforeend', '<span class="ntmods-dropdown-pill"><span class="ntmods-dropdown-pill-dot"></span>Update Available</span>');
            } else if (!hasAnyUpdate && existing) {
                existing.remove();
            }
        });
    }

    // Check if any registered module has an available update.
    // Always reads directly from localStorage + version cache — works on every page,
    // no dependency on _cachedModules or the mods page being loaded.
    function checkAnyModuleHasUpdate() {
        try {
            const versionCache = readVersionCache();
            const now = Date.now();
            const keys = Object.keys(localStorage);
            for (const key of keys) {
                if (!key.startsWith(MANIFEST_PREFIX)) continue;
                const manifest = readJson(key, null);
                if (!manifest?.id) continue;
                const meta = MODULE_META[manifest.id];
                if (!meta?.installUrl) continue;
                const installed = manifest.scriptVersion || '';
                if (!installed) continue;
                const entry = versionCache[manifest.id];
                if (!entry || (now - (entry.ts || 0) > VERSION_CACHE_TTL_MS)) continue;
                if (entry.version && isVersionOutdated(installed, entry.version)) return true;
            }
        } catch { /* ignore */ }
        return false;
    }

    function updateRouteStatus() {
        const main = document.querySelector('main.structure-content');
        if (isModMenuRoute()) {
            document.documentElement.classList.add('is-mods-route');
        } else {
            document.documentElement.classList.remove('is-mods-route');
            if (main) main.classList.remove('custom-loaded');
        }
    }

    function getModuleById(modules, moduleId) {
        return modules.find((module) => module.id === moduleId) || modules[0] || null;
    }

    // Synchronous — reads version cache only, no network requests.
    // The background fetcher (mods page only, every 24 hours max) handles Greasy Fork calls.
    function applyVersionUpdates(modules) {
        let needsRerender = false;
        for (const mod of modules) {
            if (mod.source !== 'manifest' && mod.source !== 'outdated') continue;
            const latest = getCachedLatestVersion(mod.id);
            if (latest && isVersionOutdated(mod.manifestVersion, latest) && !mod._hasUpdate) {
                needsRerender = true;
            }
        }
        updateDropdownDot();
        if (needsRerender && isModMenuRoute()) {
            invalidateModuleCache();
            renderPage('', '', true);
        }
    }

    function dismissStaleModule(moduleId) {
        // Remove the stale manifest and alive key
        try {
            localStorage.removeItem(MANIFEST_PREFIX + moduleId);
            localStorage.removeItem(ALIVE_PREFIX + moduleId);
        } catch { /* ignore */ }
        // Remove all ntcfg values for this module
        try {
            const prefix = `ntcfg:${moduleId}:`;
            Object.keys(localStorage).forEach((key) => {
                if (key.startsWith(prefix)) localStorage.removeItem(key);
            });
        } catch { /* ignore */ }
        showToast(`Cleared data for ${moduleId}.`);
        invalidateModuleCache();
        renderPage('', '', true);
    }

    let _renderDebounceTimer = null;
    let _versionCheckDone = false;
    let _cachedModules = null;
    let _rescanInProgress = false;
    let _suppressObserver = false;

    // Invalidate the module cache so the next render rebuilds from localStorage.
    function invalidateModuleCache() {
        _cachedModules = null;
        invalidateFieldCache();
        clearSectionDomCache();
        clearCardDomCache();
    }

    function renderPage(requestedModuleId = '', requestedSectionId = '', forceRebuild) {
        if (renderInProgress || !isModMenuRoute()) return;
        const mainContent = document.querySelector('main.structure-content');
        if (!mainContent) return;

        // Cancel any pending debounced render
        if (_renderDebounceTimer) {
            clearTimeout(_renderDebounceTimer);
            _renderDebounceTimer = null;
        }

        renderInProgress = true;
        _suppressObserver = true;
        try {
            ensureStyles();

            // Reuse cached module list for tab switches; rebuild on first load,
            // rescan, or when statuses change (forceRebuild).
            if (forceRebuild || !_cachedModules) {
                _cachedModules = buildVisibleModules();
            }
            const modules = _cachedModules;
            const selection = requestedModuleId
                ? {
                    activeModuleId: requestedModuleId,
                    activeSectionId: requestedSectionId
                }
                : getInitialSelection(modules);

            const activeModule = getModuleById(modules, selection.activeModuleId);
            const activeModuleSections = getRenderableSections(activeModule);
            const activeSection = activeModule
                ? activeModuleSections.find((section) => section.id === selection.activeSectionId) || activeModuleSections[0]
                : null;

            const nextUiState = {
                moduleId: activeModule ? activeModule.id : '',
                sectionId: activeSection ? activeSection.id : ''
            };

            writeUiState(nextUiState);

            // Batch-read all field values for the active module in one pass
            if (activeModule) preloadFieldValues(activeModule.id, activeModule.source);

            // Populate cached version info synchronously before rendering
            modules.forEach((mod) => {
                if (mod.source !== 'manifest' && mod.source !== 'outdated') return;
                const meta = MODULE_META[mod.id];
                if (!meta?.installUrl) return;
                const latest = getCachedLatestVersion(mod.id);
                if (latest && isVersionOutdated(mod.manifestVersion, latest)) {
                    mod._hasUpdate = true;
                    mod._latestVersion = latest;
                }
            });

            mainContent.innerHTML = buildPageHtml(modules, nextUiState.moduleId, nextUiState.sectionId);
            mainContent.classList.add('custom-loaded');
            requestAnimationFrame(() => {
                mainContent.classList.add('custom-loaded');
            });

            bindPageEvents(modules);
            setActiveDropdownItem();
            setPageTitle();
            updateDropdownDot();

            // Only run version checks once per page visit, not on every tab switch.
            // Rescan button resets the flag to allow a fresh check.
            if (!_versionCheckDone) {
                _versionCheckDone = true;
                applyVersionUpdates(modules);
            }
        } catch (err) {
            console.error('[renderPage] ERROR', err);
        } finally {
            renderInProgress = false;
            _suppressObserver = false;
        }
    }

    // ── Card-level DOM cache ──
    // Caches the fully-rendered .ntmods-module-card content (header + section + all events)
    // so tab switches are a single DOM node swap with zero rebuilding.
    const _cardDomCache = new Map();
    let _activeCardCacheKey = '';

    function clearCardDomCache() {
        _cardDomCache.clear();
        _activeCardCacheKey = '';
    }

    // Save the current card's live children back into their cache entry
    // so they can be re-attached later.
    function stashCurrentCard(card) {
        if (!_activeCardCacheKey || !card.firstChild) return;
        let container = _cardDomCache.get(_activeCardCacheKey);
        if (!container) {
            container = document.createElement('div');
            _cardDomCache.set(_activeCardCacheKey, container);
        }
        while (card.firstChild) container.appendChild(card.firstChild);
    }

    // Fast tab switch — swaps cached card DOM nodes. Zero HTML parsing, zero event re-binding.
    let _switchTabSeq = 0;
    function switchTab(moduleId, sectionId) {
        if (_renderDebounceTimer) clearTimeout(_renderDebounceTimer);
        const seq = ++_switchTabSeq;

        // Immediately update tab highlight + show loading state (before debounce)
        const tabBar = document.querySelector('.ntmods-top-tabs');
        if (tabBar) {
            tabBar.querySelectorAll('.ntmods-top-tab').forEach((tab) => {
                tab.classList.toggle('is-active', tab.getAttribute('data-module-id') === moduleId);
            });
        }
        const card = document.querySelector('.ntmods-module-card');
        if (card) {
            // Show loading placeholder if the card swap takes time
            const loadingTimer = setTimeout(() => {
                if (_switchTabSeq === seq) {
                    card.innerHTML = '<div style="padding:40px;text-align:center;color:rgba(255,255,255,0.4);font-size:14px;">Loading\u2026</div>';
                }
            }, 80);
            card._loadingTimer = loadingTimer;
        }

        _renderDebounceTimer = setTimeout(() => {
            _renderDebounceTimer = null;
            if (card && card._loadingTimer) { clearTimeout(card._loadingTimer); card._loadingTimer = null; }

            try {
                const modules = _cachedModules;
                if (!card || !modules) {
                    renderPage(moduleId, sectionId);
                    return;
                }

                const activeModule = getModuleById(modules, moduleId);
                if (!activeModule) {
                    renderPage(moduleId, sectionId);
                    return;
                }
                const activeModuleSections = getRenderableSections(activeModule);
                const activeSection = activeModuleSections.find((s) => s.id === sectionId) || activeModuleSections[0] || null;
                const cardCacheKey = `${moduleId}::${sectionId || (activeSection ? activeSection.id : '')}`;

                // Don't re-render if we're already on this tab
                if (cardCacheKey === _activeCardCacheKey) return;

                // Update UI state
                writeUiState({
                    moduleId: activeModule.id,
                    sectionId: activeSection ? activeSection.id : ''
                });

                // Tab highlight already applied immediately (before debounce)

                // Suppress MutationObserver during DOM swap to prevent cascading handlers
                _suppressObserver = true;
                try {
                    // Stash current card's DOM nodes back into cache before swapping
                    stashCurrentCard(card);

                    // Check card DOM cache — if we have this module's card, just swap the node
                    const cachedCard = _cardDomCache.get(cardCacheKey);
                    if (cachedCard && cachedCard.firstChild) {
                        while (cachedCard.firstChild) card.appendChild(cachedCard.firstChild);
                        _activeCardCacheKey = cardCacheKey;
                        setPageTitle();
                        return;
                    }

                    // No cache — build from scratch
                    _activeCardCacheKey = cardCacheKey;
                    preloadFieldValues(activeModule.id, activeModule.source);
                    card.innerHTML = buildModuleCardInner(activeModule, activeSection);
                    bindContentEvents(modules, activeModule, activeSection);
                    setPageTitle();
                } finally {
                    _suppressObserver = false;
                }
            } catch (err) {
                console.error('[switchTab] ERROR', err);
            }
        }, 30);
    }

    function bindPageEvents(modules) {
        document.querySelectorAll('[data-module-id]').forEach((button) => {
            button.addEventListener('click', () => {
                // Don't switch tabs while reorder mode is active
                const tabBar = button.closest('.ntmods-top-tabs');
                if (tabBar && tabBar.classList.contains('is-reordering')) return;
                const moduleId = button.getAttribute('data-module-id') || '';
                const module = getModuleById(modules, moduleId);
                const moduleSections = getRenderableSections(module);
                const sectionId = moduleSections[0] ? moduleSections[0].id : '';
                switchTab(moduleId, sectionId);
            });
        });

        // ── Reorder mode toggle ──
        const tabBar = document.querySelector('.ntmods-top-tabs');
        const reorderBtn = document.querySelector('[data-action-key="toggle-reorder"]');

        function enterReorderMode() {
            if (!tabBar) return;
            tabBar.classList.add('is-reordering');
            if (reorderBtn) {
                reorderBtn.classList.add('is-active');
                reorderBtn.innerHTML = `
                    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
                    Done
                `;
            }
            let dragSrc = null;

            tabBar.querySelectorAll('[data-module-id]').forEach((tab) => {
                tab.draggable = true;

                tab._dragstart = (e) => {
                    dragSrc = tab;
                    tab.classList.add('ntmods-tab-dragging');
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/plain', tab.getAttribute('data-module-id'));
                };
                tab._dragend = () => {
                    if (dragSrc) dragSrc.classList.remove('ntmods-tab-dragging');
                    tabBar.querySelectorAll('.ntmods-tab-dragover').forEach((el) => el.classList.remove('ntmods-tab-dragover'));
                    dragSrc = null;
                };
                tab._dragover = (e) => {
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    if (tab === dragSrc) return;
                    tab.classList.add('ntmods-tab-dragover');
                };
                tab._dragleave = () => {
                    tab.classList.remove('ntmods-tab-dragover');
                };
                tab._drop = (e) => {
                    e.preventDefault();
                    tab.classList.remove('ntmods-tab-dragover');
                    if (!dragSrc || dragSrc === tab) return;
                    const allTabs = [...tabBar.querySelectorAll('[data-module-id]')];
                    const fromIdx = allTabs.indexOf(dragSrc);
                    const toIdx = allTabs.indexOf(tab);
                    if (fromIdx < toIdx) {
                        tab.parentNode.insertBefore(dragSrc, tab.nextSibling);
                    } else {
                        tab.parentNode.insertBefore(dragSrc, tab);
                    }
                    const newOrder = [...tabBar.querySelectorAll('[data-module-id]')].map(
                        (t) => t.getAttribute('data-module-id')
                    );
                    writePersistentJson(TAB_ORDER_KEY, newOrder);
                    showToast('Tab order saved.');
                };

                tab.addEventListener('dragstart', tab._dragstart);
                tab.addEventListener('dragend', tab._dragend);
                tab.addEventListener('dragover', tab._dragover);
                tab.addEventListener('dragleave', tab._dragleave);
                tab.addEventListener('drop', tab._drop);
            });
        }

        function exitReorderMode() {
            if (!tabBar) return;
            tabBar.classList.remove('is-reordering');
            if (reorderBtn) {
                reorderBtn.classList.remove('is-active');
                reorderBtn.innerHTML = `
                    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>
                    Reorder Tabs
                `;
            }
            tabBar.querySelectorAll('[data-module-id]').forEach((tab) => {
                tab.draggable = false;
                if (tab._dragstart) tab.removeEventListener('dragstart', tab._dragstart);
                if (tab._dragend) tab.removeEventListener('dragend', tab._dragend);
                if (tab._dragover) tab.removeEventListener('dragover', tab._dragover);
                if (tab._dragleave) tab.removeEventListener('dragleave', tab._dragleave);
                if (tab._drop) tab.removeEventListener('drop', tab._drop);
            });
        }

        if (reorderBtn) {
            reorderBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                if (tabBar && tabBar.classList.contains('is-reordering')) {
                    exitReorderMode();
                } else {
                    enterReorderMode();
                }
            });
        }

        const uiState = readUiState();
        const activeModule = getModuleById(modules, uiState.moduleId);
        const activeModuleSections = getRenderableSections(activeModule);
        const activeSection = activeModule
            ? activeModuleSections.find((section) => section.id === uiState.sectionId) || activeModuleSections[0]
            : null;

        bindContentEvents(modules, activeModule, activeSection);
    }

    // Bind events inside .ntmods-module-card only (sections, rescan, dismiss, settings fields).
    // Called by both full renderPage and fast switchTab.
    function bindContentEvents(modules, activeModule, activeSection) {
        const card = document.querySelector('.ntmods-module-card');
        if (!card) return;

        card.querySelectorAll('[data-section-id]').forEach((button) => {
            button.addEventListener('click', () => {
                const uiState = readUiState();
                switchTab(uiState.moduleId || '', button.getAttribute('data-section-id') || '');
            });
        });

        card.querySelectorAll('[data-action-key]').forEach((button) => {
            button.addEventListener('click', () => {
                const actionKey = button.getAttribute('data-action-key') || '';
                if (actionKey === 'rescan') {
                    if (_rescanInProgress) return;
                    _rescanInProgress = true;
                    showToast('Checking for updates...');
                    // Just refresh status — never delete manifests here.
                    // Old script versions don't have heartbeat support, so
                    // no heartbeat ≠ uninstalled. Those correctly show as
                    // "Not Active" / update required. The only way to remove
                    // a stale manifest is the per-script "Dismiss & Clear Data" button.
                    (async () => {
                        try {
                            const checkedCount = await refreshInstalledModuleVersions({
                                forceRefresh: true,
                                stampGlobalCheck: true
                            });
                            _versionCheckDone = true;
                            invalidateModuleCache();
                            renderPage('', '', true);
                            if (checkedCount > 0) {
                                showToast(`Update check complete. Checked ${checkedCount} installed mod${checkedCount === 1 ? '' : 's'}.`);
                            } else {
                                showToast('Update check complete. No installed public mods found.');
                            }
                        } catch {
                            showToast('Update check completed, but some version refreshes may have failed.');
                        } finally {
                            _rescanInProgress = false;
                        }
                    })();
                    return;
                }
                if (actionKey === 'clear-settings') {
                    const confirmed = window.confirm('Clear settings and caches for the installed public Nitro Type scripts? This keeps the scripts installed, but resets their preferences and cached data.');
                    if (!confirmed) return;
                    beginGlobalReset(modules);
                    return;
                }
                // Dispatch action event for external scripts
                const moduleId = activeModule?.id || '';
                document.dispatchEvent(new CustomEvent('ntcfg:action', {
                    detail: { script: moduleId, key: actionKey }
                }));
            });
        });

        // Dismiss stale module data
        card.querySelectorAll('[data-dismiss-stale]').forEach((btn) => {
            btn.addEventListener('click', () => {
                const moduleId = btn.getAttribute('data-dismiss-stale') || '';
                if (moduleId) dismissStaleModule(moduleId);
            });
        });

        if (activeModule?.source === 'manifest' && activeSection) {
            mountGenericSection(activeModule, activeSection);
            return;
        }

        const fieldMap = new Map((activeSection?.items || []).filter((item) => item.key).map((item) => [item.key, item]));

        card.querySelectorAll('[data-field-key]').forEach((input) => {
            const fieldKey = input.getAttribute('data-field-key') || '';
            const item = fieldMap.get(fieldKey);
            if (!activeModule || !item) return;

            const inputType = input.getAttribute('data-field-type') || item.type;
            const eventName = item.type === 'toggle' || inputType === 'select' ? 'change' : 'input';

            input.addEventListener(eventName, () => {
                if (inputType === 'color-text') {
                    const colorValue = String(input.value || '').trim();
                    const colorPicker = card.querySelector(`[data-field-key="${CSS.escape(fieldKey)}"][data-field-type="color"]`);
                    if (/^#[0-9A-Fa-f]{6}$/.test(colorValue)) {
                        setFieldValue(activeModule, item, colorValue.toUpperCase());
                        if (colorPicker) colorPicker.value = colorValue.toUpperCase();
                    }
                    return;
                }

                const nextValue = coerceInputValue(input, item);
                setFieldValue(activeModule, item, nextValue);

                if (inputType === 'color') {
                    const colorText = card.querySelector(`[data-field-key="${CSS.escape(fieldKey)}"][data-field-type="color-text"]`);
                    if (colorText) colorText.value = String(nextValue).toUpperCase();
                }
            });

            if (inputType === 'text' || inputType === 'number' || inputType === 'select') {
                input.addEventListener('change', () => {
                    const nextValue = coerceInputValue(input, item);
                    setFieldValue(activeModule, item, nextValue);
                });
            }
        });

    }

    function ensureModMenuPageRendered() {
        if (!isModMenuRoute()) return;

        const main = document.querySelector('main.structure-content');
        if (!main) {
            ensureMainWaitObserver();
            return;
        }

        const app = document.getElementById('ntmods-app');
        if (app) {
            stopMainWaitObserver();
            if (!main.classList.contains('custom-loaded')) {
                main.classList.add('custom-loaded');
            }
            setActiveDropdownItem();
            setPageTitle();
            return;
        }

        stopMainWaitObserver();
        renderPage();
    }

    function handlePage() {
        insertModsDropdownItem();
        if (!isModMenuRoute()) {
            clearModMenuPageSyncTimer();
            pendingModMenuRouteSync = false;
            stopMainWaitObserver();
            setActiveDropdownItem();
            updateDropdownDot();
            return;
        }

        ensureModMenuPageRendered();
    }

    function clearModMenuPageSyncTimer() {
        if (!modMenuPageSyncTimer) return;
        clearTimeout(modMenuPageSyncTimer);
        modMenuPageSyncTimer = null;
    }

    function scheduleModMenuPageSync({ syncRoute = false, delay = 0 } = {}) {
        pendingModMenuRouteSync = pendingModMenuRouteSync || !!syncRoute;
        clearModMenuPageSyncTimer();
        modMenuPageSyncTimer = setTimeout(() => {
            modMenuPageSyncTimer = null;
            const shouldSyncRoute = pendingModMenuRouteSync;
            pendingModMenuRouteSync = false;
            if (shouldSyncRoute) updateRouteStatus();
            handlePage();
        }, Math.max(0, Number(delay) || 0));
    }

    // Build a snapshot string of all module alive statuses for dirty-checking.
    function buildAliveSnapshot() {
        const parts = [];
        try {
            Object.keys(localStorage).forEach((key) => {
                if (key.startsWith(MANIFEST_PREFIX)) {
                    const manifest = readJson(key, null);
                    if (manifest?.id) parts.push(manifest.id + ':' + getScriptStatus(manifest.id));
                }
            });
        } catch { /* ignore */ }
        parts.sort();
        return parts.join('|');
    }

    function refreshActiveModMenuPage() {
        if (!isModMenuRoute()) return;
        if (document.getElementById('ntmods-app')) {
            invalidateModuleCache();
            renderPage('', '', true);
            _lastAliveSnapshot = buildAliveSnapshot();
            return;
        }
        ensureMainWaitObserver();
        scheduleModMenuPageSync({ syncRoute: false, delay: 0 });
    }

    function refreshActiveModMenuFieldViews() {
        if (!isModMenuRoute()) return;
        if (!document.getElementById('ntmods-app')) return;
        invalidateFieldCache();
        clearSectionDomCache();
        const uiState = readUiState();
        renderPage(uiState.moduleId || '', uiState.sectionId || '', false);
    }

    // Like refreshActiveModMenuPage, but only re-renders if module statuses changed.
    // Prevents unnecessary DOM rebuilds during heartbeat polling.
    function refreshIfStatusChanged() {
        if (!isModMenuRoute()) return;
        if (!document.getElementById('ntmods-app')) return;
        const snap = buildAliveSnapshot();
        if (snap === _lastAliveSnapshot) return;
        _lastAliveSnapshot = snap;
        invalidateModuleCache();
        renderPage('', '', true);
    }

    // No alive key clearing needed. getScriptStatus simply checks if the
    // alive key exists. Scripts write once on load, so any running script
    // has a key from the current page session.

    // Auto-cleanup removed: manifests are only cleaned via the Rescan button
    // (user-initiated). This prevents running scripts from losing their
    // manifests due to heartbeat timing races on page load. Scripts without
    // heartbeats correctly show as "Not Active" (outdated) until the user
    // rescans or the script heartbeats in.

    // Session-based alive detection: PAGE_SESSION_START (defined at top) records
    // when this page session started. getScriptStatus checks if alive keys are
    // from this session. No clearing needed — avoids race conditions with child
    // scripts that may run before or after the mod menu.

    let _mainWaitObserver = null;
    let _mainWaitTimer = null;
    const MAIN_WAIT_TIMEOUT_MS = 5000;

    function stopMainWaitObserver() {
        if (_mainWaitObserver) {
            try { _mainWaitObserver.disconnect(); } catch { /* ignore */ }
            _mainWaitObserver = null;
        }
        if (_mainWaitTimer) {
            clearTimeout(_mainWaitTimer);
            _mainWaitTimer = null;
        }
    }

    function ensureMainWaitObserver() {
        if (!isModMenuRoute()) {
            stopMainWaitObserver();
            return;
        }
        if (!_mainWaitObserver) {
            _mainWaitObserver = new MutationObserver(() => {
                if (!isModMenuRoute()) {
                    stopMainWaitObserver();
                    return;
                }

                const observedMain = document.querySelector('main.structure-content');
                if (!observedMain) return;

                const app = document.getElementById('ntmods-app');
                const isLoaded = observedMain.classList.contains('custom-loaded');
                if (!app || !isLoaded) {
                    scheduleModMenuPageSync({ syncRoute: false, delay: 0 });
                }
            });

            _mainWaitObserver.observe(document.documentElement, { childList: true, subtree: true });
        }

        const main = document.querySelector('main.structure-content');
        if (main) {
            const app = document.getElementById('ntmods-app');
            const isLoaded = main.classList.contains('custom-loaded');
            if (!app || !isLoaded) {
                scheduleModMenuPageSync({ syncRoute: false, delay: 0 });
            }
        }

        if (_mainWaitTimer) clearTimeout(_mainWaitTimer);
        _mainWaitTimer = setTimeout(() => {
            stopMainWaitObserver();
        }, MAIN_WAIT_TIMEOUT_MS);
    }

    function fastInject() {
        insertModsDropdownItem();
        if (isModMenuRoute()) {
            ensureMainWaitObserver();
        }
    }

    function onSpaNavigation() {
        scheduleModMenuPageSync({ syncRoute: true, delay: 0 });
        ensureDropdownAfterNav();
    }

    // Retry dropdown injection over a few seconds to survive React re-renders.
    // React may destroy and recreate the nav bar during SPA navigations,
    // so we keep checking even after successful injection.
    let _dropdownRetryTimer = null;
    function retryDropdownInjection(remaining) {
        if (remaining === undefined) remaining = 8; // ~4s of retries
        if (remaining <= 0) {
            updateDropdownDot(); // once after all retries, not on every retry
            return;
        }
        insertModsDropdownItem();
        _dropdownRetryTimer = setTimeout(() => retryDropdownInjection(remaining - 1), 500);
    }
    retryDropdownInjection();

    // Re-inject dropdown item after SPA navigations settle.
    // React may re-render the nav bar, removing our injected item.
    function ensureDropdownAfterNav() {
        if (_dropdownRetryTimer) clearTimeout(_dropdownRetryTimer);
        retryDropdownInjection();
    }

    // No delayed re-renders needed. Each child script writes its alive key
    // BEFORE dispatching ntcfg:manifest-updated, so the mod menu's re-render
    // (triggered by that event) already sees the correct alive status.

    function startModMenuRouteShell() {
        const ntRouteHelper = initNTRouteHelper(window);
        ntRouteHelper.subscribe(() => {
            onSpaNavigation();
        }, { immediate: false });

        document.addEventListener('ntcfg:manifest-updated', refreshActiveModMenuPage);
        window.addEventListener('storage', (event) => {
            const key = String(event?.key || '');
            if (!key) return;
            if (key.startsWith(MANIFEST_PREFIX)) {
                refreshActiveModMenuPage();
                return;
            }
            if (key.startsWith('ntcfg:') || key.startsWith(PREVIEW_VALUE_PREFIX)) {
                refreshActiveModMenuFieldViews();
            }
        });

        fastInject();
        scheduleModMenuPageSync({ syncRoute: true, delay: 0 });
    }

    function startModMenuDropdownLifecycle() {
        retryDropdownInjection();
    }

    function startModMenuVersionCheckLifecycle() {
        setTimeout(() => {
            try {
                if (!isModMenuRoute()) return;
                const lastCheck = parseInt(localStorage.getItem('ntmods:version-check-ts') || '0', 10);
                if (Date.now() - lastCheck < VERSION_CACHE_TTL_MS) return;
                const modulesToCheck = getInstalledModulesForVersionCheck();
                if (modulesToCheck.length === 0) return;
                (async () => {
                    await refreshInstalledModuleVersions({
                        forceRefresh: true,
                        stampGlobalCheck: true
                    });
                    if (isModMenuRoute()) {
                        _versionCheckDone = true;
                        invalidateModuleCache();
                        renderPage('', '', true);
                    }
                })();
            } catch { /* ignore */ }
        }, 3000);
    }

    startModMenuRouteShell();
    startModMenuDropdownLifecycle();
    startModMenuVersionCheckLifecycle();
})();