Better Greasy Fork

Enhances Greasy Fork by displaying the script icon next to the title, adding an HTML toolbar for comments/descriptions, and a button to directly download the “.user.js” file from the code page. It also enriches pages with author customizations via metadata, including highlight colors, copyright info, and social icons, among other improvements.

// ==UserScript==
// @name                Better Greasy Fork
// @name:pt-BR          Greasy Fork Aprimorado
// @name:zh-CN          更好的 Greasy Fork
// @name:zh-TW          更好的 Greasy Fork
// @name:en             Better Greasy Fork
// @name:es             Greasy Fork Mejorado
// @name:ja             改良版 Greasy Fork
// @name:ko             향상된 Greasy Fork
// @name:de             Verbesserter Greasy Fork
// @name:fr             Greasy Fork Amélioré
// @namespace           https://github.com/0H4S
// @version             1.4
// @description         Enhances Greasy Fork by displaying the script icon next to the title, adding an HTML toolbar for comments/descriptions, and a button to directly download the “.user.js” file from the code page. It also enriches pages with author customizations via metadata, including highlight colors, copyright info, and social icons, among other improvements.
// @description:pt-BR   Melhora o Greasy Fork exibindo o ícone do script ao lado do título, adiciona uma barra de ferramentas HTML para comentários/descrições e um botão para baixar o arquivo “.user.js” direto da página do código. Também enriquece as páginas com personalizações do autor via metadados, incluindo cores de destaque, direitos autorais e ícones sociais, além de outras melhorias.
// @description:zh-CN   优化 Greasy Fork,在标题旁显示脚本图标,添加用于评论/描述的 HTML 工具栏,并在代码页面添加按钮以直接下载 “.user.js” 文件。还通过元数据增强页面,包括作者自定义的主题颜色、版权信息和社交图标等功能。
// @description:zh-TW   改進 Greasy Fork,在標題旁顯示腳本圖示,新增 HTML 工具列以支援評論與描述,並在程式碼頁面提供按鈕,可直接下載 “.user.js” 檔案。同時透過中繼資料增強頁面,包含作者自訂的重點顏色、版權資訊與社交圖示等功能。
// @description:en      Enhances Greasy Fork by showing the script icon beside titles, adding an HTML toolbar for comments/descriptions, and a button to download the “.user.js” file directly from the code page. Also enriches pages with author metadata customization such as highlight colors, copyright, and social icons.
// @description:es      Mejora Greasy Fork mostrando el ícono del script junto al título, agregando una barra de herramientas HTML para comentarios/descripciones y un botón para descargar directamente el archivo “.user.js” desde la página del código. También enriquece las páginas con personalizaciones del autor mediante metadatos, como colores destacados, derechos de autor e íconos sociales.
// @description:ja      Greasy Fork を強化し、タイトル横にスクリプトアイコンを表示、コメントや説明用の HTML ツールバーを追加し、コードページから “.user.js” ファイルを直接ダウンロードできるボタンを追加します。また、メタデータを使って作者のカスタマイズ(強調色、著作権情報、ソーシャルアイコンなど)をページに反映します。
// @description:ko      Greasy Fork을 개선하여 제목 옆에 스크립트 아이콘을 표시하고, 댓글/설명용 HTML 도구 모음을 추가하며, 코드 페이지에서 “.user.js” 파일을 직접 다운로드할 수 있는 버튼을 제공합니다. 또한 메타데이터를 통해 강조 색상, 저작권 정보, 소셜 아이콘 등 작성자 맞춤 요소로 페이지를 풍부하게 만듭니다.
// @description:de      Verbessert Greasy Fork, indem das Skriptsymbol neben dem Titel angezeigt, eine HTML-Symbolleiste für Kommentare/Beschreibungen hinzugefügt und eine Schaltfläche bereitgestellt wird, um die “.user.js”-Datei direkt von der Code-Seite herunterzuladen. Außerdem werden Seiten mit Autorenanpassungen über Metadaten, einschließlich Hervorhebungsfarben, Urheberrecht und sozialen Symbolen, bereichert.
// @description:fr      Améliore Greasy Fork en affichant l’icône du script à côté du titre, en ajoutant une barre d’outils HTML pour les commentaires/descriptions et un bouton permettant de télécharger directement le fichier “.user.js” depuis la page du code. Enrichit également les pages avec des personnalisations d’auteur via les métadonnées, comme les couleurs de surbrillance, le copyright et les icônes sociales.
// @author              OHAS
// @license             CC-BY-NC-ND-4.0
// @match               https://greasyfork.org/*
// @icon                data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgIDxzdHlsZT4KICAgICAgICAuZGVzZW5oYXItY29udG9ybm8gewogICAgICAgICAgICBzdHJva2UtZGFzaGFycmF5OiAxMDA7CiAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAxMDA7CiAgICAgICAgICAgIGFuaW1hdGlvbjogZGVzZW5oYXItY29udG9ybm8gMTBzIGVhc2UtaW4tb3V0IGluZmluaXRlOwogICAgICAgIH0KCiAgICAgICAgLm9uZGEtcHJlZW5jaGltZW50byB7CiAgICAgICAgICAgIGFuaW1hdGlvbjogb25kYS1wcmVlbmNoaW1lbnRvIDEwcyBlYXNlLWluLW91dCBpbmZpbml0ZTsKICAgICAgICB9CgogICAgICAgIEBrZXlmcmFtZXMgZGVzZW5oYXItY29udG9ybm8gewogICAgICAgICAgICAwJSB7CiAgICAgICAgICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogMTAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgMjUlIHsKICAgICAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgNzUlIHsKICAgICAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgMTAwJSB7CiAgICAgICAgICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogLTEwMDsKICAgICAgICAgICAgICAgIHN0cm9rZTogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBAa2V5ZnJhbWVzIG9uZGEtcHJlZW5jaGltZW50byB7CiAgICAgICAgICAgIDAlIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgyNHB4KTsKICAgICAgICAgICAgICAgIGZpbGw6ICNmZmZmZmZmZjsKICAgICAgICAgICAgfQogICAgICAgICAgICAyNSUgewogICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7CiAgICAgICAgICAgICAgICBmaWxsOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgNzUlIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgwcHgpOwogICAgICAgICAgICAgICAgZmlsbDogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgICAgIDEwMCUgewogICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDI0cHgpOwogICAgICAgICAgICAgICAgZmlsbDogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxkZWZzPgogICAgICAgIDxjbGlwUGF0aCBpZD0ibW9sZGUtZ2FyZm8iPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44OSAyLjIyN2EuMjguMjggMCAwIDEgLjI2Ni4wNzZsNS4wNjMgNS4wNjJjLjU0LjU0LjUwOSAxLjY1Mi0uMDMxIDIuMTkybDguNzcxIDguNzdjMS4zNTYgMS4zNTUtLjM2IDMuMDk3LTEuNzMgMS43MjhsLTguNzcyLTguNzdjLS41NC41NC0xLjY1MS41NzEtMi4xOTEuMDMxbC01LjA2My01LjA2Yy0uMzA0LS4zMDQuMzA0LS45MTEuNjA4LS42MDhsMy43MTQgMy43MTNMNy41OSA4LjI5N0wzLjg3NSA0LjU4MmMtLjMwNC0uMzA0LjMwNC0uOTExLjYwNy0uNjA3bDMuNzE1IDMuNzE0bDEuMDY3LTEuMDY2TDUuNTQ5IDIuOTFjLS4yMjgtLjIyOC4wNTctLjYyNi4zNDItLjY4M1oiLz4KICAgICAgICA8L2NsaXBQYXRoPgogICAgPC9kZWZzPgogICAgPGc+CiAgICAgICAgPHBhdGggZmlsbD0iIzAwMDAwMGZmIiBkPSJNMTIgMEM1LjM3NCAwIDAgNS4zNzUgMCAxMnM1LjM3NCAxMiAxMiAxMmM2LjYyNSAwIDEyLTUuMzc1IDEyLTEyUzE4LjYyNSAwIDEyIDAiLz4KICAgICAgICA8ZyBjbGlwLXBhdGg9InVybCgjbW9sZGUtZ2FyZm8pIj4KICAgICAgICAgICAgPHBhdGggY2xhc3M9Im9uZGEtcHJlZW5jaGltZW50byIgCiAgICAgICAgICAgICAgICAgIGQ9Ik0gLTIsMjQgTCAtMiwwIGMgNiwtNSA2LDUgMTIsMCBzIDYsLTUgMTIsMCBzIDYsLTUgMTIsMCBMIDMwLDI0IFoiLz4KICAgICAgICA8L2c+CiAgICAgICAgPHBhdGggY2xhc3M9ImRlc2VuaGFyLWNvbnRvcm5vIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjAuNSIgZD0iTTUuODkgMi4yMjdhLjI4LjI4IDAgMCAxIC4yNjYuMDc2bDUuMDYzIDUuMDYyYy41NC41NC41MDkgMS42NTItLjAzMSAyLjE5Mmw4Ljc3MSA4Ljc3YzEuMzU2IDEuMzU1LS4zNiAzLjA5Ny0xLjczIDEuNzI4bC04Ljc3Mi04Ljc3Yy0uNTQuNTQtMS42NTEuNTcxLTIuMTkxLjAzMWwtNS4wNjMtNS4wNmMtLjMwNC0uMzA0LjMwNC0uOTExLjYwOC0uNjA4bDMuNzE0IDMuNzEzTDcuNTkgOC4yOTdMMy44NzUgNC41ODJjLS4zMDQtLjMwNC4zMDQtLjkxMS42MDctLjYwN2wzLjcxNSAzLjcxNGwxLjA2Ny0xLjA2Nkw1LjU0OSAyLjkxYy0uMjI4LS4yMjguMDU3LS42MjYuMzQyLS42ODNaIi8+CiAgICA8L2c+Cjwvc3ZnPg==
// @resource            customCSS https://cdn.jsdelivr.net/gh/0H4S/Better-Greasy-Fork/estilo.css
// @resource            iconsJSON https://cdn.jsdelivr.net/gh/0H4S/Better-Greasy-Fork/icones.json
// @require             https://update.greasyfork.org/scripts/549920.js
// @connect             cdn.jsdelivr.net
// @connect             update.greasyfork.org
// @grant               GM_addStyle
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_deleteValue
// @grant               GM_xmlhttpRequest
// @grant               GM_getResourceText
// @grant               GM_registerMenuCommand
// @run-at              document-idle
// @compatible          chrome
// @compatible          firefox
// @compatible          edge
// @bgf-colorLT         #0059ffff
// @bgf-colorDT         #ffffffff
// @bgf-copyright       [2025 OHAS. All Rights Reserved.](https://gist.github.com/0H4S/ae2fa82957a089576367e364cbf02438)
// @bgf-compatible      brave, mobile
// @bgf-social          https://github.com/0H4S, https://www.instagram.com/o_h_a_s
// @contributionURL     https://linktr.ee/0H4S
// ==/UserScript==

(function () {
    'use strict';

    // ================
    // #region GLOBAL
    // ================

    if (window.top !== window.self) {
        return;
    }
    const SCRIPT_CONFIG = {
        notificationsUrl: 'https://cdn.jsdelivr.net/gh/0H4S/Better-Greasy-Fork/notifications.json',
        scriptVersion: '1.4',
    };
    const notifier = new ScriptNotifier(SCRIPT_CONFIG);
    notifier.run();
    const CACHE_KEY = 'Values';

    const allTranslations = {
    "en": {
        "langName": "English",
        "languageSettings": "🌐 Language",
        "close": "Close",
        "confirm": "Confirm",
        "cancel": "Cancel",
        "download": "Download",
        "compatible_with": "Compatible with",
        "force_update": "🔄️ Force Update",
        "force_update_alert": "Cache cleared. The page will reload to fetch the updated data.",
        "titles": "Headings",
        "title_placeholder": "Heading",
        "bold": "Bold",
        "bold_placeholder": "bold text",
        "italic": "Italic",
        "italic_placeholder": "italic text",
        "underline": "Underline",
        "underline_placeholder": "underlined text",
        "strikethrough": "Strikethrough",
        "strikethrough_placeholder": "strikethrough text",
        "unordered_list": "Unordered List",
        "ordered_list": "Ordered List",
        "list_item_placeholder": "Item",
        "quote": "Quote",
        "quote_placeholder": "Quote",
        "inline_code": "Inline Code",
        "inline_code_placeholder": "code",
        "code_block": "Code Block",
        "code_block_placeholder": "code here",
        "horizontal_line": "Horizontal Line",
        "horizontal_line_style": "Horizontal Line Style",
        "prompt_hr_size": "Size (px)",
        "prompt_hr_color": "Color (HEX)",
        "link": "Link",
        "prompt_insert_url": "Enter the URL:",
        "link_text_placeholder": "link text",
        "image": "Image",
        "prompt_insert_image_url": "Enter the image URL (https):",
        "prompt_image_title": "Image title (optional):",
        "image_title_placeholder": "e.g. My beautiful image",
        "prompt_image_width": "Width (optional):",
        "prompt_image_height": "Height (optional):",
        "video": "Video (YouTube/Bilibili)",
        "prompt_insert_video_url": "Enter the video URL (YouTube or Bilibili):",
        "prompt_video_width": "Width (optional):",
        "prompt_video_height": "Height (optional):",
        "alert_invalid_video_url": "Invalid or unsupported video URL.",
        "table": "Table",
        "prompt_columns": "Number of columns:",
        "prompt_rows": "Number of rows:",
        "table_header_placeholder": "Header",
        "table_cell_placeholder": "Cell",
        "subscript": "Subscript",
        "subscript_placeholder": "sub",
        "superscript": "Superscript",
        "superscript_placeholder": "sup",
        "highlight": "Highlight",
        "highlight_placeholder": "highlighted",
        "keyboard": "Keyboard",
        "keyboard_placeholder": "Ctrl+C",
        "abbreviation": "Abbreviation",
        "prompt_abbreviation_meaning": "What does the abbreviation stand for?",
        "abbreviation_placeholder": "HTML",
        "text_color": "Text Color",
        "colored_text_placeholder": "colored text",
        "background_color": "Background Color",
        "colored_background_placeholder": "colored background",
        "details": "Collapsible section",
        "details_summary_placeholder": "Summary or Title",
        "details_content_placeholder": "Content to be hidden...",
        "center": "Center Align",
        "center_placeholder": "centered text",
        "notFound": "Code not found!",
        "scriptIdNotFound": "Could not identify the script ID.",
        "downloading": "Downloading...",
        "downloadError": "An error occurred while downloading the script.",
        "downloadTimeout": "The script download timed out.",
        "info_tooltip": "Shortcuts",
        "info_shortcuts_title": "Keyboard Shortcuts",
        "info_header_shortcut": "Shortcut",
        "info_header_action": "Action",
        "info_shortcut_tab": "Inserts a tab space.",
        "info_shortcut_shift_enter": "Inserts a line break <br>.",
        "close": "Close"
    },
    "pt-BR": {
        "langName": "Português (BR)",
        "languageSettings": "🌐 Idioma",
        "close": "Fechar",
        "confirm": "Confirmar",
        "cancel": "Cancelar",
        "download": "Baixar",
        "compatible_with": "Compatível com",
        "force_update": "🔄️ Forçar Atualização",
        "force_update_alert": "O cache foi limpo. A página será recarregada para buscar os dados atualizados.",
        "titles": "Títulos",
        "title_placeholder": "Título",
        "bold": "Negrito",
        "bold_placeholder": "negrito",
        "italic": "Itálico",
        "italic_placeholder": "itálico",
        "underline": "Sublinhado",
        "underline_placeholder": "sublinhado",
        "strikethrough": "Riscado",
        "strikethrough_placeholder": "riscado",
        "unordered_list": "Lista não ordenada",
        "ordered_list": "Lista ordenada",
        "list_item_placeholder": "Item",
        "quote": "Citação",
        "quote_placeholder": "Citação",
        "inline_code": "Código Inline",
        "inline_code_placeholder": "código",
        "code_block": "Bloco de Código",
        "code_block_placeholder": "código aqui",
        "horizontal_line": "Linha Horizontal",
        "horizontal_line_style": "Estilo da Linha Horizontal",
        "prompt_hr_size": "Tamanho (px)",
        "prompt_hr_color": "Cor (HEX)",
        "link": "Link",
        "prompt_insert_url": "Insira a URL:",
        "link_text_placeholder": "texto do link",
        "image": "Imagem",
        "prompt_insert_image_url": "Insira a URL da imagem (https):",
        "prompt_image_title": "Título da imagem (opcional):",
        "image_title_placeholder": "ex: Minha bela imagem",
        "prompt_image_width": "Largura (opcional):",
        "prompt_image_height": "Altura (opcional):",
        "video": "Vídeo (YouTube/Bilibili)",
        "prompt_insert_video_url": "Insira a URL do vídeo (YouTube ou Bilibili):",
        "prompt_video_width": "Largura (opcional):",
        "prompt_video_height": "Altura (opcional):",
        "alert_invalid_video_url": "URL de vídeo inválida ou não suportada.",
        "table": "Tabela",
        "prompt_columns": "Número de colunas:",
        "prompt_rows": "Número de linhas:",
        "table_header_placeholder": "Cabeçalho",
        "table_cell_placeholder": "Célula",
        "subscript": "Subscrito",
        "subscript_placeholder": "sub",
        "superscript": "Sobrescrito",
        "superscript_placeholder": "sup",
        "highlight": "Marcação",
        "highlight_placeholder": "marcado",
        "keyboard": "Teclado",
        "keyboard_placeholder": "Ctrl+C",
        "abbreviation": "Abreviação",
        "prompt_abbreviation_meaning": "Qual o significado da abreviação?",
        "abbreviation_placeholder": "HTML",
        "text_color": "Cor do Texto",
        "colored_text_placeholder": "texto colorido",
        "background_color": "Cor de Fundo",
        "colored_background_placeholder": "fundo colorido",
        "details": "Seção Recolhível",
        "details_summary_placeholder": "Resumo ou Título",
        "details_content_placeholder": "Conteúdo a ser ocultado...",
        "center": "Centralizar",
        "center_placeholder": "texto centralizado",
        "notFound": "Código não encontrado!",
        "scriptIdNotFound": "Não foi possível identificar o ID do script.",
        "downloading": "Baixando...",
        "downloadError": "Ocorreu um erro ao baixar o script.",
        "downloadTimeout": "O tempo para baixar o script esgotou.",
        "info_tooltip": "Atalhos",
        "info_shortcuts_title": "Atalhos do Teclado",
        "info_header_shortcut": "Atalho",
        "info_header_action": "Ação",
        "info_shortcut_tab": "Insere um espaço de tabulação.",
        "info_shortcut_shift_enter": "Insere uma quebra de linha <br>.",
        "close": "Fechar"
    },
    "es": {
        "langName": "Español",
        "languageSettings": "🌐 Idioma",
        "close": "Cerrar",
        "confirm": "Confirmar",
        "cancel": "Cancelar",
        "download": "Descargar",
        "compatible_with": "Compatible con",
        "force_update": "🔄️ Forzar actualización",
        "force_update_alert": "La caché se limpió. La página se recargará para obtener los datos actualizados.",
        "titles": "Títulos",
        "title_placeholder": "Título",
        "bold": "Negrita",
        "bold_placeholder": "texto en negrita",
        "italic": "Cursiva",
        "italic_placeholder": "texto en cursiva",
        "underline": "Subrayado",
        "underline_placeholder": "texto subrayado",
        "strikethrough": "Tachado",
        "strikethrough_placeholder": "texto tachado",
        "unordered_list": "Lista sin ordenar",
        "ordered_list": "Lista ordenada",
        "list_item_placeholder": "Elemento",
        "quote": "Cita",
        "quote_placeholder": "Cita",
        "inline_code": "Código en línea",
        "inline_code_placeholder": "código",
        "code_block": "Bloque de código",
        "code_block_placeholder": "código aquí",
        "horizontal_line": "Línea horizontal",
        "horizontal_line_style": "Estilo de Línea Horizontal",
        "prompt_hr_size": "Tamaño (px)",
        "prompt_hr_color": "Color (HEX)",
        "link": "Enlace",
        "prompt_insert_url": "Introduzca la URL:",
        "link_text_placeholder": "texto del enlace",
        "image": "Imagen",
        "prompt_insert_image_url": "Introduzca la URL de la imagen (https):",
        "prompt_image_title": "Título de la imagen (opcional):",
        "image_title_placeholder": "ej: Mi hermosa imagen",
        "prompt_image_width": "Ancho (opcional):",
        "prompt_image_height": "Alto (opcional):",
        "video": "Video (YouTube/Bilibili)",
        "prompt_insert_video_url": "Introduzca la URL del video (YouTube o Bilibili):",
        "prompt_video_width": "Ancho (opcional):",
        "prompt_video_height": "Alto (opcional):",
        "alert_invalid_video_url": "URL de video no válida o no compatible.",
        "table": "Tabla",
        "prompt_columns": "Número de columnas:",
        "prompt_rows": "Número de filas:",
        "table_header_placeholder": "Encabezado",
        "table_cell_placeholder": "Celda",
        "subscript": "Subíndice",
        "subscript_placeholder": "sub",
        "superscript": "Superíndice",
        "superscript_placeholder": "sup",
        "highlight": "Resaltado",
        "highlight_placeholder": "resaltado",
        "keyboard": "Teclado",
        "keyboard_placeholder": "Ctrl+C",
        "abbreviation": "Abreviatura",
        "prompt_abbreviation_meaning": "¿Qué significa la abreviação?",
        "abbreviation_placeholder": "HTML",
        "text_color": "Color del texto",
        "colored_text_placeholder": "texto coloreado",
        "background_color": "Color de fondo",
        "colored_background_placeholder": "fondo coloreado",
        "details": "Sección Plegable",
        "details_summary_placeholder": "Resumen o Título",
        "details_content_placeholder": "Contenido a ocultar...",
        "center": "Centrar",
        "center_placeholder": "texto centrado",
        "notFound": "¡Código no encontrado!",
        "scriptIdNotFound": "No se pudo identificar el ID del script.",
        "downloading": "Descargando...",
        "downloadError": "Ocurrió un error al descargar el script.",
        "downloadTimeout": "Se agotó el tiempo de espera para la descarga del script.",
        "info_tooltip": "Atajos",
        "info_shortcuts_title": "Atajos de Teclado",
        "info_header_shortcut": "Atajo",
        "info_header_action": "Acción",
        "info_shortcut_tab": "Inserta un espacio de tabulación.",
        "info_shortcut_shift_enter": "Inserta un salto de línea <br>.",
        "close": "Cerrar"
    },
    "zh-CN": {
        "langName": "简体中文",
        "languageSettings": "🌐 语言",
        "close": "关闭",
        "confirm": "确认",
        "cancel": "取消",
        "download": "下载",
        "compatible_with": "兼容",
        "force_update": "🔄️ 强制更新",
        "force_update_alert": "缓存已清除。页面将重新加载以获取最新数据。",
        "titles": "标题",
        "title_placeholder": "标题",
        "bold": "粗体",
        "bold_placeholder": "粗体文本",
        "italic": "斜体",
        "italic_placeholder": "斜体文本",
        "underline": "下划线",
        "underline_placeholder": "下划线文本",
        "strikethrough": "删除线",
        "strikethrough_placeholder": "删除线文本",
        "unordered_list": "无序列表",
        "ordered_list": "有序列表",
        "list_item_placeholder": "项目",
        "quote": "引用",
        "quote_placeholder": "引用",
        "inline_code": "行内代码",
        "inline_code_placeholder": "代码",
        "code_block": "代码块",
        "code_block_placeholder": "在此处编写代码",
        "horizontal_line": "水平线",
        "horizontal_line_style": "水平线样式",
        "prompt_hr_size": "大小 (px)",
        "prompt_hr_color": "颜色 (HEX)",
        "link": "链接",
        "prompt_insert_url": "请输入网址:",
        "link_text_placeholder": "链接文本",
        "image": "图片",
        "prompt_insert_image_url": "请输入图片网址 (https):",
        "prompt_image_title": "图片标题(可选):",
        "image_title_placeholder": "例如:我美丽的图片",
        "prompt_image_width": "宽度(可选):",
        "prompt_image_height": "高度(可选):",
        "video": "视频 (YouTube/Bilibili)",
        "prompt_insert_video_url": "请输入视频网址 (YouTube or Bilibili):",
        "prompt_video_width": "宽度(可选):",
        "prompt_video_height": "高度(可选):",
        "alert_invalid_video_url": "无效或不支持的视频网址。",
        "table": "表格",
        "prompt_columns": "列数:",
        "prompt_rows": "行数:",
        "table_header_placeholder": "标题",
        "table_cell_placeholder": "单元格",
        "subscript": "下标",
        "subscript_placeholder": "下标",
        "superscript": "上标",
        "superscript_placeholder": "上标",
        "highlight": "标记",
        "highlight_placeholder": "标记",
        "keyboard": "键盘",
        "keyboard_placeholder": "Ctrl+C",
        "abbreviation": "缩写",
        "prompt_abbreviation_meaning": "缩写的含义是什么?",
        "abbreviation_placeholder": "HTML",
        "text_color": "文字颜色",
        "colored_text_placeholder": "彩色文本",
        "background_color": "背景颜色",
        "colored_background_placeholder": "彩色背景",
        "details": "可折叠部分",
        "details_summary_placeholder": "摘要或标题",
        "details_content_placeholder": "要隐藏的内容...",
        "center": "居中",
        "center_placeholder": "居中文字",
        "notFound": "未找到代码!",
        "scriptIdNotFound": "无法识别脚本 ID。",
        "downloading": "下载中...",
        "downloadError": "下载脚本时发生错误。",
        "downloadTimeout": "脚本下载超时。",
        "info_tooltip": "快捷方式",
        "info_shortcuts_title": "键盘快捷键",
        "info_header_shortcut": "快捷键",
        "info_header_action": "功能",
        "info_shortcut_tab": "插入一个制表符。",
        "info_shortcut_shift_enter": "插入一个换行符 <br>。",
        "close": "关闭"
    }
    };

    const translations = allTranslations;
    const icons = JSON.parse(GM_getResourceText("iconsJSON"));
    const myCss = GM_getResourceText("customCSS");

    GM_addStyle(myCss);

    function capitalizeCompatItem(item) {
        return item.replace(/\b\w/g, char => char.toUpperCase());
    }

    let currentLang = 'en';
    let languageModal = null;
    const LANG_STORAGE_KEY = 'UserScriptLang';

    function getTranslation(key) {
        return translations[currentLang] ?.[key] || translations.en[key];
    }

    async function determineLanguage() {
        const savedLang = await GM_getValue(LANG_STORAGE_KEY);
        if (savedLang && translations[savedLang]) {
            currentLang = savedLang;
            return;
        }
        const browserLang = (navigator.language || navigator.userLanguage).toLowerCase();
        if (browserLang.startsWith('pt')) currentLang = 'pt-BR';
        else if (browserLang.startsWith('es')) currentLang = 'es';
        else if (browserLang.startsWith('zh')) currentLang = 'zh-CN';
        else currentLang = 'en';
    }

    function registerLanguageMenu() {
        GM_registerMenuCommand(getTranslation('languageSettings'), () => {
            showModal(languageModal);
        });
    }

    function registerForceUpdateMenu() {
        GM_registerMenuCommand(getTranslation('force_update'), forceUpdate);
    }

    function showModal(modal) {
        if (!modal) return;
        modal.style.display = 'flex';
        setTimeout(() => {
            const box = modal.querySelector('.lang-modal-box');
            box.style.opacity = '1';
            box.style.transform = 'scale(1)';
        }, 10);
    }

    function hideModal(modal) {
        if (!modal) return;
        const box = modal.querySelector('.lang-modal-box');
        box.style.opacity = '0';
        box.style.transform = 'scale(0.95)';
        setTimeout(() => {
            modal.style.display = 'none';
        }, 200);
    }

    function createLanguageModal() {
        const overlay = document.createElement('div');
        overlay.className = 'lang-modal-overlay';
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                hideModal(overlay);
            }
        });
        const box = document.createElement('div');
        box.className = 'lang-modal-box';
        const buttonsContainer = document.createElement('div');
        buttonsContainer.className = 'lang-modal-buttons';
        Object.keys(translations).forEach(langKey => {
            const btn = document.createElement('button');
            btn.textContent = translations[langKey].langName;
            btn.onclick = async () => {
                await GM_setValue(LANG_STORAGE_KEY, langKey);
                window.location.reload();
            };
            buttonsContainer.appendChild(btn);
        });
        box.appendChild(buttonsContainer);
        overlay.appendChild(box);
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

        function applyTheme(isDark) {
            box.classList.toggle('dark-theme', isDark);
            box.classList.toggle('light-theme', !isDark);
        }
        applyTheme(mediaQuery.matches);
        mediaQuery.addEventListener('change', e => applyTheme(e.matches));
        return overlay;
    }

    async function forceUpdate() {
        alert(getTranslation('force_update_alert'));
        await GM_deleteValue(CACHE_KEY);
        window.location.reload();
    }

    // ================
    // #region ESTILIZAR
    // ================

    function isScriptPage() {
        const path = window.location.pathname;
        return /^\/([a-z]{2}(-[A-Z]{2})?\/)?scripts\/\d+-[^/]+$/.test(path);
    }

    function addAdditionalInfoSeparator() {
        const additionalInfo = document.getElementById('additional-info');
        if (additionalInfo && !additionalInfo.previousElementSibling?.matches('hr.bgs-info-separator')) {
            const hr = document.createElement('hr');
            hr.className = 'bgs-info-separator';
            additionalInfo.before(hr);
        }
    }

    function highlightScriptDescription() {
        const descriptionElements = document.querySelectorAll('#script-description, .script-description.description');
        descriptionElements.forEach(element => {
            const scriptLink = element.closest('article, li')?.querySelector('a.script-link');
            const path = scriptLink ? normalizeScriptPath(new URL(scriptLink.href).pathname) : normalizeScriptPath(window.location.pathname);
            if (element && element.parentElement.tagName !== 'BLOCKQUOTE') {
                const blockquoteWrapper = document.createElement('blockquote');
                blockquoteWrapper.className = 'script-description-blockquote';
                if (path) {
                    blockquoteWrapper.dataset.bgfPath = path;
                }
                element.parentNode.insertBefore(blockquoteWrapper, element);
                blockquoteWrapper.appendChild(element);
            }
        });
    }

    function makeDiscussionClickable() {
        document.querySelectorAll('.discussion-list-container').forEach(container => {
            container.removeEventListener('click', handleDiscussionClick);
            container.addEventListener('click', handleDiscussionClick);
        });
    }

    function handleDiscussionClick(e) {
        if (e.target.tagName === 'A' ||
            e.target.closest('a') ||
            e.target.closest('.user-link') ||
            e.target.closest('.badge-author') ||
            e.target.closest('.rating-icon')) {
            return;
        }
        const discussionLink = this.querySelector('.discussion-title');
        if (discussionLink && discussionLink.href) {
            window.location.href = discussionLink.href;
        }
    }

    function applySyntaxHighlighting() {
        document.querySelectorAll('pre code').forEach(block => {
            if (block.dataset.highlighted === 'true') { return; }
            const code = block.textContent;
            block.innerHTML = highlight(code);
            block.dataset.highlighted = 'true';
        });
    }

    function escapeHtml(str) {
        return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    }

    function highlight(code) {
        const keywords = new Set(['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'of', 'in', 'async', 'await', 'try', 'catch', 'new', 'import', 'export', 'from', 'class', 'extends', 'super', 'true', 'false', 'null', 'undefined', 'document', 'window']);
        const tokens = [];
        let cursor = 0;

        const tokenDefinitions = [
            { type: 'url',              regex: /^(https?:\/\/[^\s"'`<>]+)/ },
            { type: 'comment-special',  regex: /^(\/\/[^\r\n]*)/ },
            { type: 'comment',          regex: /^(\/\*[\s\S]*?\*\/|<!--[\s\S]*?-->)/ },
            { type: 'string',           regex: /^(`(?:\\.|[^`])*`|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')/ },
            { type: 'tag-punctuation',  regex: /^(&lt;\/?|\/&gt;|&gt;)/ },
            { type: 'tag-name',         regex: /^([\w-]+)/, context: (t) => { const l=t[t.length-1]; return l&&l.type==='tag-punctuation'&&l.content.startsWith('&lt;') }},
            { type: 'attribute',        regex: /^([\w-]+)/, context: (t) => { for(let i=t.length-1;i>=0;i--){const n=t[i];if(n.type==='tag-punctuation'&&n.content.includes('&gt;'))return!1;if(n.type==='tag-name')return!0;if(n.type==='whitespace')continue}return!1 }},
            { type: 'regex',            regex: /^(\/(?!\*)(?:[^\r\n/\\]|\\.)+\/[gimyus]*)/ },
            { type: 'number',           regex: /^\b-?(\d+(\.\d+)?)\b/ },
            { type: 'keyword',          regex: new RegExp(`^\\b(${Array.from(keywords).join('|')})\\b`) },
            { type: 'function',         regex: /^([a-zA-Z_][\w_]*)(?=\s*\()/ },
            { type: 'property',         regex: /^\.([a-zA-Z_][\w_]*)/ },
            { type: 'operator',         regex: /^(==?=?|!=?=?|=>|[+\-*/%&|^<>]=?|\?|:|=)/ },
            { type: 'punctuation',      regex: /^([,;(){}[\]])/ },
            { type: 'whitespace',       regex: /^\s+/ },
            { type: 'unknown',          regex: /^./ },
        ];
        let processedCode = escapeHtml(code);
        while (cursor < processedCode.length) {
            let matched = false;
            for (const def of tokenDefinitions) {
                if (def.context && !def.context(tokens)) { continue; }
                const match = def.regex.exec(processedCode.slice(cursor));
                if (match) {
                    const content = match[0];
                    if (def.type === 'function' && keywords.has(content)) { continue; }
                    tokens.push({ type: def.type, content });
                    cursor += content.length;
                    matched = true;
                    break;
                }
            }
            if (!matched) {
                 tokens.push({ type: 'unknown', content: processedCode[cursor] });
                 cursor++;
            }
        }
        for (let i = 0; i < tokens.length; i++) {
            if (tokens[i].type === 'string') {
                let nextToken = null;
                for(let j=i+1;j<tokens.length;j++){if(tokens[j].type!=='whitespace'){nextToken=tokens[j];break}}
                if (nextToken && nextToken.content === ':') { tokens[i].type = 'json-key'; }
            }
        }
        return tokens.map(token => {
            if (['whitespace', 'unknown', 'url'].includes(token.type)) return token.content;
            if (token.type === 'property') return `<span class="sh-punctuation">.</span><span class="sh-property">${token.content.slice(1)}</span>`;
            return `<span class="sh-${token.type}">${token.content}</span>`;
        }).join('');
    }

    // ================
    // #region ÍCONES
    // ================

    let iconCache;
    const processedKeys = new Set();

    async function saveCache() {
        await GM_setValue(CACHE_KEY, iconCache);
    }

    function normalizeScriptPath(pathname) {
        let withoutLocale = pathname.replace(/^\/[a-z]{2}(?:-[A-Z]{2})?\//, '/');
        const match = withoutLocale.match(/^\/scripts\/\d+-.+?(?=\/|$)/);
        return match ? match[0] : null;
    }

    function extractScriptIdFromNormalizedPath(normalized) {
        const match = normalized.match(/\/scripts\/(\d+)-/);
        return match ? match[1] : null;
    }

    function createIconElement(src, isHeader = false) {
        const img = document.createElement('img');
        img.src = src;
        img.alt = '';
        if (isHeader) {
            img.style.cssText = `
                width: 80px;
                height: 80px;
                margin-right: 10px;
                vertical-align: middle;
                border-radius: 4px;
                object-fit: contain;
                pointer-events: none;
            `;
        } else {
            img.style.cssText = `
                width: 40px;
                height: 40px;
                margin-right: 8px;
                vertical-align: middle;
                border-radius: 3px;
                object-fit: contain;
                pointer-events: none;
            `;
        }
        img.loading = 'lazy';
        return img;
    }

    function extractMetadataFromContent(content) {
        if (typeof content !== 'string') return {};
        const metadata = {};
        const lines = content.split('\n');
        const supportedTags = new Set([
            '@icon', '@bgf-colorLT', '@bgf-colorDT', '@bgf-compatible',
            '@bgf-copyright', '@bgf-social'
        ]);
        for (const line of lines) {
            const trimmedLine = line.trim();
            if (trimmedLine.startsWith('// ==/UserScript==')) break;
            if (!trimmedLine.startsWith('// @')) continue;
            const match = trimmedLine.match(/\/\/\s*(@[a-zA-Z0-9-]+)\s+(.+)/);
            if (!match) continue;
            const key = match[1];
            let value = match[2].trim();
            if (supportedTags.has(key) && !metadata.hasOwnProperty(key)) {
                if (key === '@bgf-colorLT' || key === '@bgf-colorDT') {
                    const colorRegex = /(#[0-9a-fA-F]{3,8}|(?:rgba?|hsla?)\s*\([^)]+\))/;
                    const colorMatch = value.match(colorRegex);
                    if (colorMatch) {
                        value = colorMatch[0];
                    } else {
                        value = value.split(',')[0].trim();
                    }
                }
                metadata[key] = value;
            }
        }
        return metadata;
    }

    function isValidIconUrl(url) {
        return url && (url.startsWith('http') || url.startsWith(''));
    }

    async function processScript(normalizedPath, targetElement, isHeader = false) {
        if (processedKeys.has(normalizedPath) && isHeader) {
            applyBfgFeatures(iconCache[normalizedPath]);
        }
        if (processedKeys.has(normalizedPath) && !isHeader) {
            const cached = iconCache[normalizedPath];
            if (cached && isValidIconUrl(cached.iconUrl)) {
                targetElement.prepend(createIconElement(cached.iconUrl, isHeader));
            }
            return;
        }
        processedKeys.add(normalizedPath);
        const cached = iconCache[normalizedPath];
        const now = Date.now();
        const applyColorToBlockquote = (metadata) => {
            const blockquotes = document.querySelectorAll(`blockquote.script-description-blockquote[data-bgf-path="${normalizedPath}"]`);
            if (blockquotes.length === 0) return;

            const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
            const colorToApply = isDarkMode ? metadata.bgfColorDT : metadata.bgfColorLT;

            blockquotes.forEach(bq => {
                if (colorToApply) {
                    bq.style.setProperty('border-left-color', colorToApply, 'important');
                } else {
                    bq.style.removeProperty('border-left-color');
                }
            });
        };
        if (cached && now - cached.ts < 30 * 24 * 60 * 60 * 1000) {
            if (isValidIconUrl(cached.iconUrl)) {
                targetElement.prepend(createIconElement(cached.iconUrl, isHeader));
            }
            applyColorToBlockquote(cached);
            if (isHeader) {
                applyBfgFeatures(cached);
            }
            return;
        }
        const scriptId = extractScriptIdFromNormalizedPath(normalizedPath);
        if (!scriptId) {
            iconCache[normalizedPath] = { ts: now };
            await saveCache();
            return;
        }
        const scriptUrl = `https://update.greasyfork.org/scripts/${scriptId}.js`;
        GM_xmlhttpRequest({
            method: 'GET',
            url: scriptUrl,
            timeout: 6000,
            onload: async function (res) {
                if (typeof res.responseText !== 'string') {
                    iconCache[normalizedPath] = { ts: now };
                    await saveCache();
                    return;
                }
                const rawMetadata = extractMetadataFromContent(res.responseText);
                const metadata = {
                    iconUrl:        rawMetadata['@icon']            || null,
                    bgfColorLT:     rawMetadata['@bgf-colorLT']     || null,
                    bgfColorDT:     rawMetadata['@bgf-colorDT']     || null,
                    bgfCompatible:  rawMetadata['@bgf-compatible']  || null,
                    bgfCopyright:   rawMetadata['@bgf-copyright']   || null,
                    bgfSocial:      rawMetadata['@bgf-social']      || null,
                    ts:             now
                };
                iconCache[normalizedPath] = metadata;
                await saveCache();
                if (isValidIconUrl(metadata.iconUrl)) {
                    targetElement.prepend(createIconElement(metadata.iconUrl, isHeader));
                }
                applyColorToBlockquote(metadata);
                if (isHeader) {
                    applyBfgFeatures(metadata);
                }
            },
            onerror: async function () {
                iconCache[normalizedPath] = { ts: now };
                await saveCache();
            }
        });
    }

    function handleScriptLink(linkEl) {
        if (linkEl._handled) return;
        linkEl._handled = true;
        const href = linkEl.getAttribute('href');
        if (!href || !href.startsWith('/')) return;
        try {
            const url = new URL(href, window.location.origin);
            const normalized = normalizeScriptPath(url.pathname);
            if (!normalized) return;
            setTimeout(() => processScript(normalized, linkEl, false), 0);
        } catch (e) {}
    }

    function handleMainHeaderH2() {
        const headers = document.querySelectorAll('header');
        for (const header of headers) {
            const h2 = header.querySelector('h2');
            const desc = header.querySelector('p.script-description');
            if (h2 && desc && !h2._handled) {
                h2._handled = true;
                const normalized = normalizeScriptPath(window.location.pathname);
                if (!normalized) return;
                setTimeout(() => processScript(normalized, h2, true), 0);
                break;
            }
        }
    }

    function processIconElements() {
        document.querySelectorAll('a.script-link:not([data-icon-processed])')
            .forEach(el => {
                el.setAttribute('data-icon-processed', '1');
                handleScriptLink(el);
            });
        handleMainHeaderH2();
    }

    // ================
    // #region METADADOS
    // ================

    function applyBfgFeatures(metadata) {
        if (!metadata) return;
        applyBfgCompatibility(metadata.bgfCompatible);
        applyBfgCopyright(metadata.bgfCopyright);
        applyBfgSocial(metadata.bgfSocial);
    }

    function applyBfgCompatibility(compatValue) {
        if (!compatValue) return;
        const compatDd = document.querySelector('dd.script-show-compatibility');
        if (!compatDd) {
            return;
        }
        let compatContainer = compatDd.querySelector('span');
        if (!compatContainer) {
            compatContainer = document.createElement('span');
            compatDd.innerHTML = '';
            compatDd.appendChild(compatContainer);
        }
        const compatItems = compatValue.split(',').map(item => item.trim().toLowerCase());
        compatItems.forEach(item => {
            if (!icons[item] || compatContainer.querySelector(`.bgf-compat-${item}`)) {
                return;
            }
            const img = document.createElement('img');
            img.className = `browser-compatible bgf-compat-${item}`;
            const displayName = capitalizeCompatItem(item);
            img.alt = `${getTranslation('compatible_with')} ${displayName}`;
            img.title = `${getTranslation('compatible_with')} ${displayName}`;
            img.style.marginLeft = '1px';
            img.src = `data:image/svg+xml;utf8,${encodeURIComponent(icons[item])}`;
            compatContainer.appendChild(img);
        });
    }

    function reapplyAllBlockquoteColors() {
        const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const allBlockquotes = document.querySelectorAll('blockquote.script-description-blockquote[data-bgf-path]');
        allBlockquotes.forEach(bq => {
            const path = bq.dataset.bgfPath;
            if (!path || !iconCache[path]) return;
            const metadata = iconCache[path];
            const colorToApply = isDarkMode ? metadata.bgfColorDT : metadata.bgfColorLT;
            if (colorToApply) {
                bq.style.setProperty('border-left-color', colorToApply, 'important');
            } else {
                bq.style.removeProperty('border-left-color');
            }
        });
    }

    function setupThemeChangeListener() {
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
        mediaQuery.addEventListener('change', reapplyAllBlockquoteColors);
    }

    function applyBfgCopyright(copyrightValue) {
        if (!copyrightValue || document.querySelector('.script-show-copyright')) return;
        const copyrightRegex = /\[(.{1,50})\]\((https:\/\/gist\.github\.com\/[^)]+)\)/;
        const match = copyrightValue.match(copyrightRegex);
        if (!match) return;
        const licenseDd = document.querySelector('dd.script-show-license');
        if (!licenseDd) return;
        const text = match[1];
        const url = match[2];
        const copyrightDt = document.createElement('dt');
        copyrightDt.className = 'script-show-copyright';
        copyrightDt.innerHTML = '<span>Copyright</span>';
        const copyrightDd = document.createElement('dd');
        copyrightDd.className = 'script-show-copyright';
        copyrightDd.style.alignSelf = 'center'; 
        const link = document.createElement('a');
        link.href = url;
        link.textContent = text;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        const span = document.createElement('span');
        span.appendChild(link);
        copyrightDd.appendChild(span);
        licenseDd.after(copyrightDt, copyrightDd);
    }

    function applyBfgSocial(socialValue) {
        if (!socialValue || document.querySelector('.script-show-social')) return;
        const authorDd = document.querySelector('dd.script-show-author');
        if (!authorDd) return;
        const socialDomainMap = {
            'instagram.com':    { icon: icons.instagram, name: 'Instagram' },
            'facebook.com':     { icon: icons.facebook, name: 'Facebook' },
            'x.com':            { icon: icons.x, name: 'X / Twitter' },
            'youtube.com':      { icon: icons.youtube, name: 'YouTube' },
            'bilibili.com':     { icon: icons.bilibili, name: 'Bilibili' },
            'tiktok.com':       { icon: icons.tiktok, name: 'TikTok' },
            'douyin.com':       { icon: icons.tiktok, name: 'Douyin' },
            'github.com':       { icon: icons.github, name: 'GitHub' },
            'linkedin.com':     { icon: icons.linkedin, name: 'LinkedIn' },
        };
        const urls = socialValue.split(',').map(url => url.trim());
        const validLinks = [];
        let tiktokFamilyProcessed = false;
        urls.forEach(url => {
            try {
                const domain = new URL(url).hostname.replace('www.', '');
                if (socialDomainMap[domain]) {
                    if (domain === 'tiktok.com' || domain === 'douyin.com') {
                        if (tiktokFamilyProcessed) return;
                        tiktokFamilyProcessed = true;
                    }
                    validLinks.push({ url, ...socialDomainMap[domain] });
                }
            } catch (e) {}
        });
        if (validLinks.length === 0) return;
        const socialDt = document.createElement('dt');
        socialDt.className = 'script-show-social';
        socialDt.innerHTML = '<span>Social</span>';
        const socialDd = document.createElement('dd');
        socialDd.className = 'script-show-social';
        socialDd.style.cssText = 'display: flex; gap: 8px; align-items: center; align-self: center; z-index: 10;';
        validLinks.forEach(linkInfo => {
            const link = document.createElement('a');
            link.href = linkInfo.url;
            link.title = linkInfo.name;
            link.target = '_blank';
            link.rel = 'noopener noreferrer';
            link.innerHTML = linkInfo.icon;
            const svg = link.querySelector('svg');
            if (svg) {
                svg.style.width = '20px';
                svg.style.height = '20px';
                svg.style.verticalAlign = 'middle';
            }
            socialDd.appendChild(link);
        });
        authorDd.after(socialDt, socialDd);
    }

    // ================
    // #region EDITOR HTML
    // ================

    function insertText(textarea, prefix, suffix = '', placeholder = '') {
        const start = textarea.selectionStart;
        const end = textarea.selectionEnd;
        const selected = textarea.value.substring(start, end);
        const text = selected || placeholder;
        textarea.setRangeText(prefix + text + suffix, start, end, selected ? 'end' : 'select');
        textarea.focus();
    }

    function createToolbarButton(def) {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'txt-editor-toolbar-button';
        btn.dataset.tooltip = def.title;
        btn.innerHTML = def.icon || def.label;
        btn.addEventListener('click', e => {
            e.preventDefault();
            def.action();
        });
        return btn;
    }

    function showCustomAlert(message) {
        const overlay = document.createElement('div');
        overlay.className = 'custom-prompt-overlay';
        const modal = document.createElement('div');
        modal.className = 'custom-prompt-box custom-alert-box';
        const editorContainer = document.querySelector('.txt-editor-container');
        modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
        const messageP = document.createElement('p');
        messageP.textContent = message;
        const closeBtn = document.createElement('button');
        closeBtn.textContent = getTranslation('close');
        closeBtn.className = 'custom-prompt-confirm';
        closeBtn.onclick = () => document.body.removeChild(overlay);
        modal.append(messageP, closeBtn);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
        closeBtn.focus();
    }

    function showCustomPrompt({ inputs, onConfirm }) {
        const overlay = document.createElement('div');
        overlay.className = 'custom-prompt-overlay';
        const modal = document.createElement('div');
        modal.className = 'custom-prompt-box';
        const editorContainer = document.querySelector('.txt-editor-container');
        modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
        const form = document.createElement('form');
        const inputsMap = new Map();
        inputs.forEach(config => {
            const label = document.createElement('label');
            label.textContent = config.label;
            const input = document.createElement('input');
            input.type = config.type || 'text';
            input.placeholder = config.placeholder || '';
            input.value = config.value || '';
            input.required = config.required !== false;
            if (config.type === 'number') input.min = '1';
            label.appendChild(input);
            form.appendChild(label);
            inputsMap.set(config.id, input);
        });
        const buttons = document.createElement('div');
        buttons.className = 'custom-prompt-buttons';
        const confirmBtn = document.createElement('button');
        confirmBtn.type = 'submit';
        confirmBtn.textContent = getTranslation('confirm');
        confirmBtn.className = 'custom-prompt-confirm';
        const cancelBtn = document.createElement('button');
        cancelBtn.type = 'button';
        cancelBtn.textContent = getTranslation('cancel');
        cancelBtn.className = 'custom-prompt-cancel';
        cancelBtn.onclick = () => document.body.removeChild(overlay);
        form.onsubmit = (e) => {
            e.preventDefault();
            const results = {};
            for (const [id, inputElement] of inputsMap.entries()) {
                results[id] = inputElement.value;
            }
            onConfirm(results);
            document.body.removeChild(overlay);
        };
        buttons.append(confirmBtn, cancelBtn);
        form.appendChild(buttons);
        modal.appendChild(form);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
        inputsMap.values().next().value.focus();
    }

    function showInfoModal() {
        const overlay = document.createElement('div');
        overlay.className = 'custom-prompt-overlay info-modal-overlay';
        overlay.style.display = 'flex';
        const modal = document.createElement('div');
        modal.className = 'custom-prompt-box info-modal-box';
        const editorContainer = document.querySelector('.txt-editor-container');
        modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
        modal.innerHTML = `
            <h2>${getTranslation('info_shortcuts_title')}</h2>
            <div class="info-shortcuts">
                <table>
                    <thead>
                        <tr>
                            <th>${getTranslation('info_header_shortcut')}</th>
                            <th>${getTranslation('info_header_action')}</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><code>Tab</code></td>
                            <td>${getTranslation('info_shortcut_tab')}</td>
                        </tr>
                        <tr>
                            <td><code>Shift + Enter</code></td>
                            <td>${getTranslation('info_shortcut_shift_enter')}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div class="custom-prompt-buttons">
                <button class="custom-prompt-cancel">${getTranslation('close')}</button>
            </div>
        `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
        overlay.querySelector('.custom-prompt-cancel').onclick = () => document.body.removeChild(overlay);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                document.body.removeChild(overlay);
            }
        });
    }

    function createTextStyleEditor(textarea) {
        if (textarea.dataset.editorApplied) return;
        textarea.dataset.editorApplied = 'true';
        textarea.addEventListener('keydown', function(e) {
            if (e.key === 'Tab') {
                e.preventDefault();
                const start = this.selectionStart;
                const end = this.selectionEnd;
                this.setRangeText('   ', start, end, 'end');
            }
            if (e.shiftKey && e.key === 'Enter') {
                e.preventDefault();
                const start = this.selectionStart;
                const end = this.selectionEnd;
                this.setRangeText('\n<br>\n', start, end, 'end');
            }
        });
        const container = document.createElement('div');
        container.className = 'txt-editor-container';
        const toolbar = document.createElement('div');
        toolbar.className = 'txt-editor-toolbar';
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

        function applyTheme(isDark) {
            container.classList.toggle('dark-theme', isDark);
            container.classList.toggle('light-theme', !isDark);
        }
        applyTheme(mediaQuery.matches);
        mediaQuery.addEventListener('change', e => applyTheme(e.matches));
        const tools = [
            { type: 'select', title: getTranslation('titles'), options: { 'H1': '1', 'H2': '2', 'H3': '3', 'H4': '4', 'H5': '5', 'H6': '6' }, action: (val) => insertText(textarea, `<h${val}>`, `</h${val}>`, getTranslation('title_placeholder')) },
            { type: 'divider' },
            { title: getTranslation('bold'),            icon:   icons.bold,             action: () => insertText(textarea, '<strong>', '</strong>',         getTranslation('bold_placeholder')) },
            { title: getTranslation('italic'),          icon:   icons.italic,           action: () => insertText(textarea, '<em>', '</em>',                 getTranslation('italic_placeholder')) },
            { title: getTranslation('underline'),       icon:   icons.underline,        action: () => insertText(textarea, '<u>', '</u>',                   getTranslation('underline_placeholder')) },
            { title: getTranslation('strikethrough'),   icon:   icons.strikethrough,    action: () => insertText(textarea, '<s>', '</s>',                   getTranslation('strikethrough_placeholder')) },
            { type: 'divider' },
            { title: getTranslation('unordered_list'),  icon:   icons.ul,               action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const items = selection ? selection.split('\n').map(line => `  <li>${line}</li>`).join('\n') : `  <li>${getTranslation('list_item_placeholder')}</li>`; const listHtml = `<ul>\n${items}\n</ul>`; textarea.setRangeText(listHtml, start, end, 'select'); textarea.focus(); } },
            { title: getTranslation('ordered_list'),    icon:   icons.ol,               action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const items = selection ? selection.split('\n').map(line => `  <li>${line}</li>`).join('\n') : `  <li>${getTranslation('list_item_placeholder')}</li>`; const listHtml = `<ol>\n${items}\n</ol>`; textarea.setRangeText(listHtml, start, end, 'select');textarea.focus(); } },
            { title: getTranslation('details'),         label:  icons.details,          action: () => insertText(textarea, '\n<details>\n  <summary>' +     getTranslation('details_summary_placeholder') + '</summary>\n\n  ' + getTranslation('details_content_placeholder') + '\n\n</details>\n') },
            { title: getTranslation('center'),          label:  icons.center,           action: () => insertText(textarea, '\n<center>\n', '\n</center>',   getTranslation('center_placeholder')) },
            { type: 'divider' },
            { title: getTranslation('quote'),           icon:   icons.quote,            action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const content = selection ? selection.replace(/\n/g, '<br>\n  ') : getTranslation('quote_placeholder'); const quoteHtml = `\n<blockquote>\n  ${content}\n</blockquote>\n`; textarea.setRangeText(quoteHtml, start, end, 'select'); textarea.focus(); } },
            { title: getTranslation('inline_code'),     icon:   icons.code,             action: () => insertText(textarea, '<code>', '</code>',             getTranslation('inline_code_placeholder')) },
            { title: getTranslation('code_block'),      label:  icons.code_block,       action: () => insertText(textarea, '<pre><code>', '</code></pre>',  getTranslation('code_block_placeholder')) },
            { title: getTranslation('horizontal_line'), icon:   icons.hr,               action: () => insertText(textarea, '\n<hr>\n') },
            { type: 'divider' },
            { title: getTranslation('link'),            icon:   icons.link,             action: () => showCustomPrompt({ inputs: [{ id: 'url', label:       getTranslation('prompt_insert_url'), placeholder: 'https://', type: 'url' }], onConfirm: ({ url }) => { if (url) insertText(textarea, `<a href="${url}">`, '</a>', getTranslation('link_text_placeholder')); } }) },
            { title: getTranslation('image'),           icon:   icons.image,            action: () => showCustomPrompt({ inputs: [ { id: 'src', label:      getTranslation('prompt_insert_image_url'), placeholder: 'https://', type: 'url' }, { id: 'title', label: getTranslation('prompt_image_title'), placeholder: getTranslation('image_title_placeholder'), required: false }, { id: 'width', label: getTranslation('prompt_image_width'), placeholder: '500', type: 'number', required: false }, { id: 'height', label: getTranslation('prompt_image_height'), placeholder: '500', type: 'number', required: false } ], onConfirm: ({ src, title, width, height }) => { if (src) { const titleAttr = title ? ` title="${title}"` : ''; const widthAttr = width ? ` width="${width}"` : ''; const heightAttr = height ? ` height="${height}"` : ''; insertText(textarea, `<img src="${src}"${titleAttr}${widthAttr}${heightAttr}>`); } } }) },
            { title: getTranslation('table'),           icon:   icons.table,            action: () => showCustomPrompt({ inputs: [ { id: 'cols', label:     getTranslation('prompt_columns'), type: 'number', value: '3' }, { id: 'rows', label: getTranslation('prompt_rows'), type: 'number', value: '2' } ], onConfirm: ({ cols, rows }) => { const numCols = parseInt(cols, 10) || 3; const numRows = parseInt(rows, 10) || 2; let table = '<table>\n  <thead>\n    <tr>\n'; table += '      ' + Array(numCols).fill(`<th>${getTranslation('table_header_placeholder')}</th>`).join('\n      ') + '\n    </tr>\n  </thead>\n  <tbody>\n'; for (let i = 0; i < numRows; i++) { table += '    <tr>\n'; table += '      ' + Array(numCols).fill(`<td>${getTranslation('table_cell_placeholder')}</td>`).join('\n      ') + '\n    </tr>\n'; } table += '  </tbody>\n</table>'; insertText(textarea, `\n${table}\n`); } }) },
            { title: getTranslation('video'),           icon:   icons.video,            action: () => showCustomPrompt({ inputs: [ { id: 'url', label:      getTranslation('prompt_insert_video_url'), placeholder: 'YouTube or Bilibili URL', type: 'url' }, { id: 'width', label: getTranslation('prompt_video_width'), placeholder: '560', type: 'number', required: false }, { id: 'height', label: getTranslation('prompt_video_height'), placeholder: '315', type: 'number', required: false } ], onConfirm: ({ url, width, height }) => { if (!url) return; let src = ''; try { if (url.includes('youtube.com/watch?v=')) src = `https://www.youtube.com/embed/${new URL(url).searchParams.get('v')}`; else if (url.includes('youtu.be/')) src = `https://www.youtube.com/embed/${new URL(url).pathname.substring(1)}`; else if (url.includes('bilibili.com/video/')) src = `https://player.bilibili.com/player.html?bvid=${new URL(url).pathname.split('/')[2]}`; } catch { src = ''; } if (src) { const widthAttr = width ? ` width="${width}"` : ''; const heightAttr = height ? ` height="${height}"` : ''; insertText(textarea, `\n<iframe src="${src}"${widthAttr}${heightAttr} allowfullscreen></iframe>\n`); } else { showCustomAlert(getTranslation('alert_invalid_video_url')); } } }) },
            { type: 'divider' },
            { title: getTranslation('subscript'),       label:  icons.subscript,        action: () => insertText(textarea, '<sub>', '</sub>',               getTranslation('subscript_placeholder')) },
            { title: getTranslation('superscript'),     label:  icons.superscript,      action: () => insertText(textarea, '<sup>', '</sup>',               getTranslation('superscript_placeholder')) },
            { title: getTranslation('highlight'),       label:  icons.highlight,        action: () => insertText(textarea, '<mark>', '</mark>',             getTranslation('highlight_placeholder')) },
            { title: getTranslation('keyboard'),        label:  icons.keyboard,         action: () => insertText(textarea, '<kbd>', '</kbd>',               getTranslation('keyboard_placeholder')) },
            { title: getTranslation('abbreviation'),    label:  icons.abbreviation,     action: () => showCustomPrompt({ inputs: [{ id: 'title', label:     getTranslation('prompt_abbreviation_meaning') }], onConfirm: ({ title }) => { if (title) insertText(textarea, `<abbr title="${title}">`, `</abbr>`, getTranslation('abbreviation_placeholder')); } }) },
            { type: 'color-picker' }
        ];
        tools.forEach(tool => {
            if (tool.type === 'divider') {
                const div = document.createElement('div');
                div.className = 'txt-editor-toolbar-divider';
                toolbar.appendChild(div);
            } else if (tool.type === 'select') {
                const container = document.createElement('span');
                container.className = 'txt-editor-toolbar-button';
                container.dataset.tooltip = tool.title;
                container.style.position = 'relative';
                container.style.display = 'flex';
                container.style.alignItems = 'center';
                container.style.justifyContent = 'center';
                container.innerHTML = icons.h;
                const select = document.createElement('select');
                select.className = 'txt-editor-toolbar-select';
                select.style.cssText = ` -webkit-appearance: none; appearance: none; background: transparent; border: none; color: transparent; position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; `;
                const placeholderOpt = document.createElement('option');
                placeholderOpt.value = '';
                placeholderOpt.textContent = '';
                placeholderOpt.disabled = true;
                placeholderOpt.selected = true;
                placeholderOpt.style.display = 'none';
                select.appendChild(placeholderOpt);
                Object.keys(tool.options).forEach(key => {
                    const opt = document.createElement('option');
                    opt.value = tool.options[key];
                    opt.textContent = key;
                    select.appendChild(opt);
                });
                select.addEventListener('change', () => {
                    if (select.value) tool.action(select.value);
                    select.selectedIndex = 0;
                });
                container.appendChild(select);
                toolbar.appendChild(container);
            } else if (tool.type === 'color-picker') {
                const colorContainer = document.createElement('div');
                colorContainer.className = 'txt-color-picker-container';
                const input = document.createElement('input');
                input.type = 'color';
                input.className = 'txt-color-picker-input';
                input.value = "#58a6ff";
                const colorBtn = createToolbarButton({
                    title: getTranslation('text_color'),
                    label: icons.text_color,
                    action: () => insertText(textarea, `<span style="color: ${input.value};">`, '</span>', getTranslation('colored_text_placeholder'))
                });
                const bgBtn = createToolbarButton({
                    title: getTranslation('background_color'),
                    label: icons.background_color,
                    action: () => insertText(textarea, `<span style="background-color: ${input.value};">`, '</span>', getTranslation('colored_background_placeholder'))
                });
                const hrStyleBtn = createToolbarButton({
                    title: getTranslation('horizontal_line_style'),
                    label: icons.hr_style,
                    action: () => {
                        const start = textarea.selectionStart;
                        const end = textarea.selectionEnd;
                        const selectedText = textarea.value.substring(start, end).trim();
                        let currentSize = '1';
                        let currentColor = input.value;
                        const hrRegex = /<hr\s+style="border:\s*(\d+)px\s+solid\s+([^;"]+)/i;
                        const match = selectedText.match(hrRegex);
                        if (match && selectedText.startsWith('<hr')) {
                            currentSize = match[1];
                            currentColor = match[2];
                        }
                        showCustomPrompt({
                            inputs: [
                                { id: 'size',  label: getTranslation('prompt_hr_size'), type: 'number', value: currentSize },
                                { id: 'color', label: getTranslation('prompt_hr_color'), type: 'text', value: currentColor }
                            ],
                            onConfirm: ({ size, color }) => {
                                const newHr = `<hr style="border: ${size}px solid ${color};">`;
                                textarea.setRangeText(newHr, start, end, 'select');
                                textarea.focus();
                            }
                        });
                    }
                });

                colorContainer.append(input, colorBtn, bgBtn, hrStyleBtn);
                toolbar.appendChild(colorContainer);
            } else {
                toolbar.appendChild(createToolbarButton(tool));
            }
        });
        const infoButton = createToolbarButton({
            title: getTranslation('info_tooltip'),
            icon: icons.info,
            action: showInfoModal
        });
        infoButton.style.marginLeft = 'auto';
        toolbar.appendChild(infoButton);
        textarea.parentNode.insertBefore(container, textarea);
        container.append(toolbar, textarea);
    }

    function applyToAllTextareas() {
        const textareas = document.querySelectorAll('textarea:not(#script_version_code):not([data-editor-applied])');
        textareas.forEach(createTextStyleEditor);
    }

    function enableSourceEditorCheckbox() {
        const enableCheckbox = () => {
            const checkbox = document.getElementById('enable-source-editor-code');
            if (checkbox && !checkbox.checked) {
                checkbox.checked = true;
                const event = new Event('change', {
                    bubbles: true
                });
                checkbox.dispatchEvent(event);
            }
        };
        enableCheckbox();
        const observer = new MutationObserver((mutationsList, observer) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    const checkbox = document.getElementById('enable-source-editor-code');
                    if (checkbox) {
                        enableCheckbox();
                        observer.disconnect();
                        break;
                    }
                }
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    function isMarkdownPage() {
        const path = window.location.pathname;
        const markdownSegments = [
            '/new',
            '/edit',
            '/feedback',
            '/discussions'
        ];
        if (path.includes('/sets/')) {
            return false;
        }
        return markdownSegments.some(segment => path.includes(segment));
    }

    // ================
    // #region DOWNLOAD
    // ================

    function isCodePage() {
        return /^\/([a-z]{2}(-[A-Z]{2})?\/)?scripts\/\d+-.+\/code/.test(window.location.pathname);
    }

    function initializeDownloadButton() {
        const waitFor = (sel) =>
            new Promise((resolve) => {
                const el = document.querySelector(sel);
                if (el) return resolve(el);
                const obs = new MutationObserver(() => {
                    const el = document.querySelector(sel);
                    if (el) {
                        obs.disconnect();
                        resolve(el);
                    }
                });
                obs.observe(document, { childList: true, subtree: true });
            });

        waitFor('label[for="wrap-lines"]').then((label) => {
            const wrapLinesCheckbox = document.getElementById('wrap-lines');
            if (wrapLinesCheckbox) {
                wrapLinesCheckbox.checked = false;
            }
            const toolbar = label.parentElement;
            const btn = document.createElement('button');
            btn.className = 'btn';
            btn.textContent = getTranslation('download');
            btn.style.marginLeft = '12px';
            btn.style.backgroundColor = '#005200';
            btn.style.color = 'white';
            btn.style.border = 'none';
            btn.style.padding = '6px 16px';
            btn.style.borderRadius = '4px';
            btn.style.cursor = 'pointer';
            btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#1e971e');
            btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#005200');

            btn.addEventListener('click', () => {
                const normalizedPath = normalizeScriptPath(window.location.pathname);
                const scriptId = extractScriptIdFromNormalizedPath(normalizedPath);

                if (!scriptId) {
                    alert(getTranslation('scriptIdNotFound'));
                    return;
                }

                const scriptUrl = `https://update.greasyfork.org/scripts/${scriptId}.js`;

                btn.disabled = true;
                btn.textContent = getTranslation('downloading');

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: scriptUrl,
                    onload: function (res) {
                        const code = res.responseText;
                        if (!code) {
                            alert(getTranslation('notFound'));
                            return;
                        }
                        const nameMatch = code.match(/\/\/\s*@name\s+(.+)/i);
                        const fileName = nameMatch ? `${nameMatch[1].trim()}.user.js` : 'script.user.js';
                        const blob = new Blob([code], { type: 'application/javascript;charset=utf-8' });
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = fileName;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                    },
                    onerror: function (res) {
                        alert(getTranslation('downloadError'));
                    },
                    ontimeout: function () {
                        alert(getTranslation('downloadTimeout'));
                    },
                    onloadend: function () {
                        btn.disabled = false;
                        btn.textContent = getTranslation('download');
                    }
                });
            });
            toolbar.appendChild(btn);
            const spacer = document.createElement('div');
            spacer.style.height = '12px';
            toolbar.appendChild(spacer);
        });
    }

    // ================
    // #region INICIALIZAR
    // ================

    async function start() {
        iconCache = await GM_getValue(CACHE_KEY, {});
        await determineLanguage();
        languageModal = createLanguageModal();
        document.body.appendChild(languageModal);
        registerLanguageMenu();
        registerForceUpdateMenu(); 
        setupThemeChangeListener();
        if (isMarkdownPage()) {
            applyToAllTextareas();
            enableSourceEditorCheckbox();
        }
        if (isCodePage()){
            initializeDownloadButton();
        }
        processIconElements();
        highlightScriptDescription();
        if (isScriptPage()) {
            addAdditionalInfoSeparator();
        }
        makeDiscussionClickable();
        applySyntaxHighlighting();
        const observer = new MutationObserver(() => {
            processIconElements();
            highlightScriptDescription();
            if (isScriptPage()) {
                addAdditionalInfoSeparator();
            }
            if (isMarkdownPage()) {
                applyToAllTextareas();
            }
            makeDiscussionClickable();
            applySyntaxHighlighting();
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    start();
})();