JanitorAI Character Card Scraper

Extract character card with "T" key (WHILE IN CHAT PAGE) and save as .txt, .png, or .json (proxy required)

As of 14. 07. 2025. See the latest version.

// ==UserScript==
// @name         JanitorAI Character Card Scraper
// @version      3.7
// @description  Extract character card with "T" key (WHILE IN CHAT PAGE) and save as .txt, .png, or .json (proxy required)
// @match        https://janitorai.com/*
// @icon         https://images.dwncdn.net/images/t_app-icon-l/p/46413ec0-e1d8-4eab-a0bc-67eadabb2604/3920235030/janitor-ai-logo
// @grant        none
// @namespace    https://sleazyfork.org/en/scripts/537206-janitorai-character-card-scraper
// @inject-into  auto
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function runInPageContext() {

        "use strict";

        /* ============================
           ==      VARIABLES        ==
           ============================ */
        let hasInitialized = false;
        let viewActive = false;
        let shouldInterceptNext = false;
        let networkInterceptActive = false;
        let exportFormat = null;
        let chatData = null;
        let currentTab = sessionStorage.getItem("lastActiveTab") || "export";
        let useChatNameForName =
            localStorage.getItem("useChatNameForName") === "true" || false;
        let applyCharToken = localStorage.getItem("applyCharToken") !== "false";
        let filenameTemplate =
            localStorage.getItem("filenameTemplateDraft") ||
            localStorage.getItem("filenameTemplate") ||
            "{name}";
        // Migration: Update old default for existing users
        const storedSavePath = localStorage.getItem("savePathTemplate");
        if (storedSavePath === "images/{creator}") {
            localStorage.setItem("savePathTemplate", "cards/{creator}");
        }

        // Simple boolean: false = show prefill, true = user has applied changes
        // Default to false for fresh installs, only true if user explicitly applied changes
        let userChangedSavePath =
            localStorage.getItem("userChangedSavePath") === "true";

        let savePathTemplate = userChangedSavePath ?
            localStorage.getItem("savePathTemplate") || "" :
            "cards/{creator}";
        let animationTimeouts = [];
        let currentActiveTab = "export"; // Track current tab state
        let restorationInProgress = false; // Prevent multiple simultaneous restorations
        let guiElement = null;
        let cachedUserName = "";
        let cachedProxyUrl = "";
        sessionStorage.removeItem("char_export_scroll");
        sessionStorage.removeItem("char_settings_scroll");
        const ANIMATION_DURATION = 150; // Animation duration for modal open/close in ms
        const TAB_ANIMATION_DURATION = 300; // Animation duration for tab switching in ms
        const TAB_BUTTON_DURATION = 250; // Animation duration for tab button effects
        const BUTTON_ANIMATION = 200; // Animation duration for format buttons
        const TOGGLE_ANIMATION = 350; // Animation duration for toggle switch
        const ACTIVE_TAB_COLOR = "#0080ff"; // Color for active tab indicator
        const INACTIVE_TAB_COLOR = "transparent"; // Color for inactive tab indicator
        const BUTTON_COLOR = "#3a3a3a"; // Base color for buttons
        const BUTTON_HOVER_COLOR = "#4a4a4a"; // Hover color for buttons
        const BUTTON_ACTIVE_COLOR = "#0070dd"; // Active color for buttons when clicked
        const TOOLTIP_SLIDE_FROM_RIGHT = true; // true = slide towards right (default). Set false for slide-left variant.
        const TOOLTIP_SLIDE_OFFSET = 10; // px the tooltip travels during slide animation

        const blankMeta = {
            creatorUrl: "",
            characterVersion: "",
            characterCardUrl: "",
            name: "",
            creatorNotes: "",
            personality: "",
            scenario: "",
            firstMessage: "",
            exampleDialogs: "",
            definitionExposed: false,
        };

        const characterMetaCache = {
            id: null,
            useChatNameForName: false,
            ...blankMeta,
        };

        let jaiStateObserver;

        /**
         * @param {HTMLScriptElement} scriptNode
         */
        function extractAndCacheInitialData(scriptNode) {
            if (cachedUserName && cachedProxyUrl) {
                if (jaiStateObserver) {
                    jaiStateObserver.disconnect();
                }
                return;
            }

            try {
                const match = scriptNode.textContent.match(/JSON\.parse\("([\s\S]*?)"\)/);
                if (match && match[1]) {
                    const storeState = JSON.parse(JSON.parse(`"${match[1]}"`));

                    const profile = storeState?.user?.profile;
                    if (profile?.name && !cachedUserName) {
                        cachedUserName = profile.name.trim();
                    }

                    const config = storeState?.user?.config;
                    if (config?.open_ai_reverse_proxy && !cachedProxyUrl) {
                        cachedProxyUrl = config.open_ai_reverse_proxy;
                    }

                    if (cachedUserName && cachedProxyUrl && jaiStateObserver) {
                        jaiStateObserver.disconnect();
                    }
                }
            } catch (e) {}
        }

        // STRATEGY 1: Immediate Scan
        // Look for the data right away in case it's already on the page.
        document.querySelectorAll('script').forEach(script => {
            if (script.textContent.includes('window._storeState_')) {
                extractAndCacheInitialData(script);
            }
        });

        // STRATEGY 2: Asynchronous Watcher
        // Set up an observer to catch the data if it's added to the page later.
        jaiStateObserver = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.tagName === 'SCRIPT' && node.textContent.includes('window._storeState_')) {
                        extractAndCacheInitialData(node);
                    }
                }
            }
        });

        // Start observing the entire page. The helper function will handle disconnecting.
        jaiStateObserver.observe(document.documentElement, {
            childList: true,
            subtree: true
        });

        interceptNetwork();

        /* ============================
           ==      UTILITIES        ==
           ============================ */
        function makeElement(tag, attrs = {}, styles = {}) {
            const el = document.createElement(tag);
            Object.entries(attrs).forEach(([key, value]) => (el[key] = value));

            if (styles) {
                Object.entries(styles).forEach(([key, value]) => (el.style[key] = value));
            }
            return el;
        }

        /**
         * Extract creator profile URL from fetched character page document.
         * Looks for the profile anchor used across pages.
         * @param {Document} doc – HTML document parsed from character page.
         * @returns {string} Absolute URL or empty string.
         */
        function getCreatorUrlFromDoc(doc) {
            const link = doc.querySelector("a.chakra-link.css-15sl5jl");
            if (link) {
                const href = link.getAttribute("href");
                if (href) return `https://janitorai.com${href}`;
            }
            return "";
        }

        async function saveFile(filename, blob) {
            const useFileSystemAPI =
                localStorage.getItem("useFileSystemAccess") === "true";

            if (useFileSystemAPI && "showDirectoryPicker" in window) {
                try {
                    const directoryHandle = window.selectedDirectoryHandle;

                    if (directoryHandle) {
                        const userChangedSavePath =
                            localStorage.getItem("userChangedSavePath") === "true";
                        const savePathTemplate = userChangedSavePath ?
                            localStorage.getItem("savePathTemplate") || "" :
                            "cards/{creator}";

                        let savePath = savePathTemplate;

                        console.log("[DEBUG] Original save path template:", savePathTemplate);
                        console.log("[DEBUG] Available chatData:", chatData);

                        const meta = await getCharacterMeta();
                        const tokens = await getFilenameTokens(meta);

                        console.log("[DEBUG] Available tokens:", tokens);

                        Object.entries(tokens).forEach(([tokenKey, tokenValue]) => {
                            if (tokenValue && String(tokenValue).trim() !== "") {
                                const regex = new RegExp(`\\{${escapeRegExp(tokenKey)}\\}`, "g");
                                const oldSavePath = savePath;
                                savePath = savePath.replace(regex, String(tokenValue));
                                if (oldSavePath !== savePath) {
                                    console.log(`[DEBUG] Replaced {${tokenKey}} with:`, tokenValue);
                                }
                            }
                        });

                        console.log("[DEBUG] Final save path:", savePath);

                        savePath = savePath.replace(/^\/+|\/+$/g, "");
                        const pathSegments = savePath ?
                            savePath.split("/").filter((segment) => segment.length > 0) : [];

                        let currentDir = directoryHandle;
                        for (const segment of pathSegments) {
                            try {
                                currentDir = await currentDir.getDirectoryHandle(segment, {
                                    create: true,
                                });
                            } catch (error) {
                                console.error(
                                    `Failed to create/access directory ${segment}:`,
                                    error,
                                );
                                regularDownload(filename, blob);
                                return;
                            }
                        }

                        let finalFilename = filename;
                        let counter = 1;
                        let fileHandle;

                        while (true) {
                            try {
                                await currentDir.getFileHandle(finalFilename);
                                const lastDot = filename.lastIndexOf(".");
                                if (lastDot === -1) {
                                    finalFilename = `${filename} (${counter})`;
                                } else {
                                    const nameWithoutExt = filename.substring(0, lastDot);
                                    const extension = filename.substring(lastDot);
                                    finalFilename = `${nameWithoutExt} (${counter})${extension}`;
                                }
                                counter++;
                            } catch (error) {
                                break;
                            }
                        }

                        fileHandle = await currentDir.getFileHandle(finalFilename, {
                            create: true,
                        });
                        const writable = await fileHandle.createWritable();
                        await writable.write(blob);
                        await writable.close();

                        const successMessage = `Saved to: ${pathSegments.length > 0 ? pathSegments.join("/") + "/" : ""}${finalFilename}`;
                        console.log(successMessage);

                        const notification = document.createElement("div");
                        notification.style.cssText = `
              position: fixed;
              top: 20px;
              right: 20px;
              background: #4CAF50;
              color: white;
              padding: 12px 20px;
              border-radius: 6px;
              z-index: 10001;
              box-shadow: 0 4px 12px rgba(0,0,0,0.3);
              font-size: 14px;
              font-weight: bold;
              opacity: 0;
              transform: translateX(100%);
              transition: all 300ms ease;
            `;
                        notification.textContent = `✓ ${successMessage}`;
                        document.body.appendChild(notification);

                        requestAnimationFrame(() => {
                            notification.style.opacity = "1";
                            notification.style.transform = "translateX(0)";
                        });

                        setTimeout(() => {
                            notification.style.opacity = "0";
                            notification.style.transform = "translateX(100%)";
                            setTimeout(() => notification.remove(), 300);
                        }, 3000);

                        return;
                    }
                } catch (error) {
                    console.error("FileSystemAccess API failed:", error);
                    const errorNotification = document.createElement("div");
                    errorNotification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: #f44336;
            color: white;
            padding: 12px 20px;
            border-radius: 6px;
            z-index: 10001;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            font-size: 14px;
            font-weight: bold;
            opacity: 0;
            transform: translateX(100%);
            transition: all 300ms ease;
          `;
                    errorNotification.textContent = `⚠ FileSystem save failed, using download instead`;
                    document.body.appendChild(errorNotification);

                    requestAnimationFrame(() => {
                        errorNotification.style.opacity = "1";
                        errorNotification.style.transform = "translateX(0)";
                    });

                    setTimeout(() => {
                        errorNotification.style.opacity = "0";
                        errorNotification.style.transform = "translateX(100%)";
                        setTimeout(() => errorNotification.remove(), 300);
                    }, 3000);
                }
            }

            regularDownload(filename, blob);
        }

        function regularDownload(filename, blob) {
            const url = URL.createObjectURL(blob);
            const a = makeElement("a", {
                href: url,
                download: filename,
            });
            a.addEventListener("click", (e) => {
                e.stopPropagation();
            });
            document.body.appendChild(a);
            a.click();
            a.remove();
            URL.revokeObjectURL(url);
        }

        function getCharacterCardUrl(id) {
            return `https://janitorai.com/characters/${id}`;
        }

        function escapeRegExp(s) {
            return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        }

        function extractTagContent(sys, charName) {
            if (!charName || !sys) return "";
            const variants = [];
            const trimmed = charName.trim();
            variants.push(trimmed);
            const collapsed = trimmed.replace(/\s+/g, " ");
            if (collapsed !== trimmed) variants.push(collapsed);
            if (trimmed.includes(" ")) variants.push(trimmed.replace(/\s+/g, "_"));

            for (const name of variants) {
                const escName = escapeRegExp(name);
                const regex = new RegExp(
                    `<${escName}(?:\\s[^>]*)?\\s*>([\\s\\S]*?)<\\/${escName}\\s*>`,
                    "i",
                );
                const m = sys.match(regex);
                if (m && m[1] != null) {
                    return m[1].trim();
                }
                const openTagRx = new RegExp(`<${escName}(?:\\s[^>]*)?\\s*>`, "i");
                const closeTagRx = new RegExp(`<\\/${escName}\\s*>`, "i");
                const openMatch = openTagRx.exec(sys);
                const closeMatch = closeTagRx.exec(sys);
                if (openMatch && closeMatch && closeMatch.index > openMatch.index) {
                    const start = openMatch.index + openMatch[0].length;
                    const end = closeMatch.index;
                    return sys.substring(start, end).trim();
                }
                try {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(
                        `<root>${sys}</root>`,
                        "application/xml",
                    );
                    const elems = doc.getElementsByTagName(name);
                    if (elems.length) {
                        return elems[0].textContent.trim();
                    }
                } catch (_) {}
            }
            return "";
        }

        function stripWatermark(text) {
            if (!text) return "";
            const lines = text.split(/\r?\n/);
            const filtered = lines.filter((l) => {
                const t = l.trim();
                return !(/^created/i.test(t) && /janitorai\.com"?$/i.test(t));
            });
            return filtered.join("\n").trim();
        }

        // === Early Chat Data Prefetch ===
        function findChatId() {
            const direct = window.location.href.match(/\/chats\/(\d+)/);
            if (direct) return direct[1];
            const scripts = document.querySelectorAll("script");
            for (const s of scripts) {
                const txt = s.textContent;
                if (!txt || !txt.includes("window._storeState_")) continue;
                const m = txt.match(/JSON\.parse\("([\s\S]*?)"\)/);
                if (m && m[1]) {
                    try {
                        const decoded = JSON.parse(`"${m[1]}"`); // unescape
                        const obj = JSON.parse(decoded);
                        let cid = null;
                        const walk = (o) => {
                            if (!o || typeof o !== "object" || cid) return;
                            if (
                                Object.prototype.hasOwnProperty.call(o, "chatId") &&
                                typeof o.chatId === "number"
                            ) {
                                cid = o.chatId;
                                return;
                            }
                            for (const k in o) walk(o[k]);
                        };
                        walk(obj);
                        if (cid) return cid;
                    } catch (_) {}
                }
            }
            return null;
        }

        function getAuthHeader() {
            const matches = document.cookie.match(/sb-auth-auth-token\.[^=]+=([^;]+)/g);
            if (matches && matches.length) {
                for (const seg of matches) {
                    const rawVal = seg.substring(seg.indexOf("=") + 1);
                    const hdr = extractBearer(rawVal);
                    if (hdr) return hdr;
                }
            }
            return null;

            function extractBearer(val) {
                let raw = decodeURIComponent(val);
                if (raw.startsWith("base64-")) raw = raw.slice(7);
                if (/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(raw)) {
                    console.log("[getAuthHeader] using raw JWT");
                    return `Bearer ${raw}`;
                }
                try {
                    const json = JSON.parse(atob(raw));
                    if (json && json.access_token) {
                        console.log("[getAuthHeader] using access_token from JSON");
                        return `Bearer ${json.access_token}`;
                    }
                } catch (err) {
                    /* ignore */
                }
                return null;
            }
            const m = document.cookie.match(/sb-auth-auth-token\.\d+=([^;]+)/);
            if (!m) return null;
            let raw = decodeURIComponent(m[1]);
            if (raw.startsWith("base64-")) raw = raw.slice(7);
            try {
                const json = JSON.parse(atob(raw));
                if (json.access_token) return `Bearer ${json.access_token}`;
            } catch (err) {
                if (/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(raw)) {
                    return `Bearer ${raw}`;
                }
            }
            return null;
        }

        function getAppVersion() {
            const scr = [...document.querySelectorAll("script[src]")].find((s) =>
                /\bv=([\w.-]+)/.test(s.src),
            );
            if (scr) {
                const m = scr.src.match(/\bv=([\w.-]+)/);
                if (m) return m[1];
            }
            return "2025-06-24.81a918c33";
        }

        async function prefetchChatData() {
            if (chatData) return;
            const chatId = findChatId();
            if (!chatId) return;
            const endpoint = `https://janitorai.com/hampter/chats/${chatId}`;
            const appVer = getAppVersion();
            const auth = getAuthHeader();
            console.log("[prefetchChatData] auth:", auth);
            const baseHeaders = {
                "x-app-version": appVer,
                accept: "application/json, text/plain, */*",
            };
            if (auth) baseHeaders["Authorization"] = auth;
            console.log("[prefetchChatData] request headers", baseHeaders);
            // First try with cookies + headers
            try {
                let res = await fetch(endpoint, {
                    method: "GET",
                    credentials: "include",
                    headers: baseHeaders,
                });
                if (res.status === 401) {
                    // Retry with Authorization header if available
                    const auth = getAuthHeader();
                    if (auth) {
                        res = await fetch(endpoint, {
                            method: "GET",
                            credentials: "include",
                            headers: {
                                ...baseHeaders,
                                Authorization: auth,
                            },
                        });
                    }
                }
                if (res.ok) {
                    const json = await res.json();
                    if (json && json.character) {
                        chatData = json;
                        console.log("[prefetchChatData] chatData pre-fetched");
                    }
                }
            } catch (err) {
                console.warn("[prefetchChatData] failed:", err);
            }
        }

        function tokenizeNames(text, charName, userName) {
            if (!text) return text;
            const parts = text.split("\n\n");
            const [cRx, uRx] = [charName, userName].map((n) =>
                n ? escapeRegExp(n) : "",
            );

            for (let i = 0, l = parts.length; i < l; ++i) {
                if (
                    !/^==== (Name|Chat Name|Initial Message|Character Card|Creator) ====/.test(
                        parts[i],
                    )
                ) {
                    // Always tokenize {{user}} with the corrected regex
                    if (uRx) {
                        const userRegex = new RegExp(`\\b${uRx}('s?)?(?!\\w)`, "gi");
                        parts[i] = parts[i].replace(userRegex, (match, suffix) => {
                            return `{{user}}${suffix || ""}`;
                        });
                    }

                    // Conditionally tokenize {{char}} based on the toggle
                    if (applyCharToken) {
                        if (cRx) {
                            const charRegex = new RegExp(`\\b${cRx}('s?)?(?!\\w)`, "gi");
                            parts[i] = parts[i].replace(charRegex, (match, suffix) => {
                                return `{{char}}${suffix || ""}`;
                            });
                        }
                    } else if (charName) {
                        parts[i] = parts[i].replace(/\{\{char\}\}/gi, charName);
                    }
                }
            }
            return parts.join("\n\n");
        }

        function tokenizeField(text, charName, userName) {
            if (!text) return text;
            let out = text;
            const esc = (n) => escapeRegExp(n);

            // Always apply {{user}} tokenization with the corrected regex
            if (userName) {
                const userRegex = new RegExp(`\\b${esc(userName)}('s?)?(?!\\w)`, "gi");
                out = out.replace(userRegex, (match, suffix) => `{{user}}${suffix || ""}`);
            }

            // Conditionally apply {{char}} tokenization
            if (applyCharToken && charName) {
                const charRegex = new RegExp(`\\b${esc(charName)}('s?)?(?!\\w)`, "gi");
                out = out.replace(charRegex, (match, suffix) => `{{char}}${suffix || ""}`);
            } else if (charName) {
                // De-tokenize if setting is off
                out = out.replace(/\{\{char\}\}/gi, charName);
            }

            return out;
        }

        /**
         * Adds a token value to the filename template input field
         * @param {string} token - Token to add (e.g., "id", "name", etc.)
         * @param {HTMLInputElement} inputField - The template input field
         */
        function addTokenToTemplate(token, inputField) {
            if (!inputField || !token) return;

            const currentValue = inputField.value || "";
            const cursorPos = inputField.selectionStart || currentValue.length;

            // Add space before token if there's content and no trailing space or slash
            const needsSpace =
                currentValue &&
                !currentValue.endsWith(" ") &&
                !currentValue.endsWith("/") &&
                cursorPos === currentValue.length;
            const tokenToAdd = needsSpace ? ` {${token}}` : `{${token}}`;

            const newValue =
                currentValue.slice(0, cursorPos) +
                tokenToAdd +
                currentValue.slice(cursorPos);
            inputField.value = newValue;

            // Update cursor position
            const newCursorPos = cursorPos + tokenToAdd.length;
            inputField.setSelectionRange(newCursorPos, newCursorPos);
            inputField.focus();

            // Trigger change event
            inputField.dispatchEvent(new Event("input", {
                bubbles: true
            }));
        }

        /**
         * Creates a token button for the filename template UI
         * @param {string} token - Token name
         * @param {string} label - Display label for the button
         * @param {HTMLInputElement} inputField - Template input field
         * @returns {HTMLElement} - Button element
         */
        function createTokenButton(token, label, inputElement, isSavePath = false) {
            const button = makeElement(
                "button", {
                    textContent: label,
                    type: "button",
                }, {
                    background: "#3a3a3a",
                    border: "1px solid #555",
                    color: "#fff",
                    padding: "4px 8px",
                    borderRadius: "4px",
                    cursor: "pointer",
                    fontSize: "11px",
                    transition: "all 150ms ease",
                    margin: "2px",
                },
            );

            button.addEventListener("mouseover", () => {
                button.style.background = "#4a4a4a";
                button.style.borderColor = "#666";
            });

            button.addEventListener("mouseout", () => {
                button.style.background = "#3a3a3a";
                button.style.borderColor = "#555";
            });

            button.addEventListener("mousedown", (e) => {
                // Only trigger on left mouse button
                if (e.button !== 0) return;

                e.preventDefault();
                addTokenToTemplate(token, inputElement);

                // Green flash and pulse animation for left click
                button.style.background = "#4CAF50";
                button.style.borderColor = "#66BB6A";
                button.style.transform = "scale(1.1)";
                button.style.boxShadow = "0 0 10px rgba(76, 175, 80, 0.6)";

                setTimeout(() => {
                    button.style.background = "#3a3a3a";
                    button.style.borderColor = "#555";
                    button.style.transform = "scale(1)";
                    button.style.boxShadow = "none";
                }, 100);
            });

            // Right mousedown for immediate removal
            button.addEventListener("mousedown", (e) => {
                // Only trigger on right mouse button
                if (e.button !== 2) return;

                e.preventDefault();
                e.stopPropagation();

                const currentValue = inputElement.value;
                const tokenPattern = `{${token}}`;

                if (currentValue.includes(tokenPattern)) {
                    // Find the first occurrence and remove only one instance with intelligent spacing
                    let newValue = currentValue;
                    const tokenRegex = new RegExp(`\\{${token}\\}`, "");

                    // Check if token has a space before it
                    const tokenWithSpaceRegex = new RegExp(` \\{${token}\\}`, "");

                    if (tokenWithSpaceRegex.test(newValue)) {
                        // Remove token with preceding space
                        newValue = newValue.replace(tokenWithSpaceRegex, "");
                    } else {
                        // Remove just the token
                        newValue = newValue.replace(tokenRegex, "");
                    }

                    // Clean up any double spaces that might result
                    newValue = newValue.replace(/\s+/g, " ").trim();

                    inputElement.value = newValue;

                    // Trigger input event to update state
                    if (isSavePath) {
                        savePathTemplate = newValue;
                    } else {
                        filenameTemplate = newValue;
                        localStorage.setItem("filenameTemplateDraft", filenameTemplate);
                    }

                    inputElement.dispatchEvent(new Event("input", {
                        bubbles: true
                    }));

                    // Immediate red flash and pulse animation
                    button.style.background = "#f44336";
                    button.style.borderColor = "#ff6666";
                    button.style.transform = "scale(1.1)";
                    button.style.boxShadow = "0 0 10px rgba(244, 67, 54, 0.6)";

                    setTimeout(() => {
                        button.style.background = "#3a3a3a";
                        button.style.borderColor = "#555";
                        button.style.transform = "scale(1)";
                        button.style.boxShadow = "none";
                    }, 100);
                } else {
                    // Budge animation when token not found
                    button.style.transform = "translateX(-3px)";
                    setTimeout(() => {
                        button.style.transform = "translateX(3px)";
                        setTimeout(() => {
                            button.style.transform = "translateX(0)";
                        }, 100);
                    }, 100);
                }
            });

            // Prevent context menu from appearing
            button.addEventListener("contextmenu", (e) => {
                e.preventDefault();
                e.stopPropagation();
            });

            return button;
        }

        /**
         * Creates a standardized toggle component
         * @param {string} key - localStorage key
         * @param {string} label - Display label
         * @param {string} tooltip - Tooltip text
         * @param {boolean} defaultValue - Default checked state
         * @returns {Object} - Object containing container and input elements
         */
        function createToggle(key, label, tooltip, defaultValue = false) {
            const container = makeElement(
                "div", {}, {
                    display: "flex",
                    alignItems: "center",
                    marginBottom: "8px",
                    marginTop: "10px",
                    padding: "15px",
                    background: "#2a2a2a",
                    borderRadius: "10px",
                    gap: "12px",
                    boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
                    isolation: "isolate",
                    contain: "layout style paint",
                    border: "1px solid #444",
                    background: "linear-gradient(135deg, #2a2a2a 0%, #2e2e2e 100%)",
                    transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
                    cursor: "pointer",
                },
            );

            const wrapper = makeElement(
                "div", {
                    className: "toggle-wrapper",
                }, {
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "space-between",
                    width: "100%",
                    cursor: "pointer",
                    position: "relative",
                    isolation: "isolate",
                    transition: "all 0.2s ease",
                },
            );

            const labelElement = makeElement(
                "span", {
                    textContent: label,
                }, {
                    fontSize: "13px",
                    color: "#fff",
                    order: "2",
                    textAlign: "left",
                    flex: "1",
                    paddingLeft: "10px",
                    wordBreak: "break-word",
                    lineHeight: "1.4",
                },
            );

            const toggle = makeElement(
                "label", {
                    className: "switch",
                }, {
                    position: "relative",
                    display: "inline-block",
                    width: "40px",
                    height: "24px",
                    order: "1",
                    margin: "0",
                    flexShrink: "0",
                    borderRadius: "24px",
                    boxShadow: "0 1px 3px rgba(0,0,0,0.2) inset",
                    transition: `all ${TOGGLE_ANIMATION}ms cubic-bezier(0.4, 0, 0.2, 1)`,
                    cursor: "pointer",
                },
            );

            const slider = makeElement(
                "span", {
                    className: "slider round",
                }, {
                    position: "absolute",
                    cursor: "pointer",
                    top: "0",
                    left: "0",
                    right: "0",
                    bottom: "0",
                    backgroundColor: defaultValue ? ACTIVE_TAB_COLOR : "#ccc",
                    transition: `all ${TOGGLE_ANIMATION}ms cubic-bezier(0.34, 1.56, 0.64, 1)`,
                    borderRadius: "24px",
                    overflow: "hidden",
                    boxShadow: defaultValue ? "0 0 8px rgba(0, 128, 255, 0.3)" : "none",
                },
            );

            const sliderShine = makeElement(
                "div", {}, {
                    position: "absolute",
                    top: "0",
                    left: "0",
                    width: "100%",
                    height: "100%",
                    background: "linear-gradient(135deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)",
                    opacity: "0.5",
                    transition: `opacity ${TOGGLE_ANIMATION}ms ease`,
                },
            );
            slider.appendChild(sliderShine);

            const sliderBefore = makeElement(
                "span", {
                    className: "slider-before",
                }, {
                    position: "absolute",
                    content: '""',
                    height: "16px",
                    width: "16px",
                    left: "4px",
                    bottom: "4px",
                    backgroundColor: "white",
                    transition: `transform ${TOGGLE_ANIMATION}ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow ${TOGGLE_ANIMATION}ms ease`,
                    borderRadius: "50%",
                    transform: defaultValue ? "translateX(16px)" : "translateX(0)",
                    boxShadow: defaultValue ?
                        "0 0 2px rgba(0,0,0,0.2), 0 0 5px rgba(0,128,255,0.3)" : "0 0 2px rgba(0,0,0,0.2)",
                },
            );

            const input = makeElement(
                "input", {
                    type: "checkbox",
                    checked: defaultValue,
                }, {
                    opacity: "0",
                    width: "0",
                    height: "0",
                    position: "absolute",
                },
            );

            input.addEventListener("change", (e) => {
                const isChecked = e.target.checked;
                localStorage.setItem(key, isChecked);

                slider.style.backgroundColor = isChecked ? ACTIVE_TAB_COLOR : "#ccc";
                slider.style.boxShadow = isChecked ?
                    "0 0 8px rgba(0, 128, 255, 0.3)" :
                    "none";
                sliderBefore.style.transform = isChecked ?
                    "translateX(16px)" :
                    "translateX(0)";
                sliderBefore.style.boxShadow = isChecked ?
                    "0 0 2px rgba(0,0,0,0.2), 0 0 8px rgba(0,128,255,0.5)" :
                    "0 0 2px rgba(0,0,0,0.2)";

                wrapper.classList.toggle("active", isChecked);

                container.style.transform = "scale(1.02)";
                container.style.borderColor = isChecked ?
                    "rgba(0, 128, 255, 0.4)" :
                    "#444";
                container.style.boxShadow = isChecked ?
                    "0 4px 12px rgba(0, 128, 255, 0.15)" :
                    "0 2px 4px rgba(0,0,0,0.1)";

                setTimeout(() => {
                    container.style.transform = "scale(1)";
                }, 150);
            });

            slider.appendChild(sliderBefore);
            toggle.appendChild(input);
            toggle.appendChild(slider);

            container.addEventListener("mouseenter", () => {
                if (!container.style.transform.includes("scale")) {
                    container.style.transform = "translateY(-1px) scale(1.005)";
                    container.style.boxShadow = "0 6px 16px rgba(0,0,0,0.15)";
                }
            });

            container.addEventListener("mouseleave", () => {
                if (!container.style.transform.includes("1.02")) {
                    container.style.transform = "translateY(0) scale(1)";
                    const isActive = input.checked;
                    container.style.boxShadow = isActive ?
                        "0 4px 12px rgba(0, 128, 255, 0.15)" :
                        "0 2px 4px rgba(0,0,0,0.1)";
                }
            });

            wrapper.addEventListener("click", (e) => {
                e.preventDefault();
                input.checked = !input.checked;
                const event = new Event("change");
                input.dispatchEvent(event);
                document.body.focus();
            });

            wrapper.appendChild(labelElement);
            labelElement.setAttribute("data-tooltip", tooltip);
            wrapper.appendChild(toggle);
            container.appendChild(wrapper);

            return {
                container,
                input,
                wrapper
            };
        }

        /* ============================
           ==          UI           ==
           ============================ */
        function createUI() {
            if (guiElement && document.body.contains(guiElement)) {
                return;
            }

            animationTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
            animationTimeouts = [];

            viewActive = true;

            const gui = makeElement(
                "div", {
                    id: "char-export-gui",
                }, {
                    position: "fixed",
                    top: "50%",
                    left: "50%",
                    transform: "translate(-50%, -50%) scale(0.95)",
                    background: "#222",
                    color: "white",
                    padding: "15px 20px 7px",
                    borderRadius: "8px",
                    boxShadow: "0 0 20px rgba(0,0,0,0.5)",
                    zIndex: "10000",
                    textAlign: "center",
                    width: "min(480px, 90vw)",
                    minHeight: "400px",
                    maxHeight: "85vh",
                    overflow: "hidden",
                    display: "flex",
                    flexDirection: "column",
                    boxSizing: "border-box",
                    opacity: "0",
                    transition: `opacity ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1)`,
                },
            );

            guiElement = gui;
            const unselectStyle = document.createElement("style");
            unselectStyle.textContent = `#char-export-gui, #char-export-gui * {
    user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
  }
  #char-export-gui {
    backdrop-filter: blur(6px);
    background: rgba(34,34,34,0.92);
    border: none;
    border-radius: 8px;
  }
  #char-export-overlay {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    background: rgba(0,0,0,0.5); z-index: 9998;
  }
  #char-export-gui #char-export-tooltip {
    font-size: 12px; background: #222; color: #fff;
    padding: 6px 10px; border-radius: 4px; pointer-events: none;
  }
  #char-export-gui button {
    background: #333; color: #fff; border: none;
    border-radius: 4px; padding: 8px 12px;
    transition: background 200ms ease, transform 200ms ease;
  }
  #char-export-gui button:hover:not(:disabled) {
    background: #444; transform: translateY(-1px);
  }
  #char-export-gui button:disabled {
    opacity: 0.6; cursor: not-allowed;
  }
  #char-export-gui button:focus {
    outline: none;
  }

  /* Enhanced input field styling */
  #char-export-gui input[type="text"] {
    transition: border-color 300ms cubic-bezier(0.4, 0, 0.2, 1) !important;
    border-radius: 6px !important;
  }

  #char-export-gui input[type="text"]:not(.applying-changes):hover {
    border-color: #777 !important;
  }

  #char-export-gui input[type="text"]:not(.applying-changes):focus {
    border-color: #888 !important;
  }

  /* Ensure apply animation takes priority */
  #char-export-gui input.applying-changes {
    border-color: #4CAF50 !important;
  }

  /* Disable all browser tooltips */
  #char-export-gui input,
  #char-export-gui input:hover,
  #char-export-gui input:focus,
  #char-export-gui textarea,
  #char-export-gui textarea:hover,
  #char-export-gui textarea:focus {
    title: "" !important;
  }

  #char-export-gui input[title],
  #char-export-gui textarea[title] {
    title: "" !important;
  }

/* Enhanced toggle container styling */
.toggle-wrapper {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
  border-radius: 8px !important;
}

.toggle-wrapper:hover {
  transform: translateY(-1px) scale(1.005) !important;
  box-shadow: 0 6px 16px rgba(0,0,0,0.15) !important;
  background: linear-gradient(135deg, rgba(128, 128, 128, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%) !important;
}

.toggle-wrapper.active {
  border-color: rgba(0, 128, 255, 0.4) !important;
  box-shadow: 0 4px 12px rgba(0, 128, 255, 0.15) !important;
}

  /* Token button styling */
  #char-export-gui button[type="button"] {
    transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1) !important;
  }

  #char-export-gui button[type="button"]:hover {
    transform: translateY(-2px) scale(1.05) !important;
    box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
  }

  /* Directory button styling */
  .directory-button {
    transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1) !important;
  }

  .directory-button:hover {
    box-shadow: 0 0 8px rgba(74, 144, 226, 0.4) !important;
    background: #4a90e2 !important;
  }

  .directory-button:active {
    transform: scale(0.98) !important;
  }

  /* Custom overlay scrollbar - hide native scrollbar completely */
  #settings-tab {
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE/Edge */
    margin-right: -20px;
    padding-right: 20px;
  }

  #settings-tab::-webkit-scrollbar {
    display: none; /* Chrome/Safari */
  }

  /* Custom scrollbar overlay container */
  .custom-scrollbar-container {
    position: relative;
  }

  .custom-scrollbar-track {
    position: absolute;
    top: 0;
    right: -6px;
    width: 4px;
    height: 100%;
    background: rgba(68, 68, 68, 0.1);
    border-radius: 2px;
    opacity: 0.6;
    visibility: visible;
    transition: opacity 50ms ease, box-shadow 100ms ease;
    z-index: 1000;
    pointer-events: auto;
  }

  .custom-scrollbar-track.visible {
    opacity: 0.6;
    visibility: visible;
  }

  .custom-scrollbar-track:hover {
    opacity: 1;
    background: rgba(68, 68, 68, 0.2);
    box-shadow: 0 0 2px rgba(136, 136, 136, 0.2);
  }

  .custom-scrollbar-track.expanded {
    opacity: 1;
    background: rgba(68, 68, 68, 0.2);
    box-shadow: 0 0 4px rgba(136, 136, 136, 0.3);
  }

  .custom-scrollbar-thumb {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    background: rgba(136, 136, 136, 0.7);
    border-radius: 2px;
    border: none;
    transition: background 50ms ease;
    cursor: pointer;
    min-height: 20px;
  }

  .custom-scrollbar-thumb:hover {
    background: rgba(170, 170, 170, 0.8);
  }

  .custom-scrollbar-thumb:active,
  .custom-scrollbar-thumb.dragging {
    background: rgba(200, 200, 200, 0.9);
  }
  }`;
            document.head.appendChild(unselectStyle);

            const tabContainer = makeElement(
                "div", {}, {
                    display: "flex",
                    justifyContent: "center",
                    marginBottom: "20px",
                    borderBottom: "1px solid #444",
                    paddingBottom: "12px",
                    width: "100%",
                },
            );

            const tabsWrapper = makeElement(
                "div", {}, {
                    display: "flex",
                    justifyContent: "center",
                    width: "100%",
                    maxWidth: "min(400px, 100%)",
                    margin: "0 auto",
                },
            );

            const createTabButton = (text, isActive) => {
                const button = makeElement(
                    "button", {
                        textContent: text,
                    }, {
                        background: "transparent",
                        border: "none",
                        color: "#fff",
                        padding: "8px 20px",
                        cursor: "pointer",
                        margin: "0 5px",
                        fontWeight: "bold",
                        flex: "1",
                        textAlign: "center",
                        position: "relative",
                        overflow: "hidden",
                        transition: `opacity ${TAB_BUTTON_DURATION}ms ease, transform ${TAB_BUTTON_DURATION}ms ease, color ${TAB_BUTTON_DURATION}ms ease`,
                    },
                );

                const indicator = makeElement(
                    "div", {}, {
                        position: "absolute",
                        bottom: "0",
                        left: "0",
                        width: "100%",
                        height: "2px",
                        background: isActive ? ACTIVE_TAB_COLOR : INACTIVE_TAB_COLOR,
                        transition: `transform ${TAB_BUTTON_DURATION}ms ease, background-color ${TAB_BUTTON_DURATION}ms ease`,
                    },
                );

                if (!isActive) {
                    button.style.opacity = "0.7";
                    indicator.style.transform = "scaleX(0.5)";
                }

                button.appendChild(indicator);
                return {
                    button,
                    indicator,
                };
            };

            const {
                button: exportTab,
                indicator: exportIndicator
            } = createTabButton(
                "Export",
                true,
            );
            const {
                button: settingsTab,
                indicator: settingsIndicator
            } =
            createTabButton("Settings", false);

            exportTab.onmouseover = () => {
                if (currentTab !== "export") {
                    exportTab.style.opacity = "1";
                    exportTab.style.transform = "translateY(-2px)";
                    exportIndicator.style.transform = "scaleX(0.8)";
                }
            };
            exportTab.onmouseout = () => {
                if (currentTab !== "export") {
                    exportTab.style.opacity = "0.7";
                    exportTab.style.transform = "";
                    exportIndicator.style.transform = "scaleX(0.5)";
                }
            };

            settingsTab.onmouseover = () => {
                if (currentTab !== "settings") {
                    settingsTab.style.opacity = "1";
                    settingsTab.style.transform = "translateY(-2px)";
                    settingsIndicator.style.transform = "scaleX(0.8)";
                }
            };
            settingsTab.onmouseout = () => {
                if (currentTab !== "settings") {
                    settingsTab.style.opacity = "0.7";
                    settingsTab.style.transform = "";
                    settingsIndicator.style.transform = "scaleX(0.5)";
                }
            };

            tabsWrapper.appendChild(exportTab);
            tabsWrapper.appendChild(settingsTab);
            tabContainer.appendChild(tabsWrapper);
            gui.appendChild(tabContainer);
            /* ========= Dynamic Tooltip ========= */
            (() => {
                let tEl;
                const show = (target) => {
                    const msg = target.getAttribute("data-tooltip");
                    if (!msg) return;
                    if (!tEl) {
                        tEl = document.createElement("div");
                        tEl.id = "char-export-tooltip";
                        tEl.id = "char-export-tooltip";
                        tEl.style.cssText =
                            "position:fixed;padding:6px 10px;font-size:12px;background:#222;color:#fff;border-radius:4px;white-space:nowrap;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.4);opacity:0;transition:opacity 200ms ease,transform 200ms ease;z-index:10002;";
                        const offset = TOOLTIP_SLIDE_FROM_RIGHT ?
                            -TOOLTIP_SLIDE_OFFSET :
                            TOOLTIP_SLIDE_OFFSET;
                        tEl.style.transform = `translateX(${offset}px)`;
                        document.body.appendChild(tEl);
                    }
                    tEl.textContent = msg;
                    const guiRect = gui.getBoundingClientRect();
                    const tgtRect = target.getBoundingClientRect();
                    tEl.style.top =
                        tgtRect.top + tgtRect.height / 2 - tEl.offsetHeight / 2 + "px";
                    tEl.style.left = guiRect.right + 10 + "px";
                    requestAnimationFrame(() => {
                        tEl.style.opacity = "1";
                        tEl.style.transform = "translateX(0)";
                    });
                };
                const hide = () => {
                    if (!tEl) return;
                    tEl.style.opacity = "0";
                    const offsetHide = TOOLTIP_SLIDE_FROM_RIGHT ?
                        -TOOLTIP_SLIDE_OFFSET :
                        TOOLTIP_SLIDE_OFFSET;
                    tEl.style.transform = `translateX(${offsetHide}px)`;
                };
                gui.addEventListener("mouseover", (e) => {
                    const tgt = e.target.closest("[data-tooltip]");
                    if (tgt) show(tgt);
                });
                gui.addEventListener("mouseout", (e) => {
                    const tgt = e.target.closest("[data-tooltip]");
                    if (tgt) hide();
                });
                window.addEventListener("keydown", (ev) => {
                    hide();
                    if ((ev.key === "t" || ev.key === "T") && tEl) {
                        tEl.style.opacity = "0";
                        const offsetHide = TOOLTIP_SLIDE_FROM_RIGHT ?
                            -TOOLTIP_SLIDE_OFFSET :
                            TOOLTIP_SLIDE_OFFSET;
                        tEl.style.transform = `translateX(${offsetHide}px)`;
                    }
                });
            })();

            const exportContent = makeElement(
                "div", {
                    id: "export-tab",
                }, {
                    maxHeight: "60vh",
                    overflowY: "auto",
                    padding: "0 5px 10px 0",
                    display: "flex",
                    flexDirection: "column",
                    justifyContent: "center",
                },
            );

            const title = makeElement(
                "h1", {
                    textContent: "Export Character Card",
                }, {
                    margin: "-10px 0 25px 0",
                    fontSize: "24px",
                    paddingTop: "0px",
                    textAlign: "center",
                    fontWeight: "bold",
                    color: "#fff",
                    borderBottom: "2px solid #444",
                    paddingBottom: "8px",
                },
            );
            exportContent.appendChild(title);

            const buttonContainer = makeElement(
                "div", {}, {
                    display: "flex",
                    gap: "12px",
                    justifyContent: "center",
                    marginBottom: "8px",
                    marginTop: "12px",
                },
            );

            ["TXT", "PNG", "JSON"].forEach((format) => {
                const type = format.toLowerCase();

                const button = makeElement(
                    "button", {
                        textContent: format,
                    }, {
                        background: BUTTON_COLOR,
                        border: "none",
                        color: "white",
                        padding: "12px 24px",
                        borderRadius: "8px",
                        cursor: "pointer",
                        fontWeight: "bold",
                        position: "relative",
                        overflow: "hidden",
                        flex: "1",
                        maxWidth: "min(120px, 30%)",
                        transition: `all ${BUTTON_ANIMATION}ms ease`,
                        boxShadow: "0 3px 6px rgba(0,0,0,0.15)",
                        transform: "translateY(0)",
                        fontSize: "14px",
                    },
                );

                const shine = makeElement(
                    "div", {}, {
                        position: "absolute",
                        top: "0",
                        left: "0",
                        width: "100%",
                        height: "100%",
                        background: "linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 60%)",
                        transform: "translateX(-100%)",
                        transition: `transform ${BUTTON_ANIMATION * 1.5}ms ease-out`,
                        pointerEvents: "none",
                    },
                );
                button.appendChild(shine);

                button.onmouseover = () => {
                    button.style.background = BUTTON_HOVER_COLOR;
                    button.style.transform = "translateY(-3px) scale(1.02)";
                    button.style.boxShadow = "0 6px 20px rgba(0,0,0,0.25)";
                    button.style.filter = "brightness(1.1)";
                    shine.style.transform = "translateX(100%)";
                };

                button.onmouseout = () => {
                    button.style.background = BUTTON_COLOR;
                    button.style.transform = "translateY(0) scale(1)";
                    button.style.boxShadow = "0 3px 6px rgba(0,0,0,0.15)";
                    button.style.filter = "brightness(1)";
                    shine.style.transform = "translateX(-100%)";
                };

                button.onmousedown = () => {
                    button.style.transform = "translateY(0) scale(0.98)";
                    button.style.boxShadow = "0 2px 4px rgba(0,0,0,0.3)";
                    button.style.background = BUTTON_ACTIVE_COLOR;
                    button.style.filter = "brightness(1.2)";
                };

                button.onmouseup = () => {
                    button.style.transform = "translateY(-3px) scale(1.02)";
                    button.style.boxShadow = "0 6px 20px rgba(0,0,0,0.25)";
                    button.style.background = BUTTON_HOVER_COLOR;
                    button.style.filter = "brightness(1.1)";
                };

                button.onclick = (e) => {
                    const rect = button.getBoundingClientRect();
                    const x = e.clientX - rect.left;
                    const y = e.clientY - rect.top;

                    const ripple = makeElement(
                        "div", {}, {
                            position: "absolute",
                            borderRadius: "50%",
                            backgroundColor: "rgba(255,255,255,0.4)",
                            width: "5px",
                            height: "5px",
                            transform: "scale(1)",
                            opacity: "1",
                            animation: "ripple 600ms linear",
                            pointerEvents: "none",
                            top: `${y}px`,
                            left: `${x}px`,
                            marginLeft: "-2.5px",
                            marginTop: "-2.5px",
                        },
                    );

                    button.appendChild(ripple);

                    exportFormat = type;
                    closeV();
                    extraction();

                    setTimeout(() => ripple.remove(), 600);
                };

                buttonContainer.appendChild(button);
            });

            if (!document.getElementById("char-export-style")) {
                const style = document.createElement("style");
                style.id = "char-export-style";
                style.textContent = `
            @keyframes ripple {
              to {
                transform: scale(30);
                opacity: 0; pointer-events: none;
              }
            }
          `;
                document.head.appendChild(style);
            }

            if (!document.getElementById("char-export-responsive")) {
                const responsiveStyle = document.createElement("style");
                responsiveStyle.id = "char-export-responsive";
                responsiveStyle.textContent = `
      @media (max-width: 600px) {
        #char-export-gui {
          width: 95vw !important;
          height: 90vh !important;
          max-height: 90vh !important;
          padding: 10px 15px 5px !important;
        }

        #content-wrapper {
          min-height: 280px !important;
        }

        #export-tab, #settings-tab {
          padding: 10px !important;
        }

        .toggle-wrapper {
          flex-direction: column;
          align-items: flex-start !important;
          gap: 8px !important;
        }

        .toggle-wrapper span {
          order: 1 !important;
          padding-left: 0 !important;
          text-align: left !important;
        }

        .toggle-wrapper label {
          order: 2 !important;
          align-self: flex-end;
        }
      }

      @media (max-height: 600px) {
        #char-export-gui {
          max-height: 95vh !important;
        }
      }
        `;
                document.head.appendChild(responsiveStyle);
            }

            if (!document.getElementById("char-export-settings-scroll")) {
                const settingsScrollStyle = document.createElement("style");
                settingsScrollStyle.id = "char-export-settings-scroll";
                settingsScrollStyle.textContent = `
      #settings-tab {
        scrollbar-width: thin;
        scrollbar-color: #555 #2a2a2a;
      }
      #settings-tab::-webkit-scrollbar {
        width: 6px;
      }
      #settings-tab::-webkit-scrollbar-track {
        background: #2a2a2a;
        border-radius: 3px;
      }
      #settings-tab::-webkit-scrollbar-thumb {
        background: #555;
        border-radius: 3px;
      }
      #settings-tab::-webkit-scrollbar-thumb:hover {
        background: #666;
      }

      /* Enhanced hover effects */
      .toggle-wrapper {
        transition: background 200ms ease;
      }
      .toggle-wrapper:hover {
        background: rgba(255, 255, 255, 0.02);
      }

      /* Enhanced section hover effects */
      #settings-tab > div {
        transition: box-shadow 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
                    border 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
                    transform 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
        border: 1px solid transparent;
        background: #2a2a2a;
      }
      #settings-tab > div:hover {
        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2),
                    0 4px 12px rgba(0,0,0,0.15);
        border: 1px solid rgba(128, 128, 128, 0.3);
        transform: translateY(-2px) scale(1.01);
        background: linear-gradient(135deg, #2a2a2a 0%, #323232 100%);
      }

      /* Enhanced toggle hover effects */
      .toggle-wrapper {
        transition: background 250ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
                    box-shadow 250ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
        border-radius: 8px;
        contain: layout style;
        isolation: isolate;
      }
      .toggle-wrapper:hover {
        background: linear-gradient(135deg, rgba(128, 128, 128, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
        box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.2);
      }

      /* Disable all tooltips inside UI */
      #char-export-gui [data-tooltip]:hover::before,
      #char-export-gui [data-tooltip]:hover::after,
      #char-export-gui input:hover::before,
      #char-export-gui input:hover::after,
      #char-export-gui input[data-tooltip-disabled]:hover::before,
      #char-export-gui input[data-tooltip-disabled]:hover::after {
        display: none !important;
        opacity: 0 !important;
      }

      /* Slot machine animation overflow clipping */
      .template-input-container {
        overflow: hidden !important;
        position: relative !important;
      }

      .template-apply-btn {
        will-change: transform, opacity, filter;
      }

      /* Applied text animation clipping */
      .template-input-container span {
        will-change: transform, opacity, filter;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
      }

      /* Disable browser tooltips on input */
      #char-export-gui input[data-tooltip-disabled] {
        pointer-events: auto !important;
      }
      #char-export-gui input[data-tooltip-disabled]:hover::before,
      #char-export-gui input[data-tooltip-disabled]:hover::after {
        display: none !important;
      }
    `;
                document.head.appendChild(settingsScrollStyle);
            }

            if (!document.getElementById("char-export-tooltip-style")) {
                const tooltipStyle = document.createElement("style");
                tooltipStyle.id = "char-export-tooltip-style";
                tooltipStyle.textContent = `
  [data-tooltip] {
    position: relative;
  }
  [data-tooltip]::before {
    content: '';
    position: absolute;
    left: calc(100% + 4px);
    top: 50%;
    transform: translateY(-50%) scaleY(0.5);
    border: solid transparent;
    border-width: 6px 6px 6px 0;
    border-left-color: #333;
    opacity: 0; pointer-events: none;
    transition: opacity 150ms ease;
    z-index: 10001;
  }
  [data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    left: calc(100% + 10px);
    top: 50%;
    transform: translateY(-50%) scale(0.8);
    background: #333;
    color: #fff;
    padding: 6px 10px;
    border-radius: 4px;
    white-space: nowrap;
    opacity: 0; pointer-events: none;
    transition: opacity 150ms ease, transform 150ms ease;
    z-index: 10001;
  }
  [data-tooltip]:hover::before,
  [data-tooltip]:hover::after {
    opacity: 1;
    transform: translateY(-50%) scale(1);
  }

  .toggle-wrapper {
    border-radius: 8px;
    transition: background 0.2s ease, box-shadow 0.2s ease;
  }
  .toggle-wrapper.active {
    background: transparent;
    box-shadow: none;
  }
              `;
                document.head.appendChild(tooltipStyle);
                if (!document.getElementById("char-export-scrollbar-style")) {
                    const scrollStyle = document.createElement("style");
                    scrollStyle.id = "char-export-scrollbar-style";
                    scrollStyle.textContent = `
  #content-wrapper { overflow: visible; }
  #settings-tab { overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; }
  #settings-tab::-webkit-scrollbar { width: 0; height: 0; }
  `;
                    document.head.appendChild(scrollStyle);
                    if (!document.getElementById("char-export-tooltip-override-style")) {
                        const overrideStyle = document.createElement("style");
                        overrideStyle.id = "char-export-tooltip-override-style";
                        overrideStyle.textContent = `
  [data-tooltip]::before { transform: translateY(-50%) translateX(-6px); transition: transform 200ms ease, opacity 200ms ease; }
  [data-tooltip]::after { transform: translateY(-50%) translateX(-10px); transition: transform 200ms ease, opacity 200ms ease; }
  [data-tooltip]:hover::before { transform: translateY(-50%) translateX(0); opacity:1; }
  [data-tooltip]:hover::after { transform: translateY(-50%) translateX(0); opacity:1; }

[data-tooltip]::before,[data-tooltip]::after{display:none !important;}
@keyframes toggle-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } }
.toggle-wrapper.pulse { animation: toggle-pulse 300ms ease; }

.switch .slider-before { transition: transform 200ms ease, box-shadow 200ms ease; }
.toggle-wrapper.active .slider-before { box-shadow: 0 0 2px rgba(0,0,0,0.2), 0 0 8px rgba(0,128,255,0.5); }
.toggle-wrapper.active .slider { background-color: #0080ff; }
              `;
                        document.head.appendChild(overrideStyle);
                    }
                }
            }

            exportContent.appendChild(buttonContainer);

            const contentWrapper = makeElement(
                "div", {
                    id: "content-wrapper",
                }, {
                    minHeight: "320px",
                    width: "100%",
                    overflow: "visible",
                    display: "flex",
                    flexDirection: "column",
                    justifyContent: "flex-start",
                    position: "relative",
                    flex: "1",
                },
            );
            gui.appendChild(contentWrapper);

            const tabContentStyles = {
                height: "100%",
                width: "100%",
                overflowY: "auto",
                overflowX: "hidden",
                padding: "15px",
                paddingTop: "10px",
                position: "absolute",
                top: "0",
                left: "0",
                opacity: "1",
                transform: "scale(1)",
                transition: `opacity ${TAB_ANIMATION_DURATION}ms ease, transform ${TAB_ANIMATION_DURATION}ms ease`,
                boxSizing: "border-box",
            };

            Object.assign(exportContent.style, tabContentStyles);
            exportContent.style.overflowY = "visible";
            exportContent.style.overflowX = "hidden";
            exportContent.style.display = "flex";
            exportContent.style.flexDirection = "column";
            exportContent.style.alignItems = "center";

            const settingsContent = makeElement(
                "div", {
                    id: "settings-tab",
                    style: "display: none;",
                },
                tabContentStyles,
            );

            contentWrapper.appendChild(exportContent);
            contentWrapper.appendChild(settingsContent);
            const savedExportScroll = parseInt(
                sessionStorage.getItem("char_export_scroll") || "0",
                10,
            );
            exportContent.scrollTop = savedExportScroll;
            const savedSettingsScroll = parseInt(
                sessionStorage.getItem("char_settings_scroll") || "0",
                10,
            );
            settingsContent.scrollTop = savedSettingsScroll;
            settingsContent.addEventListener("scroll", () =>
                sessionStorage.setItem("char_settings_scroll", settingsContent.scrollTop),
            );

            requestAnimationFrame(() => {
                exportContent.scrollTop = savedExportScroll;
                settingsContent.scrollTop = savedSettingsScroll;
            });

            const settingsTitle = makeElement(
                "h1", {
                    textContent: "Export Settings",
                }, {
                    margin: "-10px 0 25px 0",
                    fontSize: "24px",
                    paddingTop: "0px",
                    textAlign: "center",
                    fontWeight: "bold",
                    color: "#fff",
                    borderBottom: "2px solid #444",
                    paddingBottom: "12px",
                },
            );
            settingsContent.appendChild(settingsTitle);

            /**
             * Custom overlay scrollbar implementation
             * Creates a fully custom scrollbar that overlays the content
             * @param {HTMLElement} element - Target scrollable element
             */
            const createCustomScrollbar = (element) => {
                let track, thumb;
                let isDragging = false;
                let startY = 0;
                let startScrollTop = 0;
                let isVisible = false;
                let hideTimeout;

                const createScrollbarElements = () => {
                    // Create track
                    track = makeElement("div", {
                        className: "custom-scrollbar-track"
                    }, {});

                    // Create thumb
                    thumb = makeElement("div", {
                        className: "custom-scrollbar-thumb"
                    }, {});

                    track.appendChild(thumb);

                    // Add to container
                    const container = element.parentElement;
                    if (container) {
                        container.classList.add("custom-scrollbar-container");
                        container.style.position = "relative";
                        container.appendChild(track);
                    }

                    track.classList.remove("expanded", "visible");
                    thumb.classList.remove("dragging", "scrolling");
                    track.style.opacity = "0.6";
                    thumb.style.transition = "background 50ms ease";
                };

                const updateThumbSize = () => {
                    if (!track || !thumb) return;

                    const containerHeight = element.clientHeight;
                    const contentHeight = element.scrollHeight;
                    const scrollRatio = containerHeight / contentHeight;

                    if (scrollRatio >= 1 || contentHeight <= containerHeight) {
                        track.classList.remove("visible");
                        track.style.visibility = "hidden";
                        isVisible = false;
                        return;
                    }

                    track.classList.add("visible");
                    track.style.visibility = "visible";
                    isVisible = true;

                    const thumbHeight = Math.max(20, containerHeight * scrollRatio);
                    thumb.style.height = `${thumbHeight}px`;

                    updateThumbPosition();
                };

                const updateThumbPosition = () => {
                    if (!thumb || !isVisible) return;

                    const containerHeight = element.clientHeight;
                    const contentHeight = element.scrollHeight;
                    const trackHeight = track.clientHeight;
                    const thumbHeight = thumb.clientHeight;
                    const maxThumbTop = trackHeight - thumbHeight;
                    const scrollRatio =
                        element.scrollTop / (contentHeight - containerHeight);
                    const thumbTop = scrollRatio * maxThumbTop;

                    thumb.style.transition = "none";
                    thumb.style.transform = `translateY(${thumbTop}px)`;
                    requestAnimationFrame(() => {
                        thumb.style.transition = "background 50ms ease";
                    });
                };

                const onThumbMouseDown = (e) => {
                    e.preventDefault();
                    e.stopPropagation();

                    isDragging = true;
                    startY = e.clientY;
                    startScrollTop = element.scrollTop;

                    thumb.classList.add("dragging");
                    track.classList.add("expanded");
                    document.addEventListener("mousemove", onMouseMove);
                    document.addEventListener("mouseup", onMouseUp);
                    document.body.style.userSelect = "none";
                };

                const onMouseMove = (e) => {
                    if (!isDragging) return;

                    const deltaY = e.clientY - startY;
                    const trackHeight = track.clientHeight;
                    const thumbHeight = thumb.clientHeight;
                    const contentHeight = element.scrollHeight;
                    const containerHeight = element.clientHeight;

                    const scrollRange = contentHeight - containerHeight;
                    const thumbRange = trackHeight - thumbHeight;
                    const scrollRatio = deltaY / thumbRange;
                    const scrollDelta = scrollRatio * scrollRange;

                    element.scrollTop = Math.max(
                        0,
                        Math.min(startScrollTop + scrollDelta, scrollRange),
                    );
                };

                const onMouseUp = () => {
                    isDragging = false;
                    thumb.classList.remove("dragging");
                    track.classList.remove("expanded");
                    document.removeEventListener("mousemove", onMouseMove);
                    document.removeEventListener("mouseup", onMouseUp);
                    document.body.style.userSelect = "";
                };

                const onTrackClick = (e) => {
                    if (e.target === thumb) return;

                    const trackRect = track.getBoundingClientRect();
                    const clickY = e.clientY - trackRect.top;
                    const trackHeight = track.clientHeight;
                    const thumbHeight = thumb.clientHeight;
                    const containerHeight = element.clientHeight;
                    const contentHeight = element.scrollHeight;

                    const scrollRatio =
                        (clickY - thumbHeight / 2) / (trackHeight - thumbHeight);
                    const scrollTop = scrollRatio * (contentHeight - containerHeight);

                    element.scrollTop = Math.max(
                        0,
                        Math.min(scrollTop, contentHeight - containerHeight),
                    );
                };

                const onScroll = () => {
                    updateThumbPosition();
                };

                const onResize = () => {
                    updateThumbSize();
                };

                const init = () => {
                    createScrollbarElements();

                    const onWheel = () => {
                        updateThumbPosition();
                    };

                    // Event listeners
                    element.addEventListener("scroll", onScroll);
                    element.addEventListener("wheel", onWheel, {
                        passive: true
                    });
                    thumb.addEventListener("mousedown", onThumbMouseDown);
                    track.addEventListener("click", onTrackClick);

                    // Resize observer for content changes
                    const resizeObserver = new ResizeObserver(onResize);
                    resizeObserver.observe(element);

                    // Force clean initial state with proper timing
                    setTimeout(() => {
                        track.classList.remove("expanded", "visible");
                        thumb.classList.remove("dragging", "scrolling");
                        track.style.opacity = "0.6";
                        thumb.style.transition = "background 50ms ease";
                    }, 0);

                    updateThumbSize();
                    requestAnimationFrame(() => {
                        updateThumbSize();
                        // Additional state reset after first frame
                        track.classList.remove("expanded");
                    });
                    setTimeout(() => {
                        updateThumbSize();
                        // Final state cleanup
                        track.classList.remove("expanded");
                    }, 50);

                    return {
                        destroy: () => {
                            element.removeEventListener("scroll", onScroll);
                            element.removeEventListener("wheel", onWheel);
                            if (thumb) thumb.removeEventListener("mousedown", onThumbMouseDown);
                            if (track) {
                                track.removeEventListener("click", onTrackClick);
                                track.remove();
                            }
                            resizeObserver.disconnect();
                            clearTimeout(element.scrollTimeout);
                        },
                    };
                };

                return {
                    init
                };
            };

            // Initialize custom scrollbar
            const settingsScrollbar = createCustomScrollbar(settingsContent);

            setTimeout(() => {
                settingsScrollbar.init();
            }, 10);

            // Filename Template Section
            const templateSection = makeElement(
                "div", {}, {
                    marginTop: "20px",
                    marginBottom: "15px",
                    padding: "15px",
                    background: "#2a2a2a",
                    borderRadius: "10px",
                    boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
                    transition: "all 200ms ease",
                    cursor: "default",
                },
            );

            const templateTitle = makeElement(
                "h3", {
                    textContent: "Filename",
                }, {
                    margin: "0 0 15px 0",
                    fontSize: "16px",
                    color: "#fff",
                    fontWeight: "bold",
                    textAlign: "center",
                },
            );
            templateSection.appendChild(templateTitle);

            const templateInputContainer = makeElement(
                "div", {
                    className: "template-input-container",
                }, {
                    marginBottom: "12px",
                    position: "relative",
                    overflow: "hidden",
                },
            );

            const templateInput = makeElement(
                "input", {
                    type: "text",
                    value: filenameTemplate,
                    placeholder: "Enter filename template",
                }, {
                    width: "100%",
                    padding: "10px 55px 10px 12px",
                    border: "1px solid #555",
                    borderRadius: "6px",
                    background: "#1a1a1a",
                    color: "#fff",
                    fontSize: "13px",
                    boxSizing: "border-box",
                    transition: "border-color 200ms ease, box-shadow 200ms ease",
                    outline: "none",
                },
            );

            // Disable tooltips completely
            templateInput.setAttribute("title", "");
            templateInput.setAttribute("data-tooltip-disabled", "true");
            templateInput.removeAttribute("title");
            templateInput.addEventListener("mouseenter", (e) => {
                e.target.removeAttribute("title");
                e.target.title = "";
            });

            // Override browser tooltip styles
            templateInput.style.setProperty("pointer-events", "auto", "important");
            templateInput.addEventListener("mouseover", (e) => {
                e.preventDefault();
                e.stopPropagation();
            });

            let hasChanges =
                filenameTemplate !== "" &&
                filenameTemplate !==
                (localStorage.getItem("filenameTemplate") || "{name}");
            let originalTemplate = localStorage.getItem("filenameTemplate") || "{name}";

            // Auto-restore functionality
            const restoreTemplate = () => {
                const savedDraft = localStorage.getItem("filenameTemplateDraft");
                if (savedDraft && savedDraft !== templateInput.value) {
                    templateInput.value = savedDraft;
                    filenameTemplate = savedDraft;
                    hasChanges =
                        filenameTemplate !== "" &&
                        filenameTemplate !==
                        (localStorage.getItem("filenameTemplate") || "{name}");

                    // Update apply button state
                    const applyBtn = templateSection.querySelector(".template-apply-btn");
                    if (applyBtn && hasChanges) {
                        applyBtn.style.opacity = "1";
                        applyBtn.style.color = "#4CAF50";
                        applyBtn.style.filter = "drop-shadow(0 0 4px rgba(76, 175, 80, 0.6))";
                    }
                }
            };

            templateInput.addEventListener("input", (e) => {
                filenameTemplate = e.target.value;
                localStorage.setItem("filenameTemplateDraft", filenameTemplate);
                hasChanges =
                    filenameTemplate !== "" &&
                    filenameTemplate !==
                    (localStorage.getItem("filenameTemplate") || "{name}");

                // Update apply button state with smooth animation
                const applyBtn = templateSection.querySelector(".template-apply-btn");
                if (applyBtn && !isAnimating) {
                    applyBtn.style.transition = "all 200ms ease";
                    if (hasChanges) {
                        applyBtn.style.opacity = "1";
                        applyBtn.style.color = "#4CAF50";
                        applyBtn.style.filter = "drop-shadow(0 0 4px rgba(76, 175, 80, 0.6))";
                    } else {
                        applyBtn.style.opacity = "0.5";
                        applyBtn.style.color = "#666";
                        applyBtn.style.filter = "none";
                    }
                }
            });

            // Restore on tab switch and UI open
            restoreTemplate();

            let isEnterHeld = false;
            let isMouseHeld = false;

            templateInput.addEventListener("keydown", (e) => {
                if (e.key === "Enter" && !isEnterHeld) {
                    isEnterHeld = true;
                    e.preventDefault();
                    if (hasChanges && !isAnimating) {
                        performApplyAction();
                    } else if (!hasChanges && !isAnimating) {
                        startGrayHold();
                    }
                }
            });

            templateInput.addEventListener("keyup", (e) => {
                if (e.key === "Enter") {
                    isEnterHeld = false;
                    if (!hasChanges && !isAnimating) {
                        endGrayHold();
                    }
                }
            });

            templateInput.addEventListener("mouseover", () => {
                if (
                    !isAnimating &&
                    document.activeElement !== templateInput &&
                    templateInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    templateInput.style.borderColor = "#777";
                }
            });

            templateInput.addEventListener("mouseout", () => {
                if (
                    !isAnimating &&
                    document.activeElement !== templateInput &&
                    templateInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    templateInput.style.borderColor = "#555";
                }
            });

            templateInput.addEventListener("focus", () => {
                if (
                    !isAnimating &&
                    templateInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    templateInput.style.borderColor = "#888";
                }
            });

            templateInput.addEventListener("blur", () => {
                if (
                    !isAnimating &&
                    templateInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    templateInput.style.borderColor = "#555";
                }
            });

            templateInputContainer.appendChild(templateInput);

            const applyButton = makeElement(
                "button", {
                    textContent: "✓",
                    className: "template-apply-btn",
                }, {
                    position: "absolute",
                    right: "8px",
                    top: "50%",
                    transform: "translateY(-50%)",
                    background: "transparent",
                    border: "none",
                    color: "#666",
                    fontSize: "14px",
                    cursor: "pointer",
                    padding: "4px",
                    borderRadius: "3px",
                    opacity: "0.5",
                    transition: "all 200ms ease",
                },
            );

            let isAnimating = false;

            const performApplyAction = () => {
                if (!hasChanges || isAnimating) return;
                isAnimating = true;

                // Green glow for input box (slightly thicker but subtle)
                templateInput.classList.add("applying-changes");
                templateInput.style.borderColor = "#4CAF50";
                templateInput.style.boxShadow = "0 0 8px rgba(76, 175, 80, 0.5)";

                // Show bright green glow before animation
                applyButton.style.color = "#2ecc71";
                applyButton.style.filter = "drop-shadow(0 0 8px rgba(46, 204, 113, 0.8))";
                applyButton.style.textShadow = "0 0 12px rgba(46, 204, 113, 0.9)";

                setTimeout(() => {
                    // Save current template
                    localStorage.setItem("filenameTemplate", filenameTemplate);
                    originalTemplate = filenameTemplate;
                    hasChanges = false;

                    // Slot machine animation sequence with proper overflow clipping

                    // Phase 1: Checkmark slides up with fast acceleration (like slot reel)
                    applyButton.style.transition =
                        "all 180ms cubic-bezier(0.25, 0.46, 0.45, 0.94)";
                    applyButton.style.transform = "translateY(-50%) translateY(-30px)";
                    applyButton.style.filter = "blur(4px)";
                    applyButton.style.opacity = "0";

                    setTimeout(() => {
                        // Phase 2: Applied! slides up from bottom (like slot winning symbol)
                        const appliedText = makeElement(
                            "span", {
                                textContent: "Applied!",
                            }, {
                                position: "absolute",
                                right: "8px",
                                top: "50%",
                                transform: "translateY(-50%) translateY(25px)",
                                color: "#4CAF50",
                                fontSize: "11px",
                                fontWeight: "bold",
                                opacity: "0",
                                transition: "all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94)",
                                pointerEvents: "none",
                                whiteSpace: "nowrap",
                            },
                        );

                        templateInputContainer.appendChild(appliedText);

                        // Applied! slides into view with deceleration
                        requestAnimationFrame(() => {
                            appliedText.style.opacity = "1";
                            appliedText.style.transform = "translateY(-50%) translateY(0px)";
                            appliedText.style.filter = "blur(0px)";
                        });

                        // Phase 3: Applied! slides up and disappears (faster stay time)
                        setTimeout(() => {
                            appliedText.style.transition =
                                "all 120ms cubic-bezier(0.25, 0.46, 0.45, 0.94)";
                            appliedText.style.transform = "translateY(-50%) translateY(-25px)";
                            appliedText.style.filter = "blur(3px)";
                            appliedText.style.opacity = "0";

                            // Phase 4: New checkmark slides up from bottom with glow
                            setTimeout(() => {
                                // Position new checkmark below input box ready to slide up
                                applyButton.style.transition = "none";
                                applyButton.style.transform = "translateY(-50%) translateY(25px)";
                                applyButton.style.opacity = "0";
                                applyButton.style.color = "#aaa";
                                applyButton.style.filter = "blur(1px)";

                                requestAnimationFrame(() => {
                                    // Slide up with glow effect and color mixing
                                    applyButton.style.transition =
                                        "all 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275)";
                                    applyButton.style.transform =
                                        "translateY(-50%) translateY(-2px)";
                                    applyButton.style.opacity = "0.9";
                                    applyButton.style.color = "#999";
                                    applyButton.style.filter =
                                        "blur(0px) drop-shadow(0 0 6px rgba(153, 153, 153, 0.8)) hue-rotate(20deg)";
                                    applyButton.style.textShadow =
                                        "0 0 10px rgba(200, 200, 200, 0.9)";

                                    // Phase 5: Settle with subtle bounce and color normalization
                                    setTimeout(() => {
                                        // Check if user made changes during animation
                                        const currentChanges =
                                            filenameTemplate !== "" &&
                                            filenameTemplate !==
                                            (localStorage.getItem("filenameTemplate") || "{name}");

                                        applyButton.style.transition = "all 150ms ease-out";
                                        applyButton.style.transform =
                                            "translateY(-50%) translateY(0px)";

                                        if (currentChanges) {
                                            // Show green if changes exist
                                            applyButton.style.opacity = "1";
                                            applyButton.style.color = "#4CAF50";
                                            applyButton.style.filter =
                                                "drop-shadow(0 0 4px rgba(76, 175, 80, 0.6))";
                                        } else {
                                            // Show gray if no changes
                                            applyButton.style.opacity = "0.5";
                                            applyButton.style.color = "#666";
                                            applyButton.style.filter = "none";
                                        }
                                        applyButton.style.textShadow = "none";

                                        // Reset input box border after delay
                                        setTimeout(() => {
                                            templateInput.classList.remove("applying-changes");
                                            if (document.activeElement === templateInput) {
                                                templateInput.style.borderColor = "#888";
                                                templateInput.style.boxShadow =
                                                    "inset 0 0 0 1px rgba(136, 136, 136, 0.3)";
                                            } else {
                                                templateInput.style.borderColor = "#555";
                                                templateInput.style.boxShadow = "none";
                                            }
                                        }, 100);

                                        // Clean up Applied! text
                                        appliedText.remove();
                                        isAnimating = false;
                                    }, 250);
                                });
                            }, 100);
                        }, 500); // Shorter stay time for snappier feel
                    }, 120);
                }, 50);
            };

            const startGrayHold = () => {
                if (!hasChanges && !isAnimating) {
                    // Hold down effect for gray checkmark
                    applyButton.style.transition = "all 100ms ease";
                    applyButton.style.transform = "translateY(-50%) scale(0.9)";
                    applyButton.style.opacity = "0.3";
                }
            };

            const endGrayHold = () => {
                if (!hasChanges && !isAnimating) {
                    // Release effect for gray checkmark
                    applyButton.style.transition = "all 100ms ease";
                    applyButton.style.transform = "translateY(-50%) scale(1)";
                    applyButton.style.opacity = "0.5";
                }
            };

            const handleGrayClick = () => {
                if (!hasChanges && !isAnimating) {
                    startGrayHold();
                    setTimeout(() => endGrayHold(), 100);
                }
            };

            applyButton.addEventListener("mousedown", () => {
                if (hasChanges && !isAnimating) {
                    // Green glow for input box on click too (slightly thicker but subtle)
                    templateInput.classList.add("applying-changes");
                    templateInput.style.borderColor = "#4CAF50";
                    templateInput.style.boxShadow = "0 0 8px rgba(76, 175, 80, 0.5)";
                    performApplyAction();
                } else if (!hasChanges && !isAnimating) {
                    isMouseHeld = true;
                    startGrayHold();
                }
            });

            applyButton.addEventListener("mouseup", () => {
                if (isMouseHeld) {
                    isMouseHeld = false;
                    endGrayHold();
                }
            });

            applyButton.addEventListener("mouseleave", () => {
                if (isMouseHeld) {
                    isMouseHeld = false;
                    endGrayHold();
                }
            });

            // Set initial apply button state
            if (hasChanges) {
                applyButton.style.opacity = "1";
                applyButton.style.color = "#4CAF50";
            }

            templateInputContainer.appendChild(applyButton);
            templateSection.appendChild(templateInputContainer);

            const tokenButtonsContainer = makeElement(
                "div", {}, {
                    display: "flex",
                    flexWrap: "wrap",
                    gap: "4px",
                    marginBottom: "8px",
                    alignItems: "center",
                },
            );

            // Create token buttons
            const tokens = [{
                    token: "id",
                    label: "ID"
                },
                {
                    token: "creator",
                    label: "Creator"
                },
                {
                    token: "name",
                    label: "Name"
                },
                {
                    token: "chat_name",
                    label: "Chat Name"
                },
                {
                    token: "created",
                    label: "Created"
                },
                {
                    token: "updated",
                    label: "Updated"
                },
                {
                    token: "tags",
                    label: "Tags"
                },
            ];

            tokens.forEach(({
                token,
                label
            }) => {
                const button = createTokenButton(token, label, templateInput, false);
                tokenButtonsContainer.appendChild(button);
            });

            templateSection.appendChild(tokenButtonsContainer);

            // Save Path Section
            const savePathSection = makeElement(
                "div", {}, {
                    marginTop: "20px",
                    marginBottom: "15px",
                    padding: "15px",
                    background: "#2a2a2a",
                    borderRadius: "10px",
                    boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
                    transition: "all 200ms ease",
                    cursor: "default",
                },
            );

            const savePathTitle = makeElement(
                "h3", {
                    textContent: "Save Path",
                }, {
                    margin: "0 0 15px 0",
                    fontSize: "16px",
                    color: "#fff",
                    fontWeight: "bold",
                    textAlign: "center",
                },
            );
            savePathSection.appendChild(savePathTitle);

            const savePathInputContainer = makeElement(
                "div", {
                    className: "savepath-input-container",
                }, {
                    marginBottom: "12px",
                    position: "relative",
                    overflow: "hidden",
                },
            );

            // Update userChangedSavePath to current state
            userChangedSavePath =
                localStorage.getItem("userChangedSavePath") === "true";

            // Always use default prefill if user hasn't applied changes yet
            let savePathTemplate = userChangedSavePath ?
                localStorage.getItem("savePathTemplate") || "" :
                "cards/{creator}";
            const savePathInput = makeElement(
                "input", {
                    type: "text",
                    value: savePathTemplate,
                    placeholder: "Leave empty to save to the root directory",
                }, {
                    width: "100%",
                    padding: "10px 55px 10px 12px",
                    border: "1px solid #555",
                    borderRadius: "6px",
                    background: "#1a1a1a",
                    color: "#fff",
                    fontSize: "13px",
                    boxSizing: "border-box",
                    transition: "border-color 200ms ease, box-shadow 200ms ease",
                    outline: "none",
                },
            );

            // Disable tooltips
            savePathInput.setAttribute("title", "");
            savePathInput.setAttribute("data-tooltip-disabled", "true");
            savePathInput.removeAttribute("title");
            savePathInput.addEventListener("mouseenter", (e) => {
                e.target.removeAttribute("title");
                e.target.title = "";
            });

            // Override browser tooltip styles
            savePathInput.style.setProperty("pointer-events", "auto", "important");
            savePathInput.addEventListener("mouseover", (e) => {
                e.preventDefault();
                e.stopPropagation();
            });

            let originalSavePathTemplate = userChangedSavePath ?
                localStorage.getItem("savePathTemplate") || "" :
                "cards/{creator}";
            let savePathHasChanges = savePathTemplate !== originalSavePathTemplate;

            // Auto-restore functionality removed for Save Path

            savePathInput.addEventListener("input", (e) => {
                savePathTemplate = e.target.value;
                // Update userChangedSavePath state
                userChangedSavePath =
                    localStorage.getItem("userChangedSavePath") === "true";
                const currentOriginal = userChangedSavePath ?
                    localStorage.getItem("savePathTemplate") || "" :
                    "cards/{creator}";
                savePathHasChanges = savePathTemplate !== currentOriginal;

                // Update apply button state
                const applyBtn = savePathSection.querySelector(".savepath-apply-btn");
                if (applyBtn && !savePathIsAnimating) {
                    applyBtn.style.transition = "all 200ms ease";
                    if (savePathHasChanges) {
                        applyBtn.style.opacity = "1";
                        applyBtn.style.color = "#4CAF50";
                        applyBtn.style.filter = "drop-shadow(0 0 4px rgba(76, 175, 80, 0.6))";
                    } else {
                        applyBtn.style.opacity = "0.5";
                        applyBtn.style.color = "#666";
                        applyBtn.style.filter = "none";
                    }
                }
            });

            // restoreSavePath(); // Removed auto-restore for Save Path

            let savePathIsEnterHeld = false;
            let savePathIsMouseHeld = false;
            let savePathIsAnimating = false;

            savePathInput.addEventListener("keydown", (e) => {
                if (e.key === "Enter" && !savePathIsEnterHeld) {
                    savePathIsEnterHeld = true;
                    e.preventDefault();
                    if (savePathHasChanges && !savePathIsAnimating) {
                        performSavePathApplyAction();
                    } else if (!savePathHasChanges && !savePathIsAnimating) {
                        startSavePathGrayHold();
                    }
                }
            });

            savePathInput.addEventListener("keyup", (e) => {
                if (e.key === "Enter") {
                    savePathIsEnterHeld = false;
                    if (!savePathHasChanges && !savePathIsAnimating) {
                        endSavePathGrayHold();
                    }
                }
            });

            savePathInput.addEventListener("mouseover", () => {
                if (
                    !savePathIsAnimating &&
                    document.activeElement !== savePathInput &&
                    savePathInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    savePathInput.style.borderColor = "#777";
                }
            });

            savePathInput.addEventListener("mouseout", () => {
                if (
                    !savePathIsAnimating &&
                    document.activeElement !== savePathInput &&
                    savePathInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    savePathInput.style.borderColor = "#555";
                }
            });

            savePathInput.addEventListener("focus", () => {
                if (
                    !savePathIsAnimating &&
                    savePathInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    savePathInput.style.borderColor = "#888";
                }
            });

            savePathInput.addEventListener("blur", () => {
                if (
                    !savePathIsAnimating &&
                    savePathInput.style.borderColor !== "rgb(76, 175, 80)"
                ) {
                    savePathInput.style.borderColor = "#555";
                }
            });

            savePathInputContainer.appendChild(savePathInput);

            const savePathApplyButton = makeElement(
                "button", {
                    textContent: "✓",
                    className: "savepath-apply-btn",
                }, {
                    position: "absolute",
                    right: "8px",
                    top: "50%",
                    transform: "translateY(-50%)",
                    background: "transparent",
                    border: "none",
                    color: "#666",
                    fontSize: "14px",
                    cursor: "pointer",
                    padding: "4px",
                    borderRadius: "3px",
                    opacity: "0.5",
                    transition: "all 200ms ease",
                },
            );

            const performSavePathApplyAction = () => {
                if (!savePathHasChanges || savePathIsAnimating) return;
                savePathIsAnimating = true;

                savePathInput.classList.add("applying-changes");
                savePathInput.style.borderColor = "#4CAF50";
                savePathInput.style.boxShadow = "0 0 8px rgba(76, 175, 80, 0.5)";

                savePathApplyButton.style.color = "#2ecc71";
                savePathApplyButton.style.filter =
                    "drop-shadow(0 0 8px rgba(46, 204, 113, 0.8))";
                savePathApplyButton.style.textShadow = "0 0 12px rgba(46, 204, 113, 0.9)";

                setTimeout(() => {
                    localStorage.setItem("savePathTemplate", savePathTemplate);
                    // Set boolean to true when user applies any changes to save path
                    localStorage.setItem("userChangedSavePath", "true");
                    userChangedSavePath = true; // Update the variable immediately
                    originalSavePathTemplate = savePathTemplate;
                    savePathHasChanges = false;

                    savePathApplyButton.style.transition =
                        "all 180ms cubic-bezier(0.25, 0.46, 0.45, 0.94)";
                    savePathApplyButton.style.transform =
                        "translateY(-50%) translateY(-30px)";
                    savePathApplyButton.style.filter = "blur(4px)";
                    savePathApplyButton.style.opacity = "0";

                    setTimeout(() => {
                        const appliedText = makeElement(
                            "span", {
                                textContent: "Applied!",
                            }, {
                                position: "absolute",
                                right: "8px",
                                top: "50%",
                                transform: "translateY(-50%) translateY(25px)",
                                color: "#4CAF50",
                                fontSize: "11px",
                                fontWeight: "bold",
                                opacity: "0",
                                transition: "all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94)",
                                pointerEvents: "none",
                                whiteSpace: "nowrap",
                            },
                        );

                        savePathInputContainer.appendChild(appliedText);

                        requestAnimationFrame(() => {
                            appliedText.style.opacity = "1";
                            appliedText.style.transform = "translateY(-50%) translateY(0px)";
                            appliedText.style.filter = "blur(0px)";
                        });

                        setTimeout(() => {
                            appliedText.style.transition =
                                "all 120ms cubic-bezier(0.25, 0.46, 0.45, 0.94)";
                            appliedText.style.transform = "translateY(-50%) translateY(-25px)";
                            appliedText.style.filter = "blur(3px)";
                            appliedText.style.opacity = "0";

                            setTimeout(() => {
                                savePathApplyButton.style.transition = "none";
                                savePathApplyButton.style.transform =
                                    "translateY(-50%) translateY(25px)";
                                savePathApplyButton.style.opacity = "0";
                                savePathApplyButton.style.color = "#aaa";
                                savePathApplyButton.style.filter = "blur(1px)";

                                requestAnimationFrame(() => {
                                    savePathApplyButton.style.transition =
                                        "all 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275)";
                                    savePathApplyButton.style.transform =
                                        "translateY(-50%) translateY(-2px)";
                                    savePathApplyButton.style.opacity = "0.9";
                                    savePathApplyButton.style.color = "#999";
                                    savePathApplyButton.style.filter =
                                        "blur(0px) drop-shadow(0 0 6px rgba(153, 153, 153, 0.8)) hue-rotate(20deg)";
                                    savePathApplyButton.style.textShadow =
                                        "0 0 10px rgba(200, 200, 200, 0.9)";

                                    setTimeout(() => {
                                        // Always set to gray state after applying since changes are now saved
                                        savePathApplyButton.style.transition = "all 150ms ease-out";
                                        savePathApplyButton.style.transform =
                                            "translateY(-50%) translateY(0px)";
                                        savePathApplyButton.style.opacity = "0.5";
                                        savePathApplyButton.style.color = "#666";
                                        savePathApplyButton.style.filter = "none";
                                        savePathApplyButton.style.textShadow = "none";

                                        setTimeout(() => {
                                            savePathInput.classList.remove("applying-changes");
                                            if (document.activeElement === savePathInput) {
                                                savePathInput.style.borderColor = "#888";
                                                savePathInput.style.boxShadow =
                                                    "inset 0 0 0 1px rgba(136, 136, 136, 0.3)";
                                            } else {
                                                savePathInput.style.borderColor = "#555";
                                                savePathInput.style.boxShadow = "none";
                                            }
                                        }, 100);

                                        appliedText.remove();
                                        savePathIsAnimating = false;
                                    }, 250);
                                });
                            }, 100);
                        }, 500);
                    }, 120);
                }, 50);
            };

            const startSavePathGrayHold = () => {
                if (!savePathHasChanges && !savePathIsAnimating) {
                    savePathApplyButton.style.transition = "all 100ms ease";
                    savePathApplyButton.style.transform = "translateY(-50%) scale(0.9)";
                    savePathApplyButton.style.opacity = "0.3";
                }
            };

            const endSavePathGrayHold = () => {
                if (!savePathHasChanges && !savePathIsAnimating) {
                    savePathApplyButton.style.transition = "all 100ms ease";
                    savePathApplyButton.style.transform = "translateY(-50%) scale(1)";
                    savePathApplyButton.style.opacity = "0.5";
                }
            };

            savePathApplyButton.addEventListener("mousedown", () => {
                if (savePathHasChanges && !savePathIsAnimating) {
                    savePathInput.classList.add("applying-changes");
                    savePathInput.style.borderColor = "#4CAF50";
                    savePathInput.style.boxShadow = "0 0 8px rgba(76, 175, 80, 0.5)";
                    performSavePathApplyAction();
                } else if (!savePathHasChanges && !savePathIsAnimating) {
                    savePathIsMouseHeld = true;
                    startSavePathGrayHold();
                }
            });

            savePathApplyButton.addEventListener("mouseup", () => {
                if (savePathIsMouseHeld) {
                    savePathIsMouseHeld = false;
                    endSavePathGrayHold();
                }
            });

            savePathApplyButton.addEventListener("mouseleave", () => {
                if (savePathIsMouseHeld) {
                    savePathIsMouseHeld = false;
                    endSavePathGrayHold();
                }
            });

            if (savePathHasChanges) {
                savePathApplyButton.style.opacity = "1";
                savePathApplyButton.style.color = "#4CAF50";
            }

            savePathInputContainer.appendChild(savePathApplyButton);
            savePathSection.appendChild(savePathInputContainer);

            const savePathTokenButtonsContainer = makeElement(
                "div", {}, {
                    display: "flex",
                    flexWrap: "wrap",
                    gap: "4px",
                    marginBottom: "8px",
                    alignItems: "center",
                },
            );

            tokens.forEach(({
                token,
                label
            }) => {
                const button = createTokenButton(token, label, savePathInput, true);
                savePathTokenButtonsContainer.appendChild(button);
            });

            savePathSection.appendChild(savePathTokenButtonsContainer);

            // FileSystemAccess API Toggle
            const useFileSystemToggle = createToggle(
                "useFileSystemAccess",
                "Use FileSystemAccess API",
                "Enable to use save path with directory picker. Disable for regular downloads.",
                localStorage.getItem("useFileSystemAccess") === "true",
            );

            useFileSystemToggle.input.addEventListener("change", (e) => {
                const isEnabled = e.target.checked;
                localStorage.setItem("useFileSystemAccess", isEnabled);

                // Smooth animation with resume logic
                directorySection.style.transition =
                    "all 400ms cubic-bezier(0.4, 0, 0.2, 1)";

                if (isEnabled) {
                    // Slide down animation
                    directorySection.style.maxHeight = "35px";
                    directorySection.style.opacity = "1";
                    directorySection.style.paddingTop = "5px";
                    directorySection.style.paddingBottom = "5px";
                    directorySection.style.marginBottom = "5px";
                    directorySection.style.transform = "translateY(0)";
                } else {
                    // Slide up animation
                    directorySection.style.maxHeight = "0";
                    directorySection.style.opacity = "0";
                    directorySection.style.paddingTop = "0";
                    directorySection.style.paddingBottom = "0";
                    directorySection.style.marginBottom = "0";
                    directorySection.style.transform = "translateY(-10px)";
                }
            });

            savePathSection.appendChild(useFileSystemToggle.container);

            // Directory Picker with smooth animation support
            const directorySection = makeElement(
                "div", {}, {
                    marginTop: "5px",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "space-between",
                    width: "100%",
                    overflow: "hidden",
                    transition: "all 400ms cubic-bezier(0.4, 0, 0.2, 1)",
                    maxHeight: localStorage.getItem("useFileSystemAccess") === "true" ? "35px" : "0",
                    opacity: localStorage.getItem("useFileSystemAccess") === "true" ? "1" : "0",
                    paddingTop: localStorage.getItem("useFileSystemAccess") === "true" ? "5px" : "0",
                    paddingBottom: localStorage.getItem("useFileSystemAccess") === "true" ? "5px" : "0",
                    marginBottom: localStorage.getItem("useFileSystemAccess") === "true" ? "5px" : "0",
                },
            );

            const directoryLabel = makeElement(
                "span", {
                    textContent: "Directory",
                }, {
                    fontSize: "13px",
                    color: "#fff",
                    fontWeight: "bold",
                },
            );

            const directoryRightSection = makeElement(
                "div", {}, {
                    display: "flex",
                    alignItems: "center",
                    gap: "10px",
                },
            );

            const directoryName = makeElement(
                "span", {
                    textContent: "Not selected",
                }, {
                    fontSize: "12px",
                    color: "#aaa",
                    fontStyle: "italic",
                },
            );

            const directoryButton = makeElement(
                "button", {
                    textContent: "Browse",
                    className: "directory-button",
                }, {
                    background: "#333",
                    color: "#fff",
                    border: "none",
                    borderRadius: "6px",
                    padding: "8px 16px",
                    cursor: "pointer",
                    fontSize: "12px",
                    transition: "all 200ms ease",
                },
            );

            let selectedDirectory = null;

            // IndexedDB functions for directory handle persistence
            const DB_NAME = "DirectoryHandles";
            const DB_VERSION = 1;
            const STORE_NAME = "handles";

            async function storeDirectoryHandle(handle) {
                return new Promise((resolve, reject) => {
                    const request = indexedDB.open(DB_NAME, DB_VERSION);

                    request.onupgradeneeded = (e) => {
                        const db = e.target.result;
                        if (!db.objectStoreNames.contains(STORE_NAME)) {
                            db.createObjectStore(STORE_NAME);
                        }
                    };

                    request.onsuccess = (e) => {
                        const db = e.target.result;
                        const transaction = db.transaction([STORE_NAME], "readwrite");
                        const store = transaction.objectStore(STORE_NAME);

                        const putRequest = store.put(handle, "selectedDirectory");
                        putRequest.onsuccess = () => resolve();
                        putRequest.onerror = () => reject(putRequest.error);
                    };

                    request.onerror = () => reject(request.error);
                });
            }

            async function getDirectoryHandle() {
                return new Promise((resolve, reject) => {
                    const request = indexedDB.open(DB_NAME, DB_VERSION);

                    request.onupgradeneeded = (e) => {
                        const db = e.target.result;
                        if (!db.objectStoreNames.contains(STORE_NAME)) {
                            db.createObjectStore(STORE_NAME);
                        }
                    };

                    request.onsuccess = (e) => {
                        const db = e.target.result;
                        const transaction = db.transaction([STORE_NAME], "readonly");
                        const store = transaction.objectStore(STORE_NAME);

                        const getRequest = store.get("selectedDirectory");
                        getRequest.onsuccess = () => resolve(getRequest.result);
                        getRequest.onerror = () => reject(getRequest.error);
                    };

                    request.onerror = () => reject(request.error);
                });
            }

            // Restore directory selection on page load
            directoryName.textContent = "Not selected";
            directoryName.style.color = "#aaa";
            directoryName.style.fontStyle = "italic";

            // Async function to restore directory handle
            (async () => {
                try {
                    const storedHandle = await getDirectoryHandle();
                    if (storedHandle) {
                        // Test if the handle is still valid
                        try {
                            const permission = await storedHandle.queryPermission({
                                mode: "readwrite",
                            });
                            if (permission === "granted" || permission === "prompt") {
                                selectedDirectory = storedHandle;
                                window.selectedDirectoryHandle = storedHandle;
                                directoryName.textContent = storedHandle.name;
                                directoryName.style.color = "#4CAF50";
                                directoryName.style.fontStyle = "normal";
                                console.log(
                                    "[Directory] Restored from IndexedDB:",
                                    storedHandle.name,
                                );
                            } else {
                                console.log("[Directory] Handle exists but permission denied");
                            }
                        } catch (error) {
                            console.log("[Directory] Handle invalid, removing from storage");
                            // Handle is invalid, remove it
                            const request = indexedDB.open(DB_NAME, DB_VERSION);
                            request.onsuccess = (e) => {
                                const db = e.target.result;
                                const transaction = db.transaction([STORE_NAME], "readwrite");
                                const store = transaction.objectStore(STORE_NAME);
                                store.delete("selectedDirectory");
                            };
                        }
                    }
                } catch (error) {
                    console.log("[Directory] Error restoring from IndexedDB:", error);
                }
            })();

            directoryButton.addEventListener("click", async () => {
                if ("showDirectoryPicker" in window) {
                    try {
                        selectedDirectory = await window.showDirectoryPicker();
                        window.selectedDirectoryHandle = selectedDirectory; // Store globally for save function
                        directoryName.textContent = selectedDirectory.name;
                        directoryName.style.color = "#4CAF50";
                        directoryName.style.fontStyle = "normal";

                        // Persist directory handle in IndexedDB
                        storeDirectoryHandle(selectedDirectory)
                            .then(() => {
                                console.log(
                                    "[Directory] Stored in IndexedDB:",
                                    selectedDirectory.name,
                                );
                            })
                            .catch((error) => {
                                console.log("[Directory] Failed to store in IndexedDB:", error);
                            });
                    } catch (err) {
                        console.log("Directory picker was cancelled or failed:", err);
                    }
                } else {
                    alert("FileSystemAccess API is not supported in this browser.");
                }
            });

            directoryRightSection.appendChild(directoryName);
            directoryRightSection.appendChild(directoryButton);
            directorySection.appendChild(directoryLabel);
            directorySection.appendChild(directoryRightSection);
            savePathSection.appendChild(directorySection);

            settingsContent.appendChild(savePathSection);
            settingsContent.appendChild(templateSection);

            // Export Options Section
            const exportOptionsSection = makeElement(
                "div", {}, {
                    marginTop: "20px",
                    marginBottom: "15px",
                    padding: "15px",
                    background: "#2a2a2a",
                    borderRadius: "10px",
                    boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
                    transition: "all 200ms ease",
                    cursor: "default",
                },
            );

            const exportOptionsTitle = makeElement(
                "h3", {
                    textContent: "Export Options",
                }, {
                    margin: "0 0 15px 0",
                    fontSize: "16px",
                    color: "#fff",
                    fontWeight: "bold",
                },
            );
            exportOptionsSection.appendChild(exportOptionsTitle);

            // Use character's chat name toggle
            const chatNameToggle = createToggle(
                "useChatNameForName",
                "Use chat name",
                "Uses chat name for the character name instead of the card's main name.",
                useChatNameForName,
            );
            chatNameToggle.input.addEventListener("change", (e) => {
                useChatNameForName = e.target.checked;
            });
            exportOptionsSection.appendChild(chatNameToggle.container);

            // Apply {{char}} tokenization toggle
            // Apply {{char}} tokenization toggle
            const charTokenToggle = createToggle(
                "applyCharToken",
                "Tokenize char name",
                "Replaces the character's name with {{char}}.",
                applyCharToken,
            );
            charTokenToggle.input.addEventListener("change", (e) => {
                applyCharToken = e.target.checked;
            });
            exportOptionsSection.appendChild(charTokenToggle.container);

            // Include Creator Notes toggle
            const creatorNotesToggle = createToggle(
                "includeCreatorNotes",
                "Include creator notes",
                "Include the creator's notes in exported files.",
                localStorage.getItem("includeCreatorNotes") !== "false",
            );
            exportOptionsSection.appendChild(creatorNotesToggle.container);

            // Include Tags toggle
            const includeTagsToggle = createToggle(
                "includeTags",
                "Include tags",
                "Include character tags in exported files (PNG/JSON only). Emojis will be removed.",
                localStorage.getItem("includeTags") !== "false",
            );
            exportOptionsSection.appendChild(includeTagsToggle.container);

            settingsContent.appendChild(exportOptionsSection);

            // Advanced Section
            const advancedSection = makeElement(
                "div", {}, {
                    marginTop: "20px",
                    marginBottom: "15px",
                    padding: "15px",
                    background: "#2a2a2a",
                    borderRadius: "10px",
                    boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
                    transition: "all 200ms ease",
                    cursor: "default",
                },
            );

            const advancedTitle = makeElement(
                "h3", {
                    textContent: "Advanced",
                }, {
                    margin: "0 0 15px 0",
                    fontSize: "16px",
                    color: "#fff",
                    fontWeight: "bold",
                },
            );
            advancedSection.appendChild(advancedTitle);

            // Show Debug Logs toggle
            const debugLogsToggle = createToggle(
                "showDebugLogs",
                "Debug logs",
                "Show verbose console logging for debugging",
                localStorage.getItem("showDebugLogs") === "true",
            );
            advancedSection.appendChild(debugLogsToggle.container);

            settingsContent.appendChild(advancedSection);

            const tabs = {
                export: {
                    content: exportContent,
                    tab: exportTab,
                    active: true,
                },
                settings: {
                    content: settingsContent,
                    tab: settingsTab,
                    active: false,
                },
            };

            function switchTab(tabKey) {
                const previousTab = currentActiveTab;
                currentActiveTab = tabKey;

                // Save filename draft and clean up toggle states when switching AWAY from settings
                if (previousTab === "settings" && tabKey !== "settings") {
                    const templateInput = document.querySelector(
                        'input[placeholder="Enter filename template"]',
                    );
                    if (templateInput && templateInput.value) {
                        localStorage.setItem("filenameTemplateDraft", templateInput.value);
                    }
                }

                // Too many flaws, will fix the toggle state visual restoration later
                // The commented code below is the original (flawed version) logic for restoring toggle states

                //   // Clean up any lingering toggle visual states
                //   document.querySelectorAll(".toggle-wrapper").forEach((wrapper) => {
                //     const container = wrapper.closest("div");
                //     if (container) {
                //       // Reset any stuck styles and classes
                //       container.style.transform = "";
                //       container.style.borderColor = "";
                //       container.style.boxShadow = "";
                //       container.className = container.className.replace(
                //         /toggle-container-(active|inactive)/g,
                //         "",
                //       );
                //     }
                //   });
                // }

                // Only restore when switching TO settings (not from settings)
                if (tabKey === "settings" && previousTab !== "settings") {
                    // Immediate restoration before settings tab loads
                    inputStateManager.restoreAll(false);

                    // Re-initialize scrollbar when switching to settings with proper cleanup
                    requestAnimationFrame(() => {
                        // Clean up any existing scrollbar state
                        const existingTrack = settingsContent.parentElement?.querySelector(
                            ".custom-scrollbar-track",
                        );
                        if (existingTrack) {
                            existingTrack.remove();
                        }

                        const newScrollbar = createCustomScrollbar(settingsContent);
                        newScrollbar.init();

                        // Restore complete toggle visual states
                        document.querySelectorAll(".toggle-wrapper").forEach((wrapper) => {
                            const input = wrapper.querySelector('input[type="checkbox"]');
                            const container = wrapper.closest("div");
                            const slider = wrapper.querySelector(".slider");
                            const sliderBefore = wrapper.querySelector(".slider-before");

                            if (input && container && slider && sliderBefore) {
                                const isChecked = input.checked;

                                // // Clear any existing state classes and inline styles
                                // container.style.borderColor = "";
                                // container.style.boxShadow = "";
                                // container.style.transform = "";
                                // container.className = container.className.replace(
                                //   /toggle-container-(active|inactive)/g,
                                //   "",
                                // );

                                // // Apply correct state class
                                // container.classList.add(
                                //   isChecked
                                //     ? "toggle-container-active"
                                //     : "toggle-container-inactive",
                                // );

                                // Restore slider styles
                                slider.style.backgroundColor = isChecked ? "#0080ff" : "#ccc";
                                slider.style.boxShadow = isChecked ?
                                    "0 0 8px rgba(0, 128, 255, 0.3)" :
                                    "none";

                                // Restore slider before styles
                                sliderBefore.style.transform = isChecked ?
                                    "translateX(16px)" :
                                    "translateX(0)";
                                sliderBefore.style.boxShadow = isChecked ?
                                    "0 0 2px rgba(0,0,0,0.2), 0 0 8px rgba(0,128,255,0.5)" :
                                    "0 0 2px rgba(0,0,0,0.2)";

                                // Restore wrapper class
                                // wrapper.classList.toggle("active", isChecked);
                            }
                        });
                    });

                    // Use requestAnimationFrame to ensure DOM is ready for draft restoration
                    requestAnimationFrame(() => {
                        const templateInput = document.querySelector(
                            'input[placeholder="Enter filename template"]',
                        );
                        if (templateInput) {
                            const savedDraft = localStorage.getItem("filenameTemplateDraft");
                            if (savedDraft && savedDraft !== templateInput.value) {
                                templateInput.value = savedDraft;
                                filenameTemplate = savedDraft;
                                templateInput.dispatchEvent(
                                    new Event("input", {
                                        bubbles: true
                                    }),
                                );
                            }
                        }

                        // Auto-restore removed for Save Path input
                    });
                }
                animationTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
                animationTimeouts = [];

                Object.entries(tabs).forEach(([key, {
                    content,
                    tab
                }]) => {
                    const isActive = key === tabKey;

                    tab.style.opacity = isActive ? "1" : "0.7";
                    tab.style.transform = isActive ? "translateY(-2px)" : "";

                    const indicator = tab.lastChild;
                    if (indicator) {
                        if (isActive) {
                            indicator.style.background = ACTIVE_TAB_COLOR;
                            indicator.style.transform = "scaleX(1)";
                        } else {
                            indicator.style.background = INACTIVE_TAB_COLOR;
                            indicator.style.transform = "scaleX(0.5)";
                        }
                    }

                    content.style.display = "block";
                    content.style.pointerEvents = isActive ? "auto" : "none";

                    if (isActive) {
                        content.style.opacity = "0";
                        content.style.transform = "scale(0.95)";

                        void content.offsetWidth;

                        requestAnimationFrame(() => {
                            content.style.opacity = "1";
                            content.style.transform = "scale(1)";
                        });
                    } else {
                        requestAnimationFrame(() => {
                            content.style.opacity = "0";
                            content.style.transform = "scale(0.95)";
                        });

                        const hideTimeout = setTimeout(() => {
                            if (!tabs[key].active) {
                                content.style.display = "none";
                                // Clean up scrollbar when hiding settings tab
                                if (key === "settings") {
                                    const existingTrack = content.parentElement?.querySelector(
                                        ".custom-scrollbar-track",
                                    );
                                    if (existingTrack) {
                                        existingTrack.remove();
                                    }
                                }
                            }
                        }, TAB_ANIMATION_DURATION);
                        animationTimeouts.push(hideTimeout);
                    }

                    tabs[key].active = isActive;
                });

                currentTab = tabKey;
                try {
                    sessionStorage.setItem("lastActiveTab", tabKey);
                } catch (e) {
                    console.warn("Failed to save tab state to sessionStorage", e);
                }
            }

            const handleTabClick = (e) => {
                const tt = document.getElementById("char-export-tooltip");
                if (tt) {
                    tt.style.opacity = "0";
                    const offsetHide = TOOLTIP_SLIDE_FROM_RIGHT ?
                        -TOOLTIP_SLIDE_OFFSET :
                        TOOLTIP_SLIDE_OFFSET;
                    tt.style.transform = `translateX(${offsetHide}px)`;
                }
                const tabKey = e.target === exportTab ? "export" : "settings";
                if (!tabs[tabKey].active) {
                    switchTab(tabKey);
                }
            };

            exportTab.onclick = handleTabClick;
            settingsTab.onclick = handleTabClick;

            Object.entries(tabs).forEach(([key, {
                content
            }]) => {
                const isActive = key === currentTab;

                content.style.display = isActive ? "block" : "none";
                content.style.opacity = isActive ? "1" : "0";
                content.style.transform = isActive ? "scale(1)" : "scale(0.95)";
            });

            switchTab(currentTab);
            document.body.appendChild(gui);

            // Add backdrop with smooth linear animation
            const backdrop = makeElement(
                "div", {
                    id: "char-export-backdrop",
                }, {
                    position: "fixed",
                    top: "0",
                    left: "0",
                    width: "100%",
                    height: "100%",
                    background: "rgba(0, 0, 0, 0)",
                    backdropFilter: "blur(0px)",
                    zIndex: "9999",
                    opacity: "0",
                    transition: "all 150ms ease-out",
                },
            );

            document.body.insertBefore(backdrop, gui);

            // Set initial modal state
            gui.style.opacity = "0";
            gui.style.transform = "translate(-50%, -50%) scale(0.95)";
            gui.style.filter = "blur(3px)";
            gui.style.transition = "all 150ms ease-out";

            // Start backdrop animation smoothly - no instant fill
            requestAnimationFrame(() => {
                backdrop.style.opacity = "1";
                backdrop.style.background = "rgba(0, 0, 0, 0.4)";
                backdrop.style.backdropFilter = "blur(3px)";

                gui.style.opacity = "1";
                gui.style.transform = "translate(-50%, -50%) scale(1)";
                gui.style.filter = "blur(0px) drop-shadow(0 15px 35px rgba(0,0,0,0.3))";
            });

            document.addEventListener("mousedown", handleDialogOutsideClick);
            document.addEventListener("mouseup", handleDialogOutsideClick);
        }

        function toggleUIState() {
            animationTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
            animationTimeouts = [];

            if (guiElement && document.body.contains(guiElement)) {
                if (viewActive) {
                    const backdrop = document.getElementById("char-export-backdrop");
                    const currentGuiOpacity =
                        parseFloat(getComputedStyle(guiElement).opacity) || 0;
                    const currentBackdropOpacity = backdrop ?
                        parseFloat(getComputedStyle(backdrop).opacity) || 0 :
                        0;

                    guiElement.style.display = "flex";

                    void guiElement.offsetHeight;

                    requestAnimationFrame(() => {
                        guiElement.style.transition = "all 150ms ease-out";
                        guiElement.style.opacity = "1";
                        guiElement.style.transform = "translate(-50%, -50%) scale(1)";
                        guiElement.style.filter =
                            "blur(0px) drop-shadow(0 15px 35px rgba(0,0,0,0.3))";

                        if (backdrop) {
                            backdrop.style.transition = "all 150ms ease-out";
                            backdrop.style.opacity = "1";
                            backdrop.style.background = "rgba(0, 0, 0, 0.4)";
                            backdrop.style.backdropFilter = "blur(3px)";
                        }
                    });
                } else {
                    const backdrop = document.getElementById("char-export-backdrop");

                    const currentGuiOpacity =
                        parseFloat(getComputedStyle(guiElement).opacity) || 1;
                    const currentBackdropOpacity = backdrop ?
                        parseFloat(getComputedStyle(backdrop).opacity) || 1 :
                        0;

                    const transformMatrix = getComputedStyle(guiElement).transform;
                    let currentScale = 1;
                    if (transformMatrix !== "none") {
                        const values = transformMatrix.match(/-?\d+\.?\d*/g);
                        if (values && values.length >= 6) {
                            currentScale = parseFloat(values[0]);
                        }
                    }

                    requestAnimationFrame(() => {
                        guiElement.style.transition = "all 120ms ease-in";
                        guiElement.style.opacity = "0";
                        guiElement.style.transform = "translate(-50%, -50%) scale(0.9)";
                        guiElement.style.filter = "blur(4px)";

                        if (backdrop) {
                            backdrop.style.transition = "all 120ms ease-in";
                            backdrop.style.opacity = "0";
                            backdrop.style.background = "rgba(0, 0, 0, 0)";
                            backdrop.style.backdropFilter = "blur(0px)";
                        }
                    });

                    const removeTimeout = setTimeout(() => {
                        if (!viewActive && guiElement && document.body.contains(guiElement)) {
                            document.body.removeChild(guiElement);
                            const backdrop = document.getElementById("char-export-backdrop");
                            if (backdrop) backdrop.remove();
                            document.removeEventListener("mousedown", handleDialogOutsideClick);
                            document.removeEventListener("mouseup", handleDialogOutsideClick);
                            guiElement = null;
                        }
                    }, 140);
                    animationTimeouts.push(removeTimeout);
                }
            } else if (viewActive) {
                createUI();
            }
        }

        /**
         * Centralized input state manager with immediate restoration
         * Handles DOM queries, variable sync, and immediate updates with retry logic
         * @param {boolean} force - Force restoration regardless of current value
         * @param {number} retryCount - Internal retry counter for DOM queries
         */
        function createInputStateManager() {
            const SELECTORS = {
                filename: 'input[placeholder="Enter filename template"]',
            };

            const DEFAULTS = {
                filename: "{name}",
            };

            const STATE_KEYS = {
                filename: "filenameTemplate",
            };

            function restoreInput(type, force = false, retryCount = 0) {
                const input = document.querySelector(SELECTORS[type]);

                if (!input) {
                    if (retryCount < 5) {
                        requestAnimationFrame(() =>
                            restoreInput(type, force, retryCount + 1),
                        );
                    }
                    return;
                }

                const savedValue =
                    localStorage.getItem(STATE_KEYS[type]) || DEFAULTS[type];
                const needsRestore = force || input.value.trim() === "";

                if (needsRestore) {
                    input.value = savedValue;
                    if (type === "filename") {
                        window.filenameTemplate = savedValue;
                        filenameTemplate = savedValue;
                    }

                    input.dispatchEvent(new Event("input", {
                        bubbles: true
                    }));
                }
            }

            return {
                restoreAll: (force = false) => {
                    if (restorationInProgress) return;
                    restorationInProgress = true;

                    restoreInput("filename", force);

                    setTimeout(() => {
                        restorationInProgress = false;
                    }, 50);
                },
                restoreFilename: (force = false) => restoreInput("filename", force),
            };
        }

        const inputStateManager = createInputStateManager();

        function closeV() {
            if (!viewActive) return;

            const templateInput = document.querySelector(
                'input[placeholder="Enter filename template"]',
            );
            if (templateInput && templateInput.value) {
                localStorage.setItem("filenameTemplateDraft", templateInput.value);
            }

            inputStateManager.restoreAll(false);

            requestAnimationFrame(() => {
                viewActive = false;
                toggleUIState();
            });
        }

        let isMouseDownInside = false;

        function handleDialogOutsideClick(e) {
            const gui = document.getElementById("char-export-gui");
            const backdrop = document.getElementById("char-export-backdrop");

            if (e.type === "mousedown") {
                isMouseDownInside = gui && gui.contains(e.target);
            } else if (e.type === "mouseup") {
                const isMouseUpOutside = !gui || !gui.contains(e.target);
                if (!isMouseDownInside && isMouseUpOutside) {
                    closeV();
                }
                isMouseDownInside = false;
            }
        }

        /* ============================
           ==      INTERCEPTORS     ==
           ============================ */
        function interceptNetwork() {
            if (networkInterceptActive) return;
            networkInterceptActive = true;

            const origXHR = XMLHttpRequest.prototype.open;
            XMLHttpRequest.prototype.open = function(method, url) {
                this.addEventListener("load", () => {
                    if (url.includes("generateAlpha")) modifyResponse(this.responseText);
                    if (url.includes("/hampter/chats/"))
                        modifyChatResponse(this.responseText);
                });
                return origXHR.apply(this, arguments);
            };

            const origFetch = window.fetch;
            window.fetch = async function(...args) {
                const requestUrl = typeof args[0] === 'string' ? args[0] : args[0]?.url;

                if (shouldInterceptNext && cachedProxyUrl && requestUrl === cachedProxyUrl) {
                    return new Response('{"choices":[]}', {
                        status: 200,
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    });
                }

                try {
                    const res = await origFetch(...args);
                    if (res.ok) {
                        if (res.url?.includes("generateAlpha")) {
                            res.clone().text().then(modifyResponse).catch(() => {});
                        }
                        if (res.url?.includes("/hampter/chats/")) {
                            res.clone().text().then(modifyChatResponse).catch(() => {});
                        }
                    }
                    return res;
                } catch (error) {
                    return new Response(null, {
                        status: 500,
                        statusText: "Userscript Handled Fetch"
                    });
                }
            };
        }

        function modifyResponse(text) {
            if (!shouldInterceptNext) return;
            shouldInterceptNext = false;
            try {
                const json = JSON.parse(text);
                const sys = json.messages.find((m) => m.role === "system")?.content || "";
                let initMsg = "";
                if (chatData?.chatMessages?.length) {
                    const msgs = chatData.chatMessages;
                    initMsg = msgs[msgs.length - 1].message;
                }
                const header = document.querySelector("p.chakra-text.css-1nj33dt");
                const headerName = header?.textContent
                    .match(/Chat with\s+(.*)$/)?.[1]
                    ?.trim();
                const fullName = (
                    chatData?.character?.chat_name ||
                    chatData?.character?.name ||
                    ""
                ).trim();
                const nameFirst = (chatData?.character?.name || "")
                    .trim()
                    .split(/\s+/)[0];
                const charName = fullName || nameFirst || headerName || "char";
                let charBlock = "";
                const systemTagEnd = sys.indexOf("</system>");

                if (systemTagEnd !== -1) {
                    // Start with the content immediately following the </system> tag.
                    let potentialDescription = sys.substring(systemTagEnd + 9).trimStart();

                    // Check for and strip the optional [System note: ...] if it exists at the beginning.
                    if (potentialDescription.startsWith("[System note:")) {
                        const noteEndIndex = potentialDescription.indexOf("]");
                        if (noteEndIndex !== -1) {
                            potentialDescription = potentialDescription.substring(noteEndIndex + 1).trimStart();
                        }
                    }

                    // Find the end of the description by looking for the earliest terminating tag.
                    const terminators = ["<scenario>", "<UserPersona>", "<example_dialogs>"];
                    const terminatorIndices = terminators
                        .map(t => potentialDescription.indexOf(t))
                        .filter(i => i !== -1);

                    const endOfDescriptionIndex = terminatorIndices.length > 0 ? Math.min(...terminatorIndices) : -1;

                    if (endOfDescriptionIndex !== -1) {
                        // A known terminator was found, so we take the content before it.
                        charBlock = potentialDescription.substring(0, endOfDescriptionIndex).trim();
                    } else {
                        // No known terminators. The description likely ends at the next opening tag of any kind, or the end of the string.
                        const nextTagStart = potentialDescription.indexOf("<");
                        if (nextTagStart !== -1) {
                            charBlock = potentialDescription.substring(0, nextTagStart).trim();
                        } else {
                            charBlock = potentialDescription.trim();
                        }
                    }
                }
                const scen = extractTagContent(sys, "scenario");
                const rawExs = extractTagContent(sys, "example_dialogs");
                const exs = rawExs.replace(
                    /^\s*Example conversations between[^:]*:\s*/,
                    "",
                );
                const userName = cachedUserName;
                switch (exportFormat) {
                    case "txt": {
                        saveAsTxt(charBlock, scen, initMsg, exs, charName, userName);
                        break;
                    }
                    case "png": {
                        saveAsPng(charName, charBlock, scen, initMsg, exs, userName);
                        break;
                    }
                    case "json": {
                        saveAsJson(charName, charBlock, scen, initMsg, exs, userName);
                        break;
                    }
                }
                exportFormat = null;
            } catch (err) {
                console.error("Error processing response:", err);
            }
        }

        function modifyChatResponse(text) {
            try {
                if (!text || typeof text !== "string" || !text.trim()) return;

                const data = JSON.parse(text);
                if (data && data.character) {
                    chatData = data;
                }
            } catch (err) {
                // ignore parsing errors
            }
        }

        /* ============================
           ==      CORE LOGIC       ==
           ============================ */
        async function getCharacterMeta() {
            // ---------- BEGIN Method 1 helpers ----------
            const findCharacter = (obj, visited = new Set()) => {
                // Base case: If obj is not a searchable object or has already been visited, stop.
                if (!obj || typeof obj !== 'object' || visited.has(obj)) {
                    return null;
                }
                // Add the current object to the visited set to prevent infinite recursion.
                visited.add(obj);

                // Success condition: Check if the current object looks like a valid character object.
                // We use a combination of keys that are highly likely to be unique to the character definition.
                const isCharacterObject =
                    Object.prototype.hasOwnProperty.call(obj, "showdefinition") &&
                    Object.prototype.hasOwnProperty.call(obj, "id") &&
                    Object.prototype.hasOwnProperty.call(obj, "name") &&
                    Object.prototype.hasOwnProperty.call(obj, "personality") &&
                    Object.prototype.hasOwnProperty.call(obj, "first_message");

                if (isCharacterObject) {
                    return obj; // Found it!
                }

                // Recursive step: Iterate over all properties of the current object.
                for (const key in obj) {
                    // We only check own properties whose values are also objects.
                    if (Object.prototype.hasOwnProperty.call(obj, key) && typeof obj[key] === 'object') {
                        const result = findCharacter(obj[key], visited);
                        // If a nested call finds the character, propagate the result up immediately.
                        if (result) {
                            return result;
                        }
                    }
                }

                // Not found in this branch of the JSON tree.
                return null;
            };

            const extractFromJson = (json) => {
                if (!json) return null;
                const charObj = findCharacter(json);
                if (!charObj) {
                    if (localStorage.getItem("showDebugLogs") === "true") {
                        console.log("[getCharacterMeta] Method 1: no character object found");
                    }
                    return null;
                }
                if (localStorage.getItem("showDebugLogs") === "true") {
                    console.log(
                        "[getCharacterMeta] Method 1: character object located, showdefinition=",
                        charObj.showdefinition,
                    );
                }

                const {
                    name: rawName = "",
                    chat_name: chatName = "",
                    description: creatorNotesRaw = "",
                    creator = {},
                    personality: personalityRaw = "",
                    scenario: scenarioRaw = "",
                    first_message: firstMsgRaw = "",
                    example_dialogs: exDialogsRaw = "",
                    showdefinition = false,
                    id = "",
                } = charObj;

                if (!showdefinition) return null;

                const name = (rawName || chatName).trim();

                let characterVersion = getCharacterCardUrl(id);
                if (chatName && !useChatNameForName) {
                    characterVersion += `\nChat Name: ${chatName.trim()}`;
                }

                // Verbose logging for Method 1 (JSON.parse when showdefinition=true)
                if (localStorage.getItem("showDebugLogs") === "true") {
                    const src = "JSON.parse (showdefinition=true)";
                    console.log("[getCharacterMeta] name", name, `<= ${src}`);
                    console.log(
                        "[getCharacterMeta] personality",
                        personalityRaw,
                        `<= ${src}`,
                    );
                    console.log("[getCharacterMeta] scenario", scenarioRaw, `<= ${src}`);
                    console.log(
                        "[getCharacterMeta] first_message",
                        firstMsgRaw,
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] example_dialogs",
                        exDialogsRaw,
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] creator_notes",
                        creatorNotesRaw,
                        `<= ${src}`,
                    );
                }
                return {
                    characterVersion,
                    characterCardUrl: getCharacterCardUrl(id),
                    name,
                    creatorNotes: creatorNotesRaw,
                    personality: stripWatermark(personalityRaw),
                    scenario: stripWatermark(scenarioRaw),
                    firstMessage: stripWatermark(firstMsgRaw),
                    exampleDialogs: stripWatermark(exDialogsRaw),
                    definitionExposed: true,
                };
            };
            // ---------- END Method 1 helpers ----------
            // ---------- BEGIN Method 2 helpers (chatData fallback) ----------
            // Method 2 (showdefinition false) reads directly from chatData for character information.
            // ---------- END Method 2 helpers ----------
            const charId = chatData?.character?.id;
            if (!charId)
                return {
                    creatorUrl: "",
                    characterVersion: "",
                    characterCardUrl: "",
                    name: "",
                    creatorNotes: "",
                    personality: "",
                    scenario: "",
                    firstMessage: "",
                    exampleDialogs: "",
                    definitionExposed: false,
                };

            // Check cache first - return cached values unless debug logging is enabled
            const skipCache = localStorage.getItem("showDebugLogs") === "true";
            if (
                !skipCache &&
                characterMetaCache.id === charId &&
                characterMetaCache.useChatNameForName === useChatNameForName
            ) {
                if (localStorage.getItem("showDebugLogs") === "true") {
                    console.log(
                        "[getCharacterMeta] Returning cached result for character:",
                        charId,
                    );
                }
                return {
                    creatorUrl: characterMetaCache.creatorUrl || "",
                    characterVersion: characterMetaCache.characterVersion || "",
                    characterCardUrl: characterMetaCache.characterCardUrl || "",
                    name: characterMetaCache.name || "",
                    creatorNotes: characterMetaCache.creatorNotes || "",
                    personality: characterMetaCache.personality || "",
                    scenario: characterMetaCache.scenario || "",
                    firstMessage: characterMetaCache.firstMessage || "",
                    exampleDialogs: characterMetaCache.exampleDialogs || "",
                    definitionExposed: characterMetaCache.definitionExposed || false,
                };
            }

            const characterCardUrl = getCharacterCardUrl(charId);
            let meta = {
                ...blankMeta,
                characterVersion: characterCardUrl,
                characterCardUrl,
            };
            try {
                const response = await fetch(characterCardUrl);
                const html = await response.text();
                const doc = new DOMParser().parseFromString(html, "text/html");
                // ---------- BEGIN Method 1 execution ----------
                let metaFromJson = null;
                try {
                    if (localStorage.getItem("showDebugLogs") === "true") {
                        console.log("[getCharacterMeta] Method 1: scanning <script> tags");
                    }
                    const scripts = Array.from(doc.querySelectorAll("script"));
                    for (const s of scripts) {
                        const txt = s.textContent || "";
                        if (!txt.includes("window.mbxM.push(JSON.parse(")) continue;

                        // Start searching after the "JSON.parse(" part of the string.
                        const startIndex = txt.indexOf("JSON.parse(") + "JSON.parse(".length;
                        if (startIndex === "JSON.parse(".length - 1) continue;

                        let openParenCount = 1;
                        let endIndex = -1;

                        // Manually scan for the matching closing parenthesis to correctly
                        // capture the entire argument, even with nested structures.
                        for (let i = startIndex; i < txt.length; i++) {
                            if (txt[i] === '(') {
                                openParenCount++;
                            } else if (txt[i] === ')') {
                                openParenCount--;
                                if (openParenCount === 0) {
                                    endIndex = i;
                                    break;
                                }
                            }
                        }

                        if (endIndex === -1) continue;

                        // Extract the full argument string, e.g., '"{\\"key\\":\\"value\\"}"'
                        const jsonArgString = txt.substring(startIndex, endIndex).trim();

                        try {
                            // The site uses a double-encoded JSON string. We must parse it twice.
                            // 1. Parse the JavaScript string literal to get the raw JSON content.
                            const jsonContent = JSON.parse(jsonArgString);
                            // 2. Parse the raw JSON content to get the final JavaScript object.
                            const dataObject = JSON.parse(jsonContent);

                            // Now, search for the character object within the parsed data.
                            metaFromJson = extractFromJson(dataObject);
                            if (metaFromJson) {
                                break; // Success! Exit the loop.
                            }
                        } catch (e) {
                            if (localStorage.getItem("showDebugLogs") === "true") {
                                console.warn("[getCharacterMeta] Failed to parse JSON from a script tag. Skipping.");
                            }
                            continue; // This JSON wasn't what we wanted, try the next script.
                        }
                    }
                } catch (parseErr) {
                    console.error(
                        "[getCharacterMeta] JSON parse error (Method 1):",
                        parseErr,
                    );
                }
                // ---------- END Method 1 execution ----------

                Object.assign(meta, metaFromJson || {}, {
                    creatorUrl: getCreatorUrlFromDoc(doc),
                    creatorNotes: chatData?.character?.description || "",
                });
            } catch (_) {}

            // Debug logging before cache assignment (shows every time when enabled)
            if (localStorage.getItem("showDebugLogs") === "true") {
                console.log(
                    "[getCharacterMeta] creator_url",
                    meta.creatorUrl,
                    "<= getCreatorUrlFromDoc",
                );

                if (!meta.definitionExposed) {
                    const src = "generateAlpha/chatData (showdefinition=false)";
                    console.log(
                        "[getCharacterMeta] name",
                        meta.name ||
                        chatData?.character?.name ||
                        chatData?.character?.chat_name,
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] personality",
                        chatData?.character?.personality || "extracted from generateAlpha",
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] scenario",
                        chatData?.character?.scenario || "extracted from generateAlpha",
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] first_message",
                        chatData?.character?.first_message || "extracted from generateAlpha",
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] example_dialogs",
                        chatData?.character?.example_dialogs ||
                        "extracted from generateAlpha",
                        `<= ${src}`,
                    );
                    console.log(
                        "[getCharacterMeta] creator_notes",
                        chatData?.character?.description,
                        `<= ${src}`,
                    );
                }
            }

            Object.assign(characterMetaCache, {
                id: charId,
                useChatNameForName,
                ...meta,
            });

            return meta;
        }

        async function buildTemplate(charBlock, scen, initMsg, exs) {
            const sections = [];
            const {
                creatorUrl,
                characterCardUrl,
                creatorNotes
            } =
            await getCharacterMeta();
            const includeCreatorNotes =
                localStorage.getItem("includeCreatorNotes") !== "false";

            const realName = chatData.character.name.trim();
            sections.push(`==== Name ====\n${realName}`);
            const chatName = (chatData.character.chat_name || realName).trim();
            sections.push(`==== Chat Name ====\n${chatName}`);
            if (charBlock) sections.push(`==== Description ====\n${charBlock.trim()}`);
            if (scen) sections.push(`==== Scenario ====\n${scen.trim()}`);
            if (initMsg) sections.push(`==== Initial Message ====\n${initMsg.trim()}`);
            if (exs) sections.push(`==== Example Dialogs ====\n${exs.trim()}`);
            sections.push(`==== Character Card ====\n${characterCardUrl}`);
            sections.push(`==== Creator ====\n${creatorUrl}`);
            if (includeCreatorNotes && creatorNotes)
                sections.push(`==== Creator Notes ====\n${creatorNotes}`);
            return sections.join("\n\n");
        }

        /**
         * Extracts the updated date from character page HTML
         * @param {Document} doc - Parsed HTML document from character page
         * @returns {string} - Date in format MM/DD/YYYY or empty string if not found
         */
        function extractUpdatedDate(doc) {
            try {
                const dateElement = doc.querySelector(".chakra-text.css-722v25");
                if (dateElement && dateElement.textContent) {
                    return dateElement.textContent.trim();
                }
            } catch (err) {
                console.warn("[extractUpdatedDate] Failed to extract date:", err);
            }
            return "";
        }

        /**
         * Builds filename from template using available tokens
         * @param {string} template - Template string with tokens like {id}, {name}, etc.
         * @param {Object} tokens - Available token values
         * @returns {string} - Processed filename
         */
        function buildFilenameFromTemplate(template, tokens = {}) {
            if (!template || typeof template !== "string") {
                return tokens.name || tokens.chat_name || "card";
            }

            if (!template || template.trim() === "") {
                return "";
            }

            let result = template;

            // Step 1: Create unique placeholders for double brackets to prevent interference
            const placeholders = {};
            Object.entries(tokens).forEach(([key, value]) => {
                if (
                    value !== undefined &&
                    value !== null &&
                    String(value).trim() !== ""
                ) {
                    const placeholder = `__TEMP_DOUBLE_${key}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}__`;
                    placeholders[placeholder] = `{${String(value)}}`;
                    const doubleBracketRegex = new RegExp(
                        `\\{\\{${escapeRegExp(key)}\\}\\}`,
                        "g",
                    );
                    result = result.replace(doubleBracketRegex, placeholder);
                }
            });

            // Step 2: Handle single brackets normally
            Object.entries(tokens).forEach(([key, value]) => {
                if (
                    value !== undefined &&
                    value !== null &&
                    String(value).trim() !== ""
                ) {
                    const singleBracketRegex = new RegExp(
                        `\\{${escapeRegExp(key)}\\}`,
                        "g",
                    );
                    result = result.replace(singleBracketRegex, String(value));
                }
            });

            // Step 3: Replace placeholders with final double bracket values
            Object.entries(placeholders).forEach(([placeholder, finalValue]) => {
                result = result.replace(
                    new RegExp(escapeRegExp(placeholder), "g"),
                    finalValue,
                );
            });

            // Clean up any remaining unreplaced tokens only if they don't have values
            result = result.replace(/\{\{[^}]+\}\}/g, "");
            result = result.replace(/\{[^}]+\}/g, "");

            // Clean up multiple spaces and trim
            result = result.replace(/\s+/g, " ").trim();

            // Strip all double quotes from the final filename to ensure compatibility
            result = result.replace(/"/g, '');

            // Return actual result or fallback to "export" only if completely empty
            return result || "export";
        }

        /**
         * Gets all available tokens for filename template
         * @param {Object} meta - Character metadata from getCharacterMeta
         * @returns {Object} - Object with all available tokens
         */
        async function getFilenameTokens(meta) {
            const charId = chatData?.character?.id || "";
            let creatorName = meta?.creatorUrl ?
                meta.creatorUrl.split("/").pop() || "" :
                "";

            let updatedDate = "";
            let createdDate = "";
            let tagsForCard = [];
            let tagsString = "";
            const isDebug = localStorage.getItem("showDebugLogs") === "true";

            try {
                if (isDebug) console.log(`[getFilenameTokens] Fetching character page: ${meta.characterCardUrl}`);
                const response = await fetch(meta.characterCardUrl);
                const html = await response.text();
                if (isDebug) console.log(`[getFilenameTokens] Successfully fetched HTML content.`);

                const match = html.match(/window\.mbxM\.push\(JSON\.parse\("([\s\S]*?)"\)\)/);

                if (isDebug) console.log(`[getFilenameTokens] Regex search for 'window.mbxM.push' was ${match ? 'SUCCESSFUL' : 'FAILED'}.`);

                if (match && match[1]) {
                    try {
                        if (isDebug) console.log(`[getFilenameTokens] Match found. Attempting to parse JSON...`);
                        const decoded = JSON.parse(`"${match[1]}"`);
                        const storeState = JSON.parse(decoded);
                        if (isDebug) console.log(`[getFilenameTokens] Successfully parsed storeState object.`);

                        let characterData = null;
                        for (const key in storeState) {
                            if (storeState[key]?.character?.tags) {
                                characterData = storeState[key].character;
                                if (isDebug) console.log(`[getFilenameTokens] Found character data object under key: ${key}`);
                                break;
                            }
                        }

                        if (characterData) {
                            const allTags = [];

                            if (characterData?.tags) {
                                const regularTags = characterData.tags
                                    .filter((tag) => typeof tag.id === "number" && tag.id >= 2)
                                    .map((tag) => tag.name);
                                allTags.push(...regularTags);
                            }

                            if (characterData?.custom_tags) {
                                allTags.push(...characterData.custom_tags);
                            }

                            const rawUniqueTags = [...new Set(allTags)];
                            if (isDebug) console.log(`[getFilenameTokens] Found raw unique tags with filtering:`, rawUniqueTags);

                            tagsForCard = rawUniqueTags
                                .map((tag) =>
                                    tag
                                    .replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}\uFE0F\u200D]/gu, "")
                                    .trim(),
                                )
                                .filter((tag) => tag.length > 0)
                                .filter((name, index, arr) => arr.indexOf(name) === index);

                            const tagsForFilename = rawUniqueTags
                                .map((tag) => {
                                    let cleanTag = tag
                                        .replace(/🦰/g, "")
                                        .replace(/_{2,}/g, "_")
                                        .replace(/\s{2,}/g, " ")
                                        .trim();
                                    if (/^[\p{Emoji}]/u.test(cleanTag)) {
                                        cleanTag = cleanTag.replace(
                                            /([\p{Emoji}\uFE0F]+)\s+([\p{Emoji}\uFE0F]+)/gu,
                                            "$1$2",
                                        );
                                        cleanTag = cleanTag.replace(
                                            /([\p{Emoji}\uFE0F]+)\s+/u,
                                            "$1_",
                                        );
                                        cleanTag = cleanTag.replace(/\s/g, "");
                                    } else {
                                        cleanTag = cleanTag.replace(/\s/g, "");
                                    }
                                    return cleanTag;
                                })
                                .filter((tag) => tag.length > 0);

                            tagsString = tagsForFilename.join(" ");

                            if (isDebug) {
                                console.log("[getFilenameTokens] Extracted tags for card:", tagsForCard);
                                console.log("[getFilenameTokens] Extracted tags for filename:", tagsString);
                            }

                            if (characterData.created_at) {
                                const [year, month, day] = characterData.created_at.split("T")[0].split("-");
                                createdDate = `${parseInt(month)}-${parseInt(day)}-${year}`;
                            }
                            if (characterData.updated_at) {
                                const [year, month, day] = characterData.updated_at.split("T")[0].split("-");
                                updatedDate = `${parseInt(month)}-${parseInt(day)}-${year}`;
                            }
                            if (characterData.creator_name) {
                                creatorName = characterData.creator_name;
                            }
                        } else {
                            if (isDebug) console.warn(`[getFilenameTokens] Could not find character data object within the parsed storeState.`);
                        }
                    } catch (parseError) {
                        if (isDebug) console.error("[getFilenameTokens] JSON parse error:", parseError);
                    }
                }
            } catch (err) {
                if (isDebug) console.error("[getFilenameTokens] Failed to fetch or process character page:", err);
            }

            return {
                id: charId,
                creator: creatorName,
                name: meta?.name || chatData?.character?.name || "",
                chat_name: chatData?.character?.chat_name || "",
                created: createdDate,
                updated: updatedDate,
                tags: tagsString,
                tagsArray: tagsForCard,
            };
        }

        async function saveAsTxt(charBlock, scen, initMsg, exs, charName, userName) {
            const template = await buildTemplate(charBlock, scen, initMsg, exs);
            const tokenized = tokenizeNames(template, charName, userName);

            // Use filename template system
            const meta = await getCharacterMeta();
            const tokens = await getFilenameTokens(meta);
            const currentTemplate =
                localStorage.getItem("filenameTemplate") || "{name}";
            const fileName =
                buildFilenameFromTemplate(currentTemplate, tokens) || "export";

            saveFile(
                `${fileName}.txt`,
                new Blob([tokenized], {
                    type: "text/plain",
                }),
            );
        }

        /* ============================
           ==      INTERCEPTORS     ==
           ============================ */
        function extraction() {
            if (!exportFormat) return;
            if (
                !document.querySelector("span.css-yhlqn1") &&
                !document.querySelector("span.css-154nobl")
            )
                return;

            (async () => {
                const meta = await getCharacterMeta();

                // Method 1
                if (meta.definitionExposed) {
                    const charName = meta.name;
                    const userName = cachedUserName;
                    switch (exportFormat) {
                        case "txt":
                            saveAsTxt(
                                meta.personality,
                                meta.scenario,
                                meta.firstMessage,
                                meta.exampleDialogs,
                                charName,
                                userName,
                            );
                            break;
                        case "png":
                            saveAsPng(
                                charName,
                                meta.personality,
                                meta.scenario,
                                meta.firstMessage,
                                meta.exampleDialogs,
                                userName,
                            );
                            break;
                        case "json":
                            saveAsJson(
                                charName,
                                meta.personality,
                                meta.scenario,
                                meta.firstMessage,
                                meta.exampleDialogs,
                                userName,
                            );
                            break;
                    }
                    exportFormat = null;
                    return;
                }

                shouldInterceptNext = true;
                interceptNetwork();
                callApi();
            })();
        }

        function callApi() {
            try {
                const textarea = document.querySelector("textarea");
                if (!textarea) return;

                Object.getOwnPropertyDescriptor(
                    HTMLTextAreaElement.prototype,
                    "value",
                ).set.call(textarea, "extract-char");
                textarea.dispatchEvent(
                    new Event("input", {
                        bubbles: true,
                    }),
                );

                ["keydown", "keyup"].forEach((type) =>
                    textarea.dispatchEvent(
                        new KeyboardEvent(type, {
                            key: "Enter",
                            code: "Enter",
                            bubbles: true,
                        }),
                    ),
                );
            } catch (err) {
                // ignore errors
            }
        }

        /* ============================
           ==    CHARA CARD V2      ==
           ============================ */
        async function buildCharaCardV2(
            charName,
            charBlock,
            scen,
            initMsg,
            exs,
            userName,
            tagsArray,
            avatarUrl,
        ) {
            const {
                creatorUrl,
                characterVersion,
                name: metaName,
                creatorNotes
            } =
            await getCharacterMeta();

            const tokenizedDesc = tokenizeField(charBlock, charName, userName);
            const tokenizedScen = tokenizeField(scen, charName, userName);
            const tokenizedExs = tokenizeField(exs, charName, userName);

            let displayName;
            if (useChatNameForName) {
                displayName =
                    (
                        chatData?.character?.chat_name ||
                        metaName ||
                        chatData?.character?.name ||
                        ""
                    ).trim();
            } else {
                displayName =
                    (
                        metaName ||
                        chatData?.character?.name ||
                        chatData?.character?.chat_name ||
                        ""
                    ).trim();
            }

            if (displayName) {
                displayName = displayName.replace(/"/g, '');
            }

            if (!displayName) displayName = "Unknown";

            /* --------------------
                     Build version text
                  -------------------- */
            let versionText = characterVersion;
            if (useChatNameForName) {
                const canonicalName = (
                    metaName ||
                    chatData?.character?.name ||
                    ""
                ).trim();
                if (canonicalName && canonicalName !== displayName) {
                    versionText = `${characterVersion}\nName: ${canonicalName}`;
                }
            }

            const includeCreatorNotes =
                localStorage.getItem("includeCreatorNotes") !== "false";
            const includeTags = localStorage.getItem("includeTags") !== "false";

            return {
                spec: "chara_card_v2",
                spec_version: "2.0",
                data: {
                    name: displayName,
                    description: tokenizedDesc.trim(),
                    personality: "",
                    scenario: tokenizedScen.trim(),
                    first_mes: initMsg.trim(),
                    mes_example: tokenizedExs.trim(),
                    creator_notes: includeCreatorNotes ? creatorNotes : "",
                    system_prompt: "",
                    post_history_instructions: "",
                    alternate_greetings: [],
                    character_book: null,
                    tags: includeTags ? tagsArray || [] : [],
                    creator: creatorUrl,
                    character_version: versionText,
                    avatar: avatarUrl,
                    extensions: {},
                },
            };
        }

        /* ============================
           ==       EXPORTERS       ==
           ============================ */
        async function saveAsJson(charName, charBlock, scen, initMsg, exs, userName) {
            const meta = await getCharacterMeta();
            const tokens = await getFilenameTokens(meta);

            const avatarImg = document.querySelector('img[src*="/bot-avatars/"]');
            const avatarUrl = avatarImg ? avatarImg.src : 'none';

            const jsonData = await buildCharaCardV2(
                charName,
                charBlock,
                scen,
                initMsg,
                exs,
                userName,
                tokens.tagsArray
                // avatarUrl,
            );

            // Use filename template system
            const currentTemplate =
                localStorage.getItem("filenameTemplate") || "{name}";
            const fileName =
                buildFilenameFromTemplate(currentTemplate, tokens) || "export";

            saveFile(
                `${fileName}.json`,
                new Blob([JSON.stringify(jsonData, null, 2)], {
                    type: "application/json",
                }),
            );
        }

        async function saveAsPng(charName, charBlock, scen, initMsg, exs, userName) {
            try {
                const avatarImg = document.querySelector('img[src*="/bot-avatars/"]');
                if (!avatarImg) {
                    alert("Character avatar not found.");
                    return;
                }

                const meta = await getCharacterMeta();
                const tokens = await getFilenameTokens(meta);

                const cardData = await buildCharaCardV2(
                    charName,
                    charBlock,
                    scen,
                    initMsg,
                    exs,
                    userName,
                    tokens.tagsArray,
                );

                const avatarResponse = await fetch(avatarImg.src);
                const avatarBlob = await avatarResponse.blob();

                const img = new Image();
                img.onload = () => {
                    const canvas = document.createElement("canvas");
                    canvas.width = img.width;
                    canvas.height = img.height;

                    const ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0);

                    canvas.toBlob(async (blob) => {
                        try {
                            const arrayBuffer = await blob.arrayBuffer();
                            const pngData = new Uint8Array(arrayBuffer);

                            const jsonString = JSON.stringify(cardData);

                            const base64Data = btoa(unescape(encodeURIComponent(jsonString)));

                            const keyword = "chara";
                            const keywordBytes = new TextEncoder().encode(keyword);
                            const nullByte = new Uint8Array([0]);
                            const textBytes = new TextEncoder().encode(base64Data);

                            const chunkType = new Uint8Array([116, 69, 88, 116]); // "tEXt" in ASCII
                            const dataLength =
                                keywordBytes.length + nullByte.length + textBytes.length;

                            const lengthBytes = new Uint8Array(4);
                            lengthBytes[0] = (dataLength >>> 24) & 0xff;
                            lengthBytes[1] = (dataLength >>> 16) & 0xff;
                            lengthBytes[2] = (dataLength >>> 8) & 0xff;
                            lengthBytes[3] = dataLength & 0xff;

                            const crcData = new Uint8Array(
                                chunkType.length +
                                keywordBytes.length +
                                nullByte.length +
                                textBytes.length,
                            );
                            crcData.set(chunkType, 0);
                            crcData.set(keywordBytes, chunkType.length);
                            crcData.set(nullByte, chunkType.length + keywordBytes.length);
                            crcData.set(
                                textBytes,
                                chunkType.length + keywordBytes.length + nullByte.length,
                            );

                            const crc = computeCrc32(crcData, 0, crcData.length);
                            const crcBytes = new Uint8Array(4);
                            crcBytes[0] = (crc >>> 24) & 0xff;
                            crcBytes[1] = (crc >>> 16) & 0xff;
                            crcBytes[2] = (crc >>> 8) & 0xff;
                            crcBytes[3] = crc & 0xff;

                            let pos = 8; // Skip PNG signature
                            while (pos < pngData.length - 12) {
                                const length =
                                    (pngData[pos] << 24) |
                                    (pngData[pos + 1] << 16) |
                                    (pngData[pos + 2] << 8) |
                                    pngData[pos + 3];

                                const type = String.fromCharCode(
                                    pngData[pos + 4],
                                    pngData[pos + 5],
                                    pngData[pos + 6],
                                    pngData[pos + 7],
                                );

                                if (type === "IEND") break;
                                pos += 12 + length; // 4 (length) + 4 (type) + length + 4 (CRC)
                            }

                            const finalSize =
                                pngData.length +
                                lengthBytes.length +
                                chunkType.length +
                                dataLength +
                                crcBytes.length;
                            const finalPNG = new Uint8Array(finalSize);

                            finalPNG.set(pngData.subarray(0, pos));
                            let writePos = pos;

                            finalPNG.set(lengthBytes, writePos);
                            writePos += lengthBytes.length;
                            finalPNG.set(chunkType, writePos);
                            writePos += chunkType.length;
                            finalPNG.set(keywordBytes, writePos);
                            writePos += keywordBytes.length;
                            finalPNG.set(nullByte, writePos);
                            writePos += nullByte.length;
                            finalPNG.set(textBytes, writePos);
                            writePos += textBytes.length;
                            finalPNG.set(crcBytes, writePos);
                            writePos += crcBytes.length;

                            finalPNG.set(pngData.subarray(pos), writePos);

                            // Use filename template system
                            const currentTemplate =
                                localStorage.getItem("filenameTemplate") || "{name}";
                            const fileName =
                                buildFilenameFromTemplate(currentTemplate, tokens) || "export";

                            saveFile(
                                `${fileName}.png`,
                                new Blob([finalPNG], {
                                    type: "image/png",
                                }),
                            );

                            console.log("Character card created successfully!");
                        } catch (err) {
                            console.error("Error creating PNG:", err);
                            alert("Failed to create PNG: " + err.message);
                        }
                    }, "image/png");
                };

                img.src = URL.createObjectURL(avatarBlob);
            } catch (err) {
                console.error("Error creating PNG:", err);
                alert("Failed to create PNG: " + err.message);
            }
        }

        function computeCrc32(data, start, length) {
            let crc = 0xffffffff;

            for (let i = 0; i < length; i++) {
                const byte = data[start + i];
                crc = (crc >>> 8) ^ crc32Table[(crc ^ byte) & 0xff];
            }

            return ~crc >>> 0; // Invert and cast to unsigned 32-bit
        }

        const crc32Table = (() => {
            const table = new Uint32Array(256);

            for (let i = 0; i < 256; i++) {
                let crc = i;
                for (let j = 0; j < 8; j++) {
                    crc = crc & 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
                }
                table[i] = crc;
            }

            return table;
        })();

        /* ============================
           ==       ROUTING         ==
           ============================ */
        function inChats() {
            const isInChat = /^\/chats\/\d+/.test(window.location.pathname);
            return isInChat;
        }

        function initialize() {
            if (hasInitialized || !inChats()) return;
            hasInitialized = true;
            shouldInterceptNext = false;
            networkInterceptActive = false;
            exportFormat = null;
            chatData = null;

            document.removeEventListener("keydown", handleKeyDown);
            document.addEventListener("keydown", handleKeyDown);

            interceptNetwork();
        }

        function handleKeyDown(e) {
            if (!inChats()) return;
            if (
                e.key.toLowerCase() !== "t" ||
                e.ctrlKey ||
                e.metaKey ||
                e.altKey ||
                e.shiftKey
            )
                return;
            if (
                ["INPUT", "TEXTAREA"].includes(document.activeElement.tagName) ||
                document.activeElement.isContentEditable
            )
                return;

            if (!chatData || !chatData.character || !chatData.character.allow_proxy) {
                if (chatData && chatData.character) {
                    alert("Proxy disabled — extraction aborted.");
                }
                return;
            }

            viewActive = !viewActive;
            toggleUIState();
        }

        function cleanup() {
            hasInitialized = false;
            const gui = document.getElementById("char-export-gui");
            if (gui) gui.remove();
            document.removeEventListener("keydown", handleKeyDown);
            viewActive = false;
            animationTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
            animationTimeouts = [];
        }

        function handleRoute() {
            if (inChats()) {
                initialize();
            } else {
                cleanup();
            }
        }

        /* ============================
           ==    EVENT LISTENERS    ==
           ============================ */

        // Initialize unified auto-restore system
        function initializeAutoRestore() {
            // Debounced restoration for window events to prevent conflicts
            const debouncedRestore = () => {
                if (!restorationInProgress) {
                    inputStateManager.restoreAll(false);
                }
            };

            // Multiple event listeners for comprehensive coverage
            ["beforeunload", "pagehide"].forEach((event) => {
                window.addEventListener(event, debouncedRestore);
            });

            // Separate handler for visibility changes
            window.addEventListener("visibilitychange", () => {
                if (document.visibilityState === "hidden") {
                    debouncedRestore();
                }
            });
        }

        // Initialize the auto-restore system
        initializeAutoRestore();

        document.addEventListener("DOMContentLoaded", () => {
            const style = document.createElement("style");
            style.textContent = `
        /* Disable all browser tooltips inside the UI */
        #char-export-gui input,
        #char-export-gui input:hover,
        #char-export-gui input:focus,
        #char-export-gui textarea,
        #char-export-gui textarea:hover,
        #char-export-gui textarea:focus {
          title: "" !important;
        }

        #char-export-gui input[title],
        #char-export-gui textarea[title] {
          title: "" !important;
        }

        /* Enhanced toggle animations */
        .toggle-wrapper {
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
        }

        .toggle-wrapper:hover {
          transform: translateY(-1px) scale(1.005) !important;
          box-shadow: 0 6px 16px rgba(0,0,0,0.15) !important;
        }

        .toggle-wrapper.active {
          border-color: rgba(0, 128, 255, 0.4) !important;
          box-shadow: 0 4px 12px rgba(0, 128, 255, 0.15) !important;
        }
      `;
            document.head.appendChild(style);
        });

        /* ============================
           ==      ENTRYPOINT       ==
           ============================ */
        window.addEventListener(
            "load",
            () => {
                handleRoute();
            }, {
                once: true,
            },
        );
        window.addEventListener("popstate", () => {
            handleRoute();
        });

        ["pushState", "replaceState"].forEach((fn) => {
            const orig = history[fn];
            history[fn] = function(...args) {
                const res = orig.apply(this, args);
                setTimeout(handleRoute, 50);
                return res;
            };
        });
        handleRoute();
    }
    try {
        if (typeof unsafeWindow === 'undefined' || unsafeWindow === window) {
            runInPageContext();
        } else {
            const s = document.createElement('script');
            s.textContent = '(' + runInPageContext.toString() + ')();';
            document.documentElement.appendChild(s);
            s.remove();
        }
    } catch (e) {
        const s = document.createElement('script');
        s.textContent = '(' + runInPageContext.toString() + ')();';
        document.documentElement.appendChild(s);
        s.remove();
    }
})();