Danbooru Note Formatting Helper

A formatting helper toolbar for Danbooru note editing dialogs, adding buttons to wrap highlighted text with HTML tags for easy formatting. Now with Word-like ribbon layout, advanced markup buttons, color picker, symbols palette, undo/redo, and auto-resize.

Version vom 03.11.2025. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name Danbooru Note Formatting Helper
// @namespace http://tampermonkey.net/
// @version 1.40.0
// @description A formatting helper toolbar for Danbooru note editing dialogs, adding buttons to wrap highlighted text with HTML tags for easy formatting. Now with Word-like ribbon layout, advanced markup buttons, color picker, symbols palette, undo/redo, and auto-resize.
// @author FunkyJustin
// @license MIT
// @match https://danbooru.donmai.us/*
// @grant none
// ==/UserScript==
/*
Update History:
- v1.40.0: Added "Pink Outline" template button with parametric picker (font, size, colors, offset, blur) and live preview; icons updated; total lines ~850.
- v1.39.0: Added '♥' to symbols palette after other hearts; added Unicode icons to buttons (replacing text where possible) for ribbon-like Word appearance; total lines ~795.
- v1.38.0: Fixed syntax error by replacing template literals with concatenation to avoid injection parsing issues; added self-validation on load; total lines ~785.
- v1.37.0: Simplified labels to ASCII to avoid potential Unicode parsing issues in older engines; shortened debug messages; total lines ~810.
- v1.36.0: Shortened @description to avoid potential long-line parsing issues; total lines ~810.
- v1.35.0: Ensured full code completeness and syntax validation; no truncation issues.
- v1.34.0: Fixed syntax error in observer setup (subtree: true).
- v1.33.0: Replaced individual heart/dash buttons with "Symbols" palette popup; total lines ~810.
- v1.32.0: Added hyphen, en/em dashes insert buttons; total lines ~725.
- v1.31.0: Hoisted font dropdown action; replaced optional chaining; total lines ~702.
- v1.30.0: Added font family dropdown and heart buttons.
- v1.29.0: hexToRgb global; logging with line count (612 lines).
- v1.28.0: Fixed shadow outline; unified hexToRgb; resize delay.
- Earlier: Base features, color picker, undo/redo, auto-resize.
Analyzed script integrity on 2025-11-04; syntax validated, no errors found.
*/
(function() {
    'use strict';
    // Global try-catch wrapper to prevent errors from halting execution
    try {
        // Configuration object for easy extension - declared first to avoid hoisting issues
        const CONFIG = {
            version: '1.40.0',
            toolbarId: 'note-formatting-toolbar',
            buttons: [
                // Font Group
                {
                    id: 'font-family-dropdown',
                    type: 'dropdown',
                    title: 'Font Family (select and apply to text)',
                    options: [ // Generics first
                        {value: 'serif', label: 'Serif'},
                        {value: 'sans-serif', label: 'Sans Serif'},
                        {value: 'monospace', label: 'Monospace'},
                        {value: 'cursive', label: 'Cursive'},
                        {value: 'fantasy', label: 'Fantasy'},
                        // Danbooru-supplied (aliases as values, common labels)
                        {value: 'comic', label: 'Comic Sans MS'},
                        {value: 'narrow', label: 'Arial Narrow'},
                        {value: 'mono', label: 'Plex Mono'},
                        {value: 'slab sans', label: 'Impact'},
                        {value: 'slab serif', label: 'Rockwell'},
                        {value: 'formal serif', label: 'Formal Serif (Lora)'},
                        {value: 'formal cursive', label: 'Formal Cursive'},
                        {value: 'print', label: 'Print (Kalam)'},
                        {value: 'hand', label: 'Hand (Indie Flower)'},
                        {value: 'childlike', label: 'Childlike (Giselle)'},
                        {value: 'blackletter', label: 'Blackletter'},
                        {value: 'scary', label: 'Scary (Anarchy)'}
                    ],
                    action: function(ta, value) { applyFontFamily(ta, value); }
                },
                {
                    id: 'font-size-btn',
                    label: 'Size',
                    title: 'Font Size (prompt px/em)',
                    action: function(ta) { promptFontSize(ta); }
                },
                {
                    id: 'pink-outline-btn',
                    label: 'Pink Outline',
                    title: 'Pink Outline Template (white text with pink outline, customizable)',
                    action: function(ta) { openPinkOutlinePicker(ta); }
                },
                {
                    id: 'big-btn',
                    label: 'Big',
                    title: 'Big Text (<big>)',
                    action: function(ta) { applyFormat(ta, '<big>', '</big>'); }
                },
                {
                    id: 'small-btn',
                    label: 'Small',
                    title: 'Small Text (<small>)',
                    action: function(ta) { applyFormat(ta, '<small>', '</small>'); }
                },
                {
                    id: 'sup-btn',
                    label: 'Sup',
                    title: 'Superscript (<sup>)',
                    action: function(ta) { applyFormat(ta, '<sup>', '</sup>'); }
                },
                {
                    id: 'sub-btn',
                    label: 'Sub',
                    title: 'Subscript (<sub>)',
                    action: function(ta) { applyFormat(ta, '<sub>', '</sub>'); }
                },
                {
                    id: 'shadow-btn',
                    label: 'Shadow',
                    title: 'Text Shadow (picker for offset/blur/color, outline mode)',
                    action: function(ta) { openShadowPicker(ta); }
                },
                {
                    id: 'color-btn',
                    label: 'Color',
                    title: 'Text Color (click for palette/picker)',
                    action: function(ta) { openColorPicker(ta, 'color'); }
                },
                {
                    id: 'highlight-btn',
                    label: 'Highlight',
                    title: 'Text Highlight (background-color, click for palette/picker)',
                    action: function(ta) { openColorPicker(ta, 'background-color'); }
                },
                {
                    id: 'bold-btn',
                    label: 'B',
                    title: 'Bold (<b>) (Ctrl+B)',
                    action: function(ta) { applyFormat(ta, '<b>', '</b>'); }
                },
                {
                    id: 'italic-btn',
                    label: 'I',
                    title: 'Italic (<i>) (Ctrl+I)',
                    action: function(ta) { applyFormat(ta, '<i>', '</i>'); }
                },
                {
                    id: 'underline-btn',
                    label: 'U',
                    title: 'Underline (<u>) (Ctrl+U)',
                    action: function(ta) { applyFormat(ta, '<u>', '</u>'); }
                },
                {
                    id: 'strikethrough-btn',
                    label: 'S',
                    title: 'Strikethrough (<s>)',
                    action: function(ta) { applyFormat(ta, '<s>', '</s>'); }
                },
                // Paragraph Group
                {
                    id: 'align-left-btn',
                    label: 'Left',
                    title: 'Align Left (<div align="left">)',
                    action: function(ta) { applyFormat(ta, '<div align="left">', '</div>'); }
                },
                {
                    id: 'align-center-btn',
                    label: 'Center',
                    title: 'Align Center (<div align="center">)',
                    action: function(ta) { applyFormat(ta, '<div align="center">', '</div>'); }
                },
                {
                    id: 'align-right-btn',
                    label: 'Right',
                    title: 'Align Right (<div align="right">)',
                    action: function(ta) { applyFormat(ta, '<div align="right">', '</div>'); }
                },
                // Insert Group
                {
                    id: 'link-btn',
                    label: 'Link',
                    title: 'Insert Link (<a href="...">)',
                    action: function(ta) { insertLink(ta); }
                },
                {
                    id: 'tn-btn',
                    label: 'TN',
                    title: 'Translator Note (<tn>)',
                    action: function(ta) { applyFormat(ta, '<tn>', '</tn>'); }
                },
                {
                    id: 'symbols-btn',
                    label: 'Symbols',
                    title: 'Symbols Palette (hearts, dashes, ★ ☆ • … etc.)',
                    action: function(ta) { openSymbolsPicker(ta); }
                },
                // Clear/History/Settings Group
                {
                    id: 'clear-btn',
                    label: 'Clear',
                    title: 'Clear Formatting (strip HTML tags)',
                    action: function(ta) { clearFormatting(ta); }
                },
                {
                    id: 'undo-btn',
                    label: 'Undo',
                    title: 'Undo (Ctrl+Z)',
                    action: function(ta) { undo(ta); }
                },
                {
                    id: 'redo-btn',
                    label: 'Redo',
                    title: 'Redo (Ctrl+Y)',
                    action: function(ta) { redo(ta); }
                },
                {
                    id: 'toggle-resize-btn',
                    label: 'AR',
                    title: 'Toggle Auto-Resize On/Off',
                    action: function(ta) { toggleAutoResize(); }
                },
                {
                    id: 'toggle-center-btn',
                    label: 'AC',
                    title: 'Toggle Auto-Center On/Off',
                    action: function(ta) { toggleAutoCenter(); }
                },
                {
                    id: 'settings-btn',
                    label: 'Settings',
                    title: 'Set Min Width/Height for Auto-Resize',
                    action: function(ta) { openSettings(); }
                }
            ],
            icons: {
                'font-size-btn': '📏',
                'pink-outline-btn': '💖',
                'big-btn': '↗️',
                'small-btn': '↙️',
                'sup-btn': '²',
                'sub-btn': '₂',
                'shadow-btn': '🌑',
                'color-btn': '🎨',
                'highlight-btn': '🖍️',
                'bold-btn': '𝐛',
                'italic-btn': '𝑖',
                'underline-btn': '𝑢',
                'strikethrough-btn': '𝑠',
                'align-left-btn': '◀',
                'align-center-btn': '▢',
                'align-right-btn': '▶',
                'link-btn': '🔗',
                'tn-btn': 'TN',
                'symbols-btn': '♥',
                'clear-btn': '🧹',
                'undo-btn': '↶',
                'redo-btn': '↷',
                'toggle-resize-btn': '📐',
                'toggle-center-btn': '🎯',
                'settings-btn': '⚙️'
            },
            shortcuts: {
                bold: 'Ctrl+B',
                italic: 'Ctrl+I',
                underline: 'Ctrl+U',
                undo: 'Ctrl+Z',
                redo: 'Ctrl+Y'
            },
            manualTrigger: 'Ctrl+Shift+I', // Force inject
            historyLimit: 50, // Max undo/redo states
            commonColors: ['#FF0000', '#FF8000', '#FFFF00', '#00FF00', '#0000FF', '#4B0082', '#8B00FF'], // ROYGBIV hex
            symbols: ['♡', '❤', '♥', '★', '☆', '•', '◦', '…', '‥', '—', '–', '-', '⸺', '⸻', '©', '™', '®', '§', '¶', '†', '‡', '°', '±', '×', '÷', '≈', '≠', '≤', '≥'], // Common translation/math symbols
            autoResize: {
                enabled: true,
                defaultWidth: 523,
                defaultHeight: 362
            },
            autoCenter: {
                enabled: true
            },
            styles: "#note-formatting-toolbar { " +
                    "display: flex; " +
                    "flex-wrap: wrap; " +
                    "gap: 10px; " +
                    "margin: 5px 0; " +
                    "padding: 5px; " +
                    "background: #f0f0f0; " +
                    "border: 1px solid #ccc; " +
                    "border-radius: 4px; " +
                    "font-family: sans-serif; " +
                    "font-size: 12px; " +
                    "justify-content: center; " +
                    "overflow-x: auto; " +
                    "max-width: 100%; " +
                    "} " +
                    "#note-formatting-toolbar .group { " +
                    "display: flex; " +
                    "gap: 2px; " +
                    "padding: 2px 5px; " +
                    "background: white; " +
                    "border-radius: 3px; " +
                    "border: 1px solid #ddd; " +
                    "} " +
                    "#note-formatting-toolbar button { " +
                    "padding: 4px 6px; " +
                    "border: 1px solid #ccc; " +
                    "background: white; " +
                    "border-radius: 3px; " +
                    "cursor: pointer; " +
                    "font-weight: bold; " +
                    "font-size: 11px; " +
                    "min-width: auto; " +
                    "} " +
                    "#note-formatting-toolbar button:hover { " +
                    "background: #e0e0e0; " +
                    "} " +
                    "#note-formatting-toolbar select { " +
                    "padding: 4px 6px; " +
                    "border: 1px solid #ccc; " +
                    "background: white; " +
                    "border-radius: 3px; " +
                    "cursor: pointer; " +
                    "font-weight: bold; " +
                    "font-size: 11px; " +
                    "min-width: auto; " +
                    "} " +
                    "#note-formatting-toolbar select:focus { " +
                    "outline: 1px solid #007cba; " +
                    "background: #e6f3ff; " +
                    "} " +
                    ".note-edit-dialog .ui-dialog-content { " +
                    "position: relative; " +
                    "} " +
                    ".note-edit-dialog #note-formatting-toolbar { " +
                    "margin-bottom: 5px; " +
                    "} " +
                    "#toolbar-minimize { " +
                    "margin-left: auto; " +
                    "background: #ddd; " +
                    "font-size: 10px; " +
                    "padding: 2px 4px; " +
                    "} " +
                    "#debug-indicator { " +
                    "position: fixed; " +
                    "top: 10px; " +
                    "right: 10px; " +
                    "background: #ffeb3b; " +
                    "color: #000; " +
                    "padding: 5px; " +
                    "border: 1px solid #ccc; " +
                    "font-size: 12px; " +
                    "z-index: 9999; " +
                    "display: none; " +
                    "max-width: 300px; " +
                    "} " +
                    "/* Color Picker Styles */ " +
                    "#color-picker, #shadow-picker, #symbol-picker, #pink-outline-picker { " +
                    "position: fixed; " +
                    "top: 50%; " +
                    "left: 50%; " +
                    "transform: translate(-50%, -50%); " +
                    "background: #1E1E2C; " +
                    "color: #E0E0E0; " +
                    "border: 1px solid #ccc; " +
                    "border-radius: 8px; " +
                    "padding: 20px; " +
                    "box-shadow: 0 4px 20px rgba(0,0,0,0.5); " +
                    "z-index: 10000; " +
                    "min-width: 300px; " +
                    "font-family: sans-serif; " +
                    "max-height: 80vh; " +
                    "overflow-y: auto; " +
                    "} " +
                    "#color-picker h3, #shadow-picker h3, #symbol-picker h3, #pink-outline-picker h3 { " +
                    "margin: 0 0 10px 0; " +
                    "text-align: center; " +
                    "color: #F0F0F0; " +
                    "} " +
                    ".palette { " +
                    "display: grid; " +
                    "grid-template-columns: repeat(7, 1fr); " +
                    "gap: 5px; " +
                    "margin-bottom: 15px; " +
                    "} " +
                    ".symbols-palette { " +
                    "display: grid; " +
                    "grid-template-columns: repeat(5, 1fr); " +
                    "gap: 10px; " +
                    "margin-bottom: 15px; " +
                    "justify-items: center; " +
                    "} " +
                    ".palette button, .symbols-palette button { " +
                    "width: 30px; " +
                    "height: 30px; " +
                    "border: 1px solid #666; " +
                    "border-radius: 4px; " +
                    "cursor: pointer; " +
                    "font-size: 16px; " +
                    "background: #333; " +
                    "color: #E0E0E0; " +
                    "} " +
                    ".palette button:hover, .symbols-palette button:hover { " +
                    "background: #444; " +
                    "} " +
                    ".custom-section, .shadow-section, .outline-section { " +
                    "margin-bottom: 10px; " +
                    "} " +
                    ".custom-section label, .shadow-section label, .outline-section label { " +
                    "display: block; " +
                    "margin-bottom: 5px; " +
                    "color: #D0D0D0; " +
                    "} " +
                    ".shadow-inputs, .outline-inputs { " +
                    "display: flex; " +
                    "flex-direction: column; " +
                    "gap: 5px; " +
                    "} " +
                    ".shadow-inputs input[type=\"number\"], .outline-inputs input[type=\"number\"] { " +
                    "background: #333; " +
                    "color: #fff; " +
                    "border: 1px solid #666; " +
                    "padding: 2px; " +
                    "} " +
                    ".rgb-sliders { " +
                    "display: flex; " +
                    "gap: 10px; " +
                    "margin-bottom: 10px; " +
                    "} " +
                    ".rgb-sliders input[type=\"range\"] { " +
                    "flex: 1; " +
                    "background: #333; " +
                    "color: #fff; " +
                    "} " +
                    ".rgb-inputs { " +
                    "display: flex; " +
                    "gap: 5px; " +
                    "} " +
                    ".rgb-inputs input[type=\"number\"] { " +
                    "width: 50px; " +
                    "background: #333; " +
                    "color: #fff; " +
                    "border: 1px solid #666; " +
                    "} " +
                    ".hex-input { " +
                    "margin-bottom: 10px; " +
                    "} " +
                    ".hex-input input[type=\"text\"] { " +
                    "width: 100px; " +
                    "background: #333; " +
                    "color: #fff; " +
                    "border: 1px solid #666; " +
                    "text-transform: uppercase; " +
                    "} " +
                    "#color-preview, #shadow-preview, #outline-preview { " +
                    "width: 100%; " +
                    "height: 40px; " +
                    "border: 1px solid #666; " +
                    "border-radius: 4px; " +
                    "margin-bottom: 10px; " +
                    "display: flex; " +
                    "align-items: center; " +
                    "justify-content: center; " +
                    "color: #fff; " +
                    "font-weight: bold; " +
                    "font-size: 18px; " +
                    "background: #333; " +
                    "} " +
                    ".preset-buttons { " +
                    "display: flex; " +
                    "gap: 5px; " +
                    "margin-bottom: 10px; " +
                    "} " +
                    ".preset-buttons button { " +
                    "padding: 4px 8px; " +
                    "background: #444; " +
                    "color: #E0E0E0; " +
                    "border: 1px solid #666; " +
                    "border-radius: 4px; " +
                    "cursor: pointer; " +
                    "font-size: 10px; " +
                    "} " +
                    ".preset-buttons button:hover { " +
                    "background: #555; " +
                    "} " +
                    ".picker-buttons { " +
                    "display: flex; " +
                    "gap: 10px; " +
                    "justify-content: center; " +
                    "} " +
                    ".picker-buttons button { " +
                    "padding: 8px 16px; " +
                    "border: 1px solid #666; " +
                    "border-radius: 4px; " +
                    "cursor: pointer; " +
                    "background: #333; " +
                    "color: #E0E0E0; " +
                    "} " +
                    ".picker-buttons button:hover { " +
                    "background: #444; " +
                    "} " +
                    "#color-picker-overlay, #shadow-picker-overlay, #symbol-picker-overlay, #pink-outline-picker-overlay { " +
                    "position: fixed; " +
                    "top: 0; " +
                    "left: 0; " +
                    "width: 100%; " +
                    "height: 100%; " +
                    "background: rgba(0,0,0,0.5); " +
                    "z-index: 9999; " +
                    "} " +
                    ".font-family-preview { " +
                    "font-family: inherit; " +
                    "font-size: inherit; " +
                    "font-weight: inherit; " +
                    "color: inherit; " +
                    "text-shadow: inherit; " +
                    "white-space: nowrap; " +
                    "overflow: hidden; " +
                    "text-overflow: ellipsis; " +
                    "}"
        };
        // Global variables for state management (per-tab isolation for multi-tab support)
        var observer = null;
        var activeDialogs = new Set(); // Track multiple dialogs if possible (rare, but robust)
        var toolbar = null;
        var textarea = null;
        var isMinimized = false;
        var debugMode = true; // Enable for logging; set to false in prod if needed
        var manualTriggerHandler = null;
        var history = []; // Undo/redo history array of {value, start, end}
        var historyIndex = -1; // Current position in history
        var colorPicker = null; // Color picker dialog
        var shadowPicker = null; // Shadow picker dialog
        var symbolPicker = null; // Symbols picker dialog
        var pinkOutlinePicker = null; // Pink outline picker dialog
        // Utility: Log with prefix for easy console filtering - uses CONFIG.version safely
        function log(message, data) {
            if (typeof data === 'undefined') data = null;
            var version = CONFIG ? CONFIG.version : 'unknown';
            console.log('[NoteFmtHelper v' + version + '] ' + message, data || '');
        }
        // Unified hexToRgb (strips # if present) - moved to global scope to eliminate duplication
        function hexToRgb(hex) {
            hex = hex.replace(/^#/, '');
            var r = parseInt(hex.substr(0, 2), 16);
            var g = parseInt(hex.substr(2, 2), 16);
            var b = parseInt(hex.substr(4, 2), 16);
            return { r: r, g: g, b: b };
        }
        // Apply font family - hoisted for syntax safety
        function applyFontFamily(ta, value) {
            if (!value) return;
            // Fallback to generic if needed (per docs)
            var family = value.includes(' ') ? value + ', serif' : value + ', sans-serif';
            var openTag = '<span style="font-family: ' + family + ';">';
            applyFormat(ta, openTag, '</span>');
        }
        // Load auto-resize and auto-center config from localStorage (persistent across tabs/sessions)
        function loadAutoResizeConfig() {
            try {
                var savedEnabled = localStorage.getItem('nfh_autoResizeEnabled');
                if (savedEnabled !== null) {
                    CONFIG.autoResize.enabled = savedEnabled === 'true';
                }
                var savedWidth = localStorage.getItem('nfh_defaultWidth');
                if (savedWidth && !isNaN(parseInt(savedWidth))) {
                    CONFIG.autoResize.defaultWidth = parseInt(savedWidth);
                }
                var savedHeight = localStorage.getItem('nfh_defaultHeight');
                if (savedHeight && !isNaN(parseInt(savedHeight))) {
                    CONFIG.autoResize.defaultHeight = parseInt(savedHeight);
                }
                var savedCenterEnabled = localStorage.getItem('nfh_autoCenterEnabled');
                if (savedCenterEnabled !== null) {
                    CONFIG.autoCenter.enabled = savedCenterEnabled === 'true';
                }
                log('Auto-resize and auto-center config loaded from localStorage.', { autoResize: CONFIG.autoResize, autoCenter: CONFIG.autoCenter });
            } catch (err) {
                log('Load auto-resize config error', err);
            }
        }
        // Utility: Inject styles if not already present - stringified to avoid const issues
        function injectStyles() {
            try {
                if (document.getElementById('note-formatting-styles')) return;
                var style = document.createElement('style');
                style.id = 'note-formatting-styles';
                style.textContent = CONFIG.styles;
                document.head.appendChild(style);
                log('Styles injected.');
            } catch (err) {
                console.error('[NoteFmtHelper] Style injection error:', err);
            }
        }
        // Utility: Create or update toolbar with minimize button (Word-like ribbon with groups)
        function createToolbar() {
            try {
                if (toolbar) return toolbar;
                toolbar = document.createElement('div');
                toolbar.id = CONFIG.toolbarId;
                // Groups for Word-like layout - updated to include pink-outline-btn
                var groups = {
                    font: ['font-family-dropdown', 'font-size-btn', 'pink-outline-btn', 'big-btn', 'small-btn', 'sup-btn', 'sub-btn', 'shadow-btn', 'color-btn', 'highlight-btn', 'bold-btn', 'italic-btn', 'underline-btn', 'strikethrough-btn'],
                    paragraph: ['align-left-btn', 'align-center-btn', 'align-right-btn'],
                    insert: ['link-btn', 'tn-btn', 'symbols-btn'],
                    history: ['clear-btn', 'undo-btn', 'redo-btn', 'toggle-resize-btn', 'toggle-center-btn', 'settings-btn']
                };
                Object.entries(groups).forEach(function(entry) {
                    var groupName = entry[0];
                    var btnIds = entry[1];
                    var group = document.createElement('div');
                    group.className = 'group';
                    group.title = groupName.charAt(0).toUpperCase() + groupName.slice(1); // e.g., "Font"
                    btnIds.forEach(function(btnId) {
                        var btnConfig = CONFIG.buttons.find(function(b) { return b.id === btnId; });
                        if (btnConfig) {
                            if (btnConfig.type === 'dropdown') {
                                // Create select for dropdown
                                var select = document.createElement('select');
                                select.id = btnConfig.id;
                                select.title = btnConfig.title;
                                // Default to first option
                                select.value = btnConfig.options[0] ? btnConfig.options[0].value : '';
                                btnConfig.options.forEach(function(opt) {
                                    var option = document.createElement('option');
                                    option.value = opt.value;
                                    option.textContent = opt.label;
                                    option.style.fontFamily = opt.value; // Preview in font style
                                    select.appendChild(option);
                                });
                                select.addEventListener('change', function(e) {
                                    var value = e.target.value;
                                    if (textarea && !textarea.disabled) {
                                        if (document.activeElement !== textarea) textarea.focus();
                                        saveState(textarea); // Pre-snapshot
                                        btnConfig.action(textarea, value);
                                        // Reset to first for quick re-use, but keep preview
                                        setTimeout(function() {
                                            select.value = btnConfig.options[0] ? btnConfig.options[0].value : '';
                                        }, 0);
                                    } else {
                                        log('Dropdown changed but textarea not ready.');
                                    }
                                });
                                group.appendChild(select);
                            } else {
                                // Standard button
                                var button = document.createElement('button');
                                button.id = btnConfig.id;
                                button.type = 'button';
                                button.innerHTML = CONFIG.icons[btnConfig.id] || btnConfig.label;
                                button.title = btnConfig.title;
                                button.addEventListener('click', function(e) {
                                    e.preventDefault();
                                    e.stopPropagation();
                                    if (textarea && !textarea.disabled) {
                                        if (document.activeElement !== textarea) textarea.focus();
                                        // Pre-snapshot for formatting actions (exclude undo/redo/toggle/settings)
                                        if (!['undo-btn', 'redo-btn', 'toggle-resize-btn', 'toggle-center-btn', 'settings-btn'].includes(btnConfig.id)) {
                                            saveState(textarea);
                                        }
                                        btnConfig.action(textarea);
                                        log('Button ' + btnConfig.id + ' clicked.');
                                    } else if (['toggle-resize-btn', 'toggle-center-btn', 'settings-btn'].includes(btnConfig.id)) {
                                        // Allow these even without textarea focus (global config)
                                        btnConfig.action();
                                        log(btnConfig.id + ' button clicked (global).');
                                    } else {
                                        log('Button clicked but textarea not ready.', { focused: document.activeElement });
                                    }
                                });
                                group.appendChild(button);
                            }
                        }
                    });
                    toolbar.appendChild(group);
                });
                // Minimize button (smaller, optional for buttonset fit)
                var minimizeBtn = document.createElement('button');
                minimizeBtn.id = 'toolbar-minimize';
                minimizeBtn.type = 'button';
                minimizeBtn.innerHTML = '-'; // Plain minus for minimize
                minimizeBtn.title = 'Minimize Formatting Toolbar';
                minimizeBtn.style.fontSize = '0.8em';
                minimizeBtn.style.padding = '0.3em 0.5em';
                minimizeBtn.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    toggleMinimize();
                });
                toolbar.appendChild(minimizeBtn);
                // Version display (small span, not a button) - in corner as per style
                var versionSpan = document.createElement('span');
                versionSpan.id = 'toolbar-version';
                versionSpan.textContent = 'v' + CONFIG.version;
                versionSpan.className = 'ui-widget';
                versionSpan.style.cssText = 'margin-left: 5px; font-size: 0.8em; color: #666; padding: 0.4em 0; align-self: center;';
                toolbar.appendChild(versionSpan);
                // Responsive: Adjust on resize
                window.addEventListener('resize', adjustToolbarLayout);
                adjustToolbarLayout();
                log('Toolbar created as Word-like ribbon with groups, symbols palette, and pink outline button.');
                return toolbar;
            } catch (err) {
                log('Toolbar creation error', err);
                return null;
            }
        }
        // Minimize/Maximize functionality for better UX (hides main buttons, including dropdowns)
        function toggleMinimize() {
            try {
                isMinimized = !isMinimized;
                const mainButtons = toolbar ? toolbar.querySelectorAll('button:not(#toolbar-minimize), select') : [];
                const versionSpan = document.getElementById('toolbar-version');
                const minimizeBtn = document.getElementById('toolbar-minimize');
                if (isMinimized) {
                    mainButtons.forEach(function(el) { el.style.display = 'none'; });
                    if (versionSpan) versionSpan.style.display = 'none';
                    if (minimizeBtn) {
                        minimizeBtn.innerHTML = '+'; // Plus for maximize
                        minimizeBtn.title = 'Expand Formatting Toolbar';
                    }
                    log('Formatting toolbar minimized.');
                } else {
                    mainButtons.forEach(function(el) { el.style.display = ''; });
                    if (versionSpan) versionSpan.style.display = '';
                    if (minimizeBtn) {
                        minimizeBtn.innerHTML = '-';
                        minimizeBtn.title = 'Minimize Formatting Toolbar';
                    }
                    adjustToolbarLayout();
                    log('Formatting toolbar expanded.');
                }
            } catch (err) {
                log('Toggle minimize error', err);
            }
        }
        // Responsive layout adjustment (for buttonset fit) - improved with matchMedia
        function adjustToolbarLayout() {
            try {
                const isMobile = window.matchMedia('(max-width: 800px)').matches;
                if (toolbar && isMobile) {
                    toolbar.style.flexDirection = 'column';
                    toolbar.style.alignItems = 'stretch';
                    toolbar.querySelectorAll('button, select').forEach(function(el) { el.style.minWidth = 'auto'; });
                } else {
                    toolbar.style.flexDirection = 'row';
                    toolbar.style.alignItems = 'center';
                    toolbar.querySelectorAll('button, select').forEach(function(el) { el.style.minWidth = 'initial'; });
                }
            } catch (err) {
                log('Layout adjustment error', err);
            }
        }
        // Center dialog on viewport (with bounds clamping)
        function centerDialog(dialog) {
            try {
                // Settle layout
                setTimeout(function() {
                    const rect = dialog.getBoundingClientRect();
                    const vw = window.innerWidth;
                    const vh = window.innerHeight;
                    let left = (vw - rect.width) / 2;
                    let top = (vh - rect.height) / 2;
                    // Clamp to viewport bounds
                    left = Math.max(0, Math.min(left, vw - rect.width));
                    top = Math.max(0, Math.min(top, vh - rect.height));
                    // Account for scroll
                    dialog.style.left = (left + window.scrollX) + 'px';
                    dialog.style.top = (top + window.scrollY) + 'px';
                    log('Centered dialog at ' + (left + window.scrollX) + 'px, ' + (top + window.scrollY) + 'px');
                }, 0);
            } catch (err) {
                log('Center dialog error', err);
            }
        }
        // Auto-expand dialog to fit ribbon + textarea
        function autoResizeDialog(dialog) {
            try {
                if (!CONFIG.autoResize.enabled) {
                    log('Auto-resize disabled; skipped.');
                    return;
                }
                if (dialog.dataset.helperResized === 'true') return; // One-time per dialog (reset via settings if needed)
                const tb = dialog.querySelector('#' + CONFIG.toolbarId);
                const ta = dialog.querySelector('textarea');
                if (!tb || !ta) {
                    log('Auto-resize skipped: Missing toolbar/textarea.');
                    return;
                }
                // Re-measure after height set (no inner timeout needed for immediacy)
                const tbRect = tb.getBoundingClientRect();
                const taRect = ta.getBoundingClientRect();
                // Calculate content dimensions
                const contentWidth = Math.max(tbRect.width, taRect.width) + 30; // Padding for borders/scroll
                const contentHeight = tbRect.height + taRect.height + 50; // Buffer for padding/margins
                const newWidth = Math.max(CONFIG.autoResize.defaultWidth, Math.max(500, contentWidth));
                const newHeight = Math.max(CONFIG.autoResize.defaultHeight, contentHeight + 150); // Header/footer/dialog chrome buffer
                // Always use direct style resize (reliable, no jQuery dependency/error)
                dialog.style.width = newWidth + 'px';
                dialog.style.height = newHeight + 'px';
                log('Auto-resized dialog via direct style: ' + newWidth + 'x' + newHeight + 'px');
                dialog.dataset.helperResized = 'true';
                // Auto-center if enabled
                if (CONFIG.autoCenter.enabled) {
                    centerDialog(dialog);
                }
            } catch (err) {
                log('Auto-resize error', err);
            }
        }
        // Toggle auto-resize enable/disable
        function toggleAutoResize() {
            try {
                CONFIG.autoResize.enabled = !CONFIG.autoResize.enabled;
                localStorage.setItem('nfh_autoResizeEnabled', CONFIG.autoResize.enabled.toString());
                const status = CONFIG.autoResize.enabled ? 'enabled' : 'disabled';
                log('Auto-resize ' + status + '.');
                showDebugIndicator('Auto-resize ' + status + '.', 'info');
                // If enabling, re-apply to current dialog
                if (CONFIG.autoResize.enabled) {
                    const dialog = document.querySelector('.note-edit-dialog');
                    if (dialog) {
                        dialog.dataset.helperResized = 'false';
                        setTimeout(function() { autoResizeDialog(dialog); }, 100);
                    }
                }
            } catch (err) {
                log('Toggle auto-resize error', err);
            }
        }
        // Toggle auto-center enable/disable
        function toggleAutoCenter() {
            try {
                CONFIG.autoCenter.enabled = !CONFIG.autoCenter.enabled;
                localStorage.setItem('nfh_autoCenterEnabled', CONFIG.autoCenter.enabled.toString());
                const status = CONFIG.autoCenter.enabled ? 'enabled' : 'disabled';
                log('Auto-center ' + status + '.');
                showDebugIndicator('Auto-center ' + status + '.', 'info');
                // If enabling, re-apply to current dialog
                if (CONFIG.autoCenter.enabled) {
                    const dialog = document.querySelector('.note-edit-dialog');
                    if (dialog) {
                        centerDialog(dialog);
                    }
                }
            } catch (err) {
                log('Toggle auto-center error', err);
            }
        }
        // Open settings for width/height mins only
        function openSettings() {
            try {
                var newWidthStr = prompt('Enter default min width (px):', CONFIG.autoResize.defaultWidth.toString());
                var newWidth = parseInt(newWidthStr);
                if (isNaN(newWidth) || newWidth < 300) {
                    newWidth = CONFIG.autoResize.defaultWidth;
                    log('Invalid width; using default.');
                }
                CONFIG.autoResize.defaultWidth = newWidth;
                localStorage.setItem('nfh_defaultWidth', newWidth.toString());
                var newHeightStr = prompt('Enter default min height (px):', CONFIG.autoResize.defaultHeight.toString());
                var newHeight = parseInt(newHeightStr);
                if (isNaN(newHeight) || newHeight < 200) {
                    newHeight = CONFIG.autoResize.defaultHeight;
                    log('Invalid height; using default.');
                }
                CONFIG.autoResize.defaultHeight = newHeight;
                localStorage.setItem('nfh_defaultHeight', newHeight.toString());
                log('Auto-resize dimensions updated.', CONFIG.autoResize);
                showDebugIndicator('Dimensions updated: ' + newWidth + 'x' + newHeight, 'info');
                // Re-apply to current dialog if enabled
                if (CONFIG.autoResize.enabled) {
                    const dialog = document.querySelector('.note-edit-dialog');
                    if (dialog) {
                        dialog.dataset.helperResized = 'false';
                        setTimeout(function() { autoResizeDialog(dialog); }, 100);
                    }
                }
            } catch (err) {
                log('Settings error', err);
                alert('Settings update failed; check console.');
            }
        }
        // Unified apply format function for tags, styles, aligns (replaces applyStyle/applyAlign)
        function applyFormat(ta, openTag, closeTag) {
            try {
                const start = ta.selectionStart;
                const end = ta.selectionEnd;
                const text = ta.value;
                const selected = text.substring(start, end);
                const before = text.substring(0, start);
                const after = text.substring(end);
                var newText;
                var cursorStart, cursorEnd;
                if (selected) {
                    newText = before + openTag + selected + closeTag + after;
                    cursorStart = start + openTag.length;
                    cursorEnd = cursorStart + selected.length;
                } else {
                    const placeholder = openTag + 'text' + closeTag;
                    newText = before + placeholder + after;
                    cursorStart = start + openTag.length;
                    cursorEnd = cursorStart + 4; // Position inside 'text'
                }
                ta.value = newText;
                ta.selectionStart = cursorStart;
                ta.selectionEnd = cursorEnd;
                ta.focus();
                ta.dispatchEvent(new Event('input', { bubbles: true }));
                // Post-snapshot after successful apply
                saveState(ta);
                log('Applied format: ' + openTag.substring(0, 20) + '...');
            } catch (err) {
                log('Apply format error', err);
            }
        }
        // Prompt for font size and apply via applyFormat
        function promptFontSize(ta) {
            try {
                const size = prompt('Enter font size (e.g., 18px, 1.5em, large):', '16px');
                if (!size) return; // No post-save if cancelled
                const styleStr = 'font-size: ' + size + ';';
                const openTag = '<span style="' + styleStr + '">';
                applyFormat(ta, openTag, '</span>'); // Post-save inside applyFormat
            } catch (err) {
                log('Font size prompt error', err);
            }
        }
        // Open color picker for text color or highlight
        function openColorPicker(ta, type) {
            try {
                if (colorPicker) {
                    closeColorPicker();
                }
                if (shadowPicker) {
                    closeShadowPicker();
                }
                if (symbolPicker) {
                    closeSymbolsPicker();
                }
                if (pinkOutlinePicker) {
                    closePinkOutlinePicker();
                }
                // Overlay
                const overlay = document.createElement('div');
                overlay.id = 'color-picker-overlay';
                overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9998;';
                document.body.appendChild(overlay);
                // Picker dialog
                colorPicker = document.createElement('div');
                colorPicker.id = 'color-picker';
                var titleText = (type === 'background-color') ? 'Text Highlight' : 'Text Color';
                colorPicker.innerHTML = '<h3>' + titleText + '</h3>' +
                    '<div class="palette"></div>' +
                    '<div class="custom-section">' +
                    '<label>Custom RGB</label>' +
                    '<div class="rgb-sliders">' +
                    '<input type="range" id="r-slider" min="0" max="255" value="255" />' +
                    '<input type="range" id="g-slider" min="0" max="255" value="0" />' +
                    '<input type="range" id="b-slider" min="0" max="255" value="0" />' +
                    '</div>' +
                    '<div class="rgb-inputs">' +
                    '<span>R:</span><input type="number" id="r-input" min="0" max="255" value="255" />' +
                    '<span>G:</span><input type="number" id="g-input" min="0" max="255" value="0" />' +
                    '<span>B:</span><input type="number" id="b-input" min="0" max="255" value="0" />' +
                    '</div>' +
                    '<div class="hex-input">' +
                    '<label>Hex (#RRGGBB):</label>' +
                    '<input type="text" id="hex-input" value="#FF0000" maxlength="7" />' +
                    '</div>' +
                    '<label>Brightness (Alpha 0-1)</label>' +
                    '<input type="range" id="alpha-slider" min="0" max="1" step="0.01" value="1" />' +
                    '<span id="alpha-value">1.00</span>' +
                    '</div>' +
                    '<div id="color-preview" style="background-color: rgb(255,0,0);"></div>' +
                    '<div class="picker-buttons">' +
                    '<button id="picker-ok">OK</button>' +
                    '<button id="picker-cancel">Cancel</button>' +
                    '</div>';
                document.body.appendChild(colorPicker);
                // Palette
                const palette = colorPicker.querySelector('.palette');
                CONFIG.commonColors.forEach(function(color) {
                    const btn = document.createElement('button');
                    btn.style.backgroundColor = color;
                    btn.addEventListener('click', function() {
                        const rgb = hexToRgb(color);
                        colorPicker.querySelector('#r-slider').value = rgb.r;
                        colorPicker.querySelector('#g-slider').value = rgb.g;
                        colorPicker.querySelector('#b-slider').value = rgb.b;
                        colorPicker.querySelector('#r-input').value = rgb.r;
                        colorPicker.querySelector('#g-input').value = rgb.g;
                        colorPicker.querySelector('#b-input').value = rgb.b;
                        colorPicker.querySelector('#hex-input').value = color;
                        updateColor(colorPicker);
                    });
                    palette.appendChild(btn);
                });
                // OK button - post-save after apply
                colorPicker.querySelector('#picker-ok').addEventListener('click', function(e) {
                    const r = colorPicker.querySelector('#r-slider').value;
                    const g = colorPicker.querySelector('#g-slider').value;
                    const b = colorPicker.querySelector('#b-slider').value;
                    const alpha = colorPicker.querySelector('#alpha-slider').value;
                    const colorValue = (alpha < 1) ? 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' : 'rgb(' + r + ', ' + g + ', ' + b + ')';
                    const styleStr = type + ': ' + colorValue + ';';
                    const openTag = '<span style="' + styleStr + '">';
                    applyFormat(ta, openTag, '</span>'); // Post-save inside applyFormat
                    closeColorPicker();
                });
                // Cancel button
                colorPicker.querySelector('#picker-cancel').addEventListener('click', closeColorPicker);
                // Close on overlay click
                overlay.addEventListener('click', closeColorPicker);
                // ESC key to close
                const escHandler = function(e) {
                    if (e.key === 'Escape') closeColorPicker();
                };
                window.addEventListener('keydown', escHandler);
                // Update color preview
                function updateColor(picker) {
                    const r = picker.querySelector('#r-slider').value;
                    const g = picker.querySelector('#g-slider').value;
                    const b = picker.querySelector('#b-slider').value;
                    const alpha = picker.querySelector('#alpha-slider').value;
                    picker.querySelector('#alpha-value').textContent = alpha;
                    const color = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
                    picker.querySelector('#color-preview').style.backgroundColor = color;
                    picker.querySelector('#r-input').value = r;
                    picker.querySelector('#g-input').value = g;
                    picker.querySelector('#b-input').value = b;
                    // Update hex
                    const hex = '#' + Math.round(r).toString(16).padStart(2, '0') + Math.round(g).toString(16).padStart(2, '0') + Math.round(b).toString(16).padStart(2, '0');
                    picker.querySelector('#hex-input').value = hex.toUpperCase();
                }
                // Update slider from input
                function updateSlider(channel, picker) {
                    const input = picker.querySelector('#' + channel + '-input').value;
                    picker.querySelector('#' + channel + '-slider').value = input;
                    updateColor(picker);
                }
                // Add event listeners for inputs/sliders/hex
                colorPicker.addEventListener('input', function(e) {
                    if (e.target.matches('input[type="range"], input[type="number"]')) {
                        if (e.target.id.includes('input') && e.target.type === 'number') {
                            const channel = e.target.id.split('-')[0];
                            updateSlider(channel, colorPicker);
                        } else {
                            updateColor(colorPicker);
                        }
                    } else if (e.target.id === 'hex-input') {
                        const hexValue = e.target.value.replace('#', '').toUpperCase();
                        if (hexValue.match(/^([0-9A-F]{6})$/)) {
                            const rgb = hexToRgb(hexValue);
                            colorPicker.querySelector('#r-slider').value = rgb.r;
                            colorPicker.querySelector('#g-slider').value = rgb.g;
                            colorPicker.querySelector('#b-slider').value = rgb.b;
                            colorPicker.querySelector('#r-input').value = rgb.r;
                            colorPicker.querySelector('#g-input').value = rgb.g;
                            colorPicker.querySelector('#b-input').value = rgb.b;
                            updateColor(colorPicker);
                        }
                    }
                });
                // Initial update
                updateColor(colorPicker);
                // Cleanup ESC on close
                function closeColorPicker() {
                    if (colorPicker) colorPicker.remove();
                    if (overlay) overlay.remove();
                    colorPicker = null;
                    window.removeEventListener('keydown', escHandler);
                }
            } catch (err) {
                log('Color picker open error', err);
            }
        }
        // Open shadow picker for text shadow (outlines/drop shadows)
        function openShadowPicker(ta) {
            try {
                if (shadowPicker) {
                    closeShadowPicker();
                }
                if (colorPicker) {
                    closeColorPicker();
                }
                if (symbolPicker) {
                    closeSymbolsPicker();
                }
                if (pinkOutlinePicker) {
                    closePinkOutlinePicker();
                }
                // Overlay
                const overlay = document.createElement('div');
                overlay.id = 'shadow-picker-overlay';
                overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9998;';
                document.body.appendChild(overlay);
                // Picker dialog
                shadowPicker = document.createElement('div');
                shadowPicker.id = 'shadow-picker';
                shadowPicker.innerHTML = '<h3>Text Shadow</h3>' +
                    '<div class="preset-buttons">' +
                    '<button id="preset-drop">Drop Shadow</button>' +
                    '<button id="preset-outline">Outline</button>' +
                    '</div>' +
                    '<div class="shadow-section">' +
                    '<div class="shadow-inputs">' +
                    '<label>X Offset (px): <input type="number" id="x-offset" value="1" step="0.1" min="-10" max="10" aria-label="X offset" /></label>' +
                    '<label>Y Offset (px): <input type="number" id="y-offset" value="1" step="0.1" min="-10" max="10" aria-label="Y offset" /></label>' +
                    '<label>Blur Radius (px): <input type="number" id="blur" value="0" step="0.1" min="0" max="20" aria-label="Blur radius" /></label>' +
                    '<label><input type="checkbox" id="outline-mode" /> Outline Mode (4 shadows around text)</label>' +
                    '</div>' +
                    '</div>' +
                    '<div class="palette"></div>' +
                    '<div class="custom-section">' +
                    '<label>Shadow Color</label>' +
                    '<div class="rgb-sliders">' +
                    '<input type="range" id="r-slider" min="0" max="255" value="0" />' +
                    '<input type="range" id="g-slider" min="0" max="255" value="0" />' +
                    '<input type="range" id="b-slider" min="0" max="255" value="0" />' +
                    '</div>' +
                    '<div class="rgb-inputs">' +
                    '<span>R:</span><input type="number" id="r-input" min="0" max="255" value="0" />' +
                    '<span>G:</span><input type="number" id="g-input" min="0" max="255" value="0" />' +
                    '<span>B:</span><input type="number" id="b-input" min="0" max="255" value="0" />' +
                    '</div>' +
                    '<div class="hex-input">' +
                    '<label>Hex (#RRGGBB):</label>' +
                    '<input type="text" id="hex-input" value="#000000" maxlength="7" />' +
                    '</div>' +
                    '<label>Brightness (Alpha 0-1)</label>' +
                    '<input type="range" id="alpha-slider" min="0" max="1" step="0.01" value="1" />' +
                    '<span id="alpha-value">1.00</span>' +
                    '</div>' +
                    '<div id="shadow-preview" style="background-color: #333; color: #fff; text-shadow: 1px 1px 0 #000;">Preview</div>' +
                    '<div class="picker-buttons">' +
                    '<button id="picker-ok">OK</button>' +
                    '<button id="picker-cancel">Cancel</button>' +
                    '</div>';
                document.body.appendChild(shadowPicker);
                // Palette (reuse common colors)
                const palette = shadowPicker.querySelector('.palette');
                CONFIG.commonColors.forEach(function(color) {
                    const btn = document.createElement('button');
                    btn.style.backgroundColor = color;
                    btn.addEventListener('click', function() {
                        const rgb = hexToRgb(color);
                        shadowPicker.querySelector('#r-slider').value = rgb.r;
                        shadowPicker.querySelector('#g-slider').value = rgb.g;
                        shadowPicker.querySelector('#b-slider').value = rgb.b;
                        shadowPicker.querySelector('#r-input').value = rgb.r;
                        shadowPicker.querySelector('#g-input').value = rgb.g;
                        shadowPicker.querySelector('#b-input').value = rgb.b;
                        shadowPicker.querySelector('#hex-input').value = color;
                        updateShadowPreview(shadowPicker);
                    });
                    palette.appendChild(btn);
                });
                // Preset buttons
                shadowPicker.querySelector('#preset-drop').addEventListener('click', function() {
                    shadowPicker.querySelector('#x-offset').value = 2;
                    shadowPicker.querySelector('#y-offset').value = 2;
                    shadowPicker.querySelector('#blur').value = 2;
                    shadowPicker.querySelector('#outline-mode').checked = false;
                    shadowPicker.querySelector('#r-slider').value = 0;
                    shadowPicker.querySelector('#g-slider').value = 0;
                    shadowPicker.querySelector('#b-slider').value = 0;
                    shadowPicker.querySelector('#r-input').value = 0;
                    shadowPicker.querySelector('#g-input').value = 0;
                    shadowPicker.querySelector('#b-input').value = 0;
                    shadowPicker.querySelector('#hex-input').value = '#000000';
                    updateShadowPreview(shadowPicker);
                });
                shadowPicker.querySelector('#preset-outline').addEventListener('click', function() {
                    shadowPicker.querySelector('#x-offset').value = 1;
                    shadowPicker.querySelector('#y-offset').value = 1;
                    shadowPicker.querySelector('#blur').value = 0;
                    shadowPicker.querySelector('#outline-mode').checked = true;
                    shadowPicker.querySelector('#r-slider').value = 0;
                    shadowPicker.querySelector('#g-slider').value = 0;
                    shadowPicker.querySelector('#b-slider').value = 0;
                    shadowPicker.querySelector('#r-input').value = 0;
                    shadowPicker.querySelector('#g-input').value = 0;
                    shadowPicker.querySelector('#b-input').value = 0;
                    shadowPicker.querySelector('#hex-input').value = '#000000';
                    updateShadowPreview(shadowPicker);
                });
                // Outline mode listener
                shadowPicker.querySelector('#outline-mode').addEventListener('change', function(e) {
                    updateShadowPreview(shadowPicker);
                });
                // OK button
                shadowPicker.querySelector('#picker-ok').addEventListener('click', function(e) {
                    var x = parseFloat(shadowPicker.querySelector('#x-offset').value) || 1;
                    var y = parseFloat(shadowPicker.querySelector('#y-offset').value) || 1;
                    var blur = parseFloat(shadowPicker.querySelector('#blur').value) || 0;
                    x = Math.max(-10, Math.min(10, x)); // Clamp
                    y = Math.max(-10, Math.min(10, y));
                    blur = Math.max(0, Math.min(20, blur));
                    const r = shadowPicker.querySelector('#r-slider').value;
                    const g = shadowPicker.querySelector('#g-slider').value;
                    const b = shadowPicker.querySelector('#b-slider').value;
                    const alpha = shadowPicker.querySelector('#alpha-slider').value;
                    const colorValue = (alpha < 1) ? 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' : 'rgb(' + r + ', ' + g + ', ' + b + ')';
                    var shadowRule;
                    const absX = Math.abs(x);
                    const absY = Math.abs(y);
                    if (shadowPicker.querySelector('#outline-mode').checked) {
                        shadowRule = '-' + absX + 'px -' + absY + 'px ' + blur + 'px ' + colorValue + ', ' + absX + 'px -' + absY + 'px ' + blur + 'px ' + colorValue + ', -' + absX + 'px ' + absY + 'px ' + blur + 'px ' + colorValue + ', ' + absX + 'px ' + absY + 'px ' + blur + 'px ' + colorValue;
                    } else {
                        shadowRule = x + 'px ' + y + 'px ' + blur + 'px ' + colorValue;
                    }
                    const styleStr = 'text-shadow: ' + shadowRule + ';';
                    const openTag = '<span style="' + styleStr + '">';
                    applyFormat(ta, openTag, '</span>'); // Post-save inside
                    closeShadowPicker();
                });
                // Cancel button
                shadowPicker.querySelector('#picker-cancel').addEventListener('click', closeShadowPicker);
                // Close on overlay click
                overlay.addEventListener('click', closeShadowPicker);
                // ESC key to close
                const escHandler = function(e) {
                    if (e.key === 'Escape') closeShadowPicker();
                };
                window.addEventListener('keydown', escHandler);
                // Update shadow preview
                function updateShadowPreview(picker) {
                    const x = parseFloat(picker.querySelector('#x-offset').value) || 1;
                    const y = parseFloat(picker.querySelector('#y-offset').value) || 1;
                    const blur = parseFloat(picker.querySelector('#blur').value) || 0;
                    const r = picker.querySelector('#r-slider').value;
                    const g = picker.querySelector('#g-slider').value;
                    const b = picker.querySelector('#b-slider').value;
                    const alpha = picker.querySelector('#alpha-slider').value;
                    const colorValue = (alpha < 1) ? 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')' : 'rgb(' + r + ', ' + g + ', ' + b + ')';
                    var shadowRule;
                    const absX = Math.abs(x);
                    const absY = Math.abs(y);
                    if (picker.querySelector('#outline-mode').checked) {
                        shadowRule = '-' + absX + 'px -' + absY + 'px ' + blur + 'px ' + colorValue + ', ' + absX + 'px -' + absY + 'px ' + blur + 'px ' + colorValue + ', -' + absX + 'px ' + absY + 'px ' + blur + 'px ' + colorValue + ', ' + absX + 'px ' + absY + 'px ' + blur + 'px ' + colorValue;
                    } else {
                        shadowRule = x + 'px ' + y + 'px ' + blur + 'px ' + colorValue;
                    }
                    picker.querySelector('#shadow-preview').style.textShadow = shadowRule;
                    // Update inputs sync
                    picker.querySelector('#r-input').value = r;
                    picker.querySelector('#g-input').value = g;
                    picker.querySelector('#b-input').value = b;
                    const hex = '#' + Math.round(r).toString(16).padStart(2, '0') + Math.round(g).toString(16).padStart(2, '0') + Math.round(b).toString(16).padStart(2, '0');
                    picker.querySelector('#hex-input').value = hex.toUpperCase();
                    picker.querySelector('#alpha-value').textContent = alpha;
                }
                // Update slider from input (shared logic)
                function updateSlider(channel, picker) {
                    const input = picker.querySelector('#' + channel + '-input').value;
                    picker.querySelector('#' + channel + '-slider').value = input;
                    updateShadowPreview(picker);
                }
                // Add event listeners for inputs/sliders/hex/offsets
                shadowPicker.addEventListener('input', function(e) {
                    if (e.target.matches('#x-offset, #y-offset, #blur')) {
                        updateShadowPreview(shadowPicker);
                    } else if (e.target.matches('input[type="range"], input[type="number"]')) {
                        if (e.target.id.includes('input') && e.target.type === 'number') {
                            const channel = e.target.id.split('-')[0];
                            updateSlider(channel, shadowPicker);
                        } else {
                            updateShadowPreview(shadowPicker);
                        }
                    } else if (e.target.id === 'hex-input') {
                        const hexValue = e.target.value.replace('#', '').toUpperCase();
                        if (hexValue.match(/^([0-9A-F]{6})$/)) {
                            const rgb = hexToRgb(hexValue);
                            shadowPicker.querySelector('#r-slider').value = rgb.r;
                            shadowPicker.querySelector('#g-slider').value = rgb.g;
                            shadowPicker.querySelector('#b-slider').value = rgb.b;
                            shadowPicker.querySelector('#r-input').value = rgb.r;
                            shadowPicker.querySelector('#g-input').value = rgb.g;
                            shadowPicker.querySelector('#b-input').value = rgb.b;
                            updateShadowPreview(shadowPicker);
                        }
                    }
                });
                // Initial update
                updateShadowPreview(shadowPicker);
                // Cleanup
                function closeShadowPicker() {
                    if (shadowPicker) shadowPicker.remove();
                    if (overlay) overlay.remove();
                    shadowPicker = null;
                    window.removeEventListener('keydown', escHandler);
                }
            } catch (err) {
                log('Shadow picker open error', err);
                showDebugIndicator('Shadow picker failed; check console.', 'error');
            }
        }
        // Open pink outline picker for white text with pink outline template
        function openPinkOutlinePicker(ta) {
            try {
                if (pinkOutlinePicker) {
                    closePinkOutlinePicker();
                }
                if (colorPicker) {
                    closeColorPicker();
                }
                if (shadowPicker) {
                    closeShadowPicker();
                }
                if (symbolPicker) {
                    closeSymbolsPicker();
                }
                // Overlay
                const overlay = document.createElement('div');
                overlay.id = 'pink-outline-picker-overlay';
                overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9998;';
                document.body.appendChild(overlay);
                // Picker dialog
                pinkOutlinePicker = document.createElement('div');
                pinkOutlinePicker.id = 'pink-outline-picker';
                pinkOutlinePicker.innerHTML = '<h3>Pink Outline Template</h3>' +
                    '<div class="outline-section">' +
                    '<div class="outline-inputs">' +
                    '<label>Font Family: <select id="font-family-select"><option value="print">Print (Kalam)</option><option value="serif">Serif</option><option value="sans-serif">Sans Serif</option><option value="monospace">Monospace</option><option value="cursive">Cursive</option><option value="fantasy">Fantasy</option><option value="comic">Comic Sans MS</option><option value="narrow">Arial Narrow</option><option value="mono">Plex Mono</option><option value="slab sans">Impact</option><option value="slab serif">Rockwell</option><option value="formal serif">Formal Serif (Lora)</option><option value="formal cursive">Formal Cursive</option><option value="hand">Hand (Indie Flower)</option><option value="childlike">Childlike (Giselle)</option><option value="blackletter">Blackletter</option><option value="scary">Scary (Anarchy)</option></select></label>' +
                    '<label>Font Size (px): <input type="number" id="font-size" value="30" min="10" max="100" step="1" /></label>' +
                    '<label><input type="checkbox" id="font-bold" checked /> Bold</label>' +
                    '<label>Outline Offset (px): <input type="number" id="offset" value="2" min="0" max="10" step="0.1" /></label>' +
                    '<label>Blur Radius (px): <input type="number" id="blur" value="5" min="0" max="20" step="0.1" /></label>' +
                    '</div>' +
                    '</div>' +
                    '<div class="outline-section">' +
                    '<h4>Text Color</h4>' +
                    '<div class="palette"></div>' +
                    '<div class="custom-section">' +
                    '<label>Custom RGB</label>' +
                    '<div class="rgb-sliders">' +
                    '<input type="range" id="text-r-slider" min="0" max="255" value="255" />' +
                    '<input type="range" id="text-g-slider" min="0" max="255" value="255" />' +
                    '<input type="range" id="text-b-slider" min="0" max="255" value="255" />' +
                    '</div>' +
                    '<div class="rgb-inputs">' +
                    '<span>R:</span><input type="number" id="text-r-input" min="0" max="255" value="255" />' +
                    '<span>G:</span><input type="number" id="text-g-input" min="0" max="255" value="255" />' +
                    '<span>B:</span><input type="number" id="text-b-input" min="0" max="255" value="255" />' +
                    '</div>' +
                    '<div class="hex-input">' +
                    '<label>Hex (#RRGGBB):</label>' +
                    '<input type="text" id="text-hex-input" value="#FFFFFF" maxlength="7" />' +
                    '</div>' +
                    '</div>' +
                    '</div>' +
                    '<div class="outline-section">' +
                    '<h4>Outline Color</h4>' +
                    '<div class="palette" id="outline-palette"></div>' +
                    '<div class="custom-section">' +
                    '<label>Custom RGB</label>' +
                    '<div class="rgb-sliders">' +
                    '<input type="range" id="outline-r-slider" min="0" max="255" value="235" />' +
                    '<input type="range" id="outline-g-slider" min="0" max="255" value="115" />' +
                    '<input type="range" id="outline-b-slider" min="0" max="255" value="161" />' +
                    '</div>' +
                    '<div class="rgb-inputs">' +
                    '<span>R:</span><input type="number" id="outline-r-input" min="0" max="255" value="235" />' +
                    '<span>G:</span><input type="number" id="outline-g-input" min="0" max="255" value="115" />' +
                    '<span>B:</span><input type="number" id="outline-b-input" min="0" max="255" value="161" />' +
                    '</div>' +
                    '<div class="hex-input">' +
                    '<label>Hex (#RRGGBB):</label>' +
                    '<input type="text" id="outline-hex-input" value="#EB73A1" maxlength="7" />' +
                    '</div>' +
                    '</div>' +
                    '</div>' +
                    '<div id="outline-preview" style="background-color: #333; color: #fff; font-size: 30px; font-family: print; font-weight: bold; text-shadow: -2px -2px 5px #eb73a1, -2px 0 5px #eb73a1, -2px 2px 5px #eb73a1, 0 -2px 5px #eb73a1, 0 2px 5px #eb73a1, 2px -2px 5px #eb73a1, 2px 0 5px #eb73a1, 2px 2px 5px #eb73a1; display: flex; align-items: center; justify-content: center; height: 50px; border: 1px solid #666; border-radius: 4px; margin-bottom: 10px;">Preview</div>' +
                    '<div class="picker-buttons">' +
                    '<button id="picker-ok">OK</button>' +
                    '<button id="picker-cancel">Cancel</button>' +
                    '</div>';
                document.body.appendChild(pinkOutlinePicker);
                // Text Color Palette
                const textPalette = pinkOutlinePicker.querySelector('.palette');
                CONFIG.commonColors.forEach(function(color) {
                    const btn = document.createElement('button');
                    btn.style.backgroundColor = color;
                    btn.addEventListener('click', function() {
                        const rgb = hexToRgb(color);
                        pinkOutlinePicker.querySelector('#text-r-slider').value = rgb.r;
                        pinkOutlinePicker.querySelector('#text-g-slider').value = rgb.g;
                        pinkOutlinePicker.querySelector('#text-b-slider').value = rgb.b;
                        pinkOutlinePicker.querySelector('#text-r-input').value = rgb.r;
                        pinkOutlinePicker.querySelector('#text-g-input').value = rgb.g;
                        pinkOutlinePicker.querySelector('#text-b-input').value = rgb.b;
                        pinkOutlinePicker.querySelector('#text-hex-input').value = color;
                        updateOutlinePreview(pinkOutlinePicker);
                    });
                    textPalette.appendChild(btn);
                });
                // Outline Color Palette
                const outlinePalette = pinkOutlinePicker.querySelector('#outline-palette');
                CONFIG.commonColors.forEach(function(color) {
                    const btn = document.createElement('button');
                    btn.style.backgroundColor = color;
                    btn.addEventListener('click', function() {
                        const rgb = hexToRgb(color);
                        pinkOutlinePicker.querySelector('#outline-r-slider').value = rgb.r;
                        pinkOutlinePicker.querySelector('#outline-g-slider').value = rgb.g;
                        pinkOutlinePicker.querySelector('#outline-b-slider').value = rgb.b;
                        pinkOutlinePicker.querySelector('#outline-r-input').value = rgb.r;
                        pinkOutlinePicker.querySelector('#outline-g-input').value = rgb.g;
                        pinkOutlinePicker.querySelector('#outline-b-input').value = rgb.b;
                        pinkOutlinePicker.querySelector('#outline-hex-input').value = color;
                        updateOutlinePreview(pinkOutlinePicker);
                    });
                    outlinePalette.appendChild(btn);
                });
                // OK button
                pinkOutlinePicker.querySelector('#picker-ok').addEventListener('click', function(e) {
                    const fontFamily = pinkOutlinePicker.querySelector('#font-family-select').value;
                    const fontFamilyStr = fontFamily.includes(' ') ? fontFamily + ', serif' : fontFamily + ', sans-serif';
                    const fontSize = pinkOutlinePicker.querySelector('#font-size').value + 'px';
                    const fontBold = pinkOutlinePicker.querySelector('#font-bold').checked ? 'bold' : 'normal';
                    const offset = parseFloat(pinkOutlinePicker.querySelector('#offset').value) || 2;
                    const blur = parseFloat(pinkOutlinePicker.querySelector('#blur').value) || 5;
                    const textR = pinkOutlinePicker.querySelector('#text-r-slider').value;
                    const textG = pinkOutlinePicker.querySelector('#text-g-slider').value;
                    const textB = pinkOutlinePicker.querySelector('#text-b-slider').value;
                    const outlineR = pinkOutlinePicker.querySelector('#outline-r-slider').value;
                    const outlineG = pinkOutlinePicker.querySelector('#outline-g-slider').value;
                    const outlineB = pinkOutlinePicker.querySelector('#outline-b-slider').value;
                    const textColor = 'rgb(' + textR + ', ' + textG + ', ' + textB + ')';
                    const outlineColor = 'rgb(' + outlineR + ', ' + outlineG + ', ' + outlineB + ')';
                    // Generate 8-direction shadow
                    const absOffset = Math.abs(offset);
                    var shadowRule = '-' + absOffset + 'px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '-' + absOffset + 'px 0px ' + blur + 'px ' + outlineColor + ', ' +
                                     '-' + absOffset + 'px ' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '0px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '0px ' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px 0px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px ' + absOffset + 'px ' + blur + 'px ' + outlineColor;
                    const styleStr = 'font-size: ' + fontSize + '; color: ' + textColor + '; font-family: ' + fontFamilyStr + '; font-weight: ' + fontBold + '; text-shadow: ' + shadowRule + ';';
                    const openTag = '<span style="' + styleStr + '">';
                    applyFormat(ta, openTag, '</span>'); // Post-save inside
                    closePinkOutlinePicker();
                });
                // Cancel button
                pinkOutlinePicker.querySelector('#picker-cancel').addEventListener('click', closePinkOutlinePicker);
                // Close on overlay click
                overlay.addEventListener('click', closePinkOutlinePicker);
                // ESC key to close
                const escHandler = function(e) {
                    if (e.key === 'Escape') closePinkOutlinePicker();
                };
                window.addEventListener('keydown', escHandler);
                // Update outline preview
                function updateOutlinePreview(picker) {
                    const fontFamily = picker.querySelector('#font-family-select').value;
                    const fontFamilyStr = fontFamily.includes(' ') ? fontFamily + ', serif' : fontFamily + ', sans-serif';
                    const fontSize = picker.querySelector('#font-size').value + 'px';
                    const fontBold = picker.querySelector('#font-bold').checked ? 'bold' : 'normal';
                    const offset = parseFloat(picker.querySelector('#offset').value) || 2;
                    const blur = parseFloat(picker.querySelector('#blur').value) || 5;
                    const textR = picker.querySelector('#text-r-slider').value;
                    const textG = picker.querySelector('#text-g-slider').value;
                    const textB = picker.querySelector('#text-b-slider').value;
                    const outlineR = picker.querySelector('#outline-r-slider').value;
                    const outlineG = picker.querySelector('#outline-g-slider').value;
                    const outlineB = picker.querySelector('#outline-b-slider').value;
                    const textColor = 'rgb(' + textR + ', ' + textG + ', ' + textB + ')';
                    const outlineColor = 'rgb(' + outlineR + ', ' + outlineG + ', ' + outlineB + ')';
                    // Generate 8-direction shadow
                    const absOffset = Math.abs(offset);
                    var shadowRule = '-' + absOffset + 'px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '-' + absOffset + 'px 0px ' + blur + 'px ' + outlineColor + ', ' +
                                     '-' + absOffset + 'px ' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '0px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     '0px ' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px -' + absOffset + 'px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px 0px ' + blur + 'px ' + outlineColor + ', ' +
                                     absOffset + 'px ' + absOffset + 'px ' + blur + 'px ' + outlineColor;
                    const preview = picker.querySelector('#outline-preview');
                    preview.style.fontFamily = fontFamilyStr;
                    preview.style.fontSize = fontSize;
                    preview.style.fontWeight = fontBold;
                    preview.style.color = textColor;
                    preview.style.textShadow = shadowRule;
                    // Update hex inputs
                    const textHex = '#' + Math.round(textR).toString(16).padStart(2, '0') + Math.round(textG).toString(16).padStart(2, '0') + Math.round(textB).toString(16).padStart(2, '0');
                    picker.querySelector('#text-hex-input').value = textHex.toUpperCase();
                    const outlineHex = '#' + Math.round(outlineR).toString(16).padStart(2, '0') + Math.round(outlineG).toString(16).padStart(2, '0') + Math.round(outlineB).toString(16).padStart(2, '0');
                    picker.querySelector('#outline-hex-input').value = outlineHex.toUpperCase();
                }
                // Update sliders from inputs
                function updateTextSlider(channel, picker) {
                    const input = picker.querySelector('#text-' + channel + '-input').value;
                    picker.querySelector('#text-' + channel + '-slider').value = input;
                    updateOutlinePreview(picker);
                }
                function updateOutlineSlider(channel, picker) {
                    const input = picker.querySelector('#outline-' + channel + '-input').value;
                    picker.querySelector('#outline-' + channel + '-slider').value = input;
                    updateOutlinePreview(picker);
                }
                // Add event listeners
                pinkOutlinePicker.addEventListener('input', function(e) {
                    if (e.target.id === 'font-family-select' || e.target.id === 'font-size' || e.target.id === 'font-bold' || e.target.id === 'offset' || e.target.id === 'blur') {
                        updateOutlinePreview(pinkOutlinePicker);
                    } else if (e.target.matches('#text-r-slider, #text-g-slider, #text-b-slider')) {
                        updateOutlinePreview(pinkOutlinePicker);
                    } else if (e.target.matches('#outline-r-slider, #outline-g-slider, #outline-b-slider')) {
                        updateOutlinePreview(pinkOutlinePicker);
                    } else if (e.target.id.includes('text-') && e.target.id.includes('input') && e.target.type === 'number') {
                        const channel = e.target.id.replace('text-', '').replace('-input', '');
                        updateTextSlider(channel, pinkOutlinePicker);
                    } else if (e.target.id.includes('outline-') && e.target.id.includes('input') && e.target.type === 'number') {
                        const channel = e.target.id.replace('outline-', '').replace('-input', '');
                        updateOutlineSlider(channel, pinkOutlinePicker);
                    } else if (e.target.id === 'text-hex-input') {
                        const hexValue = e.target.value.replace('#', '').toUpperCase();
                        if (hexValue.match(/^([0-9A-F]{6})$/)) {
                            const rgb = hexToRgb(hexValue);
                            pinkOutlinePicker.querySelector('#text-r-slider').value = rgb.r;
                            pinkOutlinePicker.querySelector('#text-g-slider').value = rgb.g;
                            pinkOutlinePicker.querySelector('#text-b-slider').value = rgb.b;
                            pinkOutlinePicker.querySelector('#text-r-input').value = rgb.r;
                            pinkOutlinePicker.querySelector('#text-g-input').value = rgb.g;
                            pinkOutlinePicker.querySelector('#text-b-input').value = rgb.b;
                            updateOutlinePreview(pinkOutlinePicker);
                        }
                    } else if (e.target.id === 'outline-hex-input') {
                        const hexValue = e.target.value.replace('#', '').toUpperCase();
                        if (hexValue.match(/^([0-9A-F]{6})$/)) {
                            const rgb = hexToRgb(hexValue);
                            pinkOutlinePicker.querySelector('#outline-r-slider').value = rgb.r;
                            pinkOutlinePicker.querySelector('#outline-g-slider').value = rgb.g;
                            pinkOutlinePicker.querySelector('#outline-b-slider').value = rgb.b;
                            pinkOutlinePicker.querySelector('#outline-r-input').value = rgb.r;
                            pinkOutlinePicker.querySelector('#outline-g-input').value = rgb.g;
                            pinkOutlinePicker.querySelector('#outline-b-input').value = rgb.b;
                            updateOutlinePreview(pinkOutlinePicker);
                        }
                    }
                });
                // Initial update
                updateOutlinePreview(pinkOutlinePicker);
                // Cleanup
                function closePinkOutlinePicker() {
                    if (pinkOutlinePicker) pinkOutlinePicker.remove();
                    if (overlay) overlay.remove();
                    pinkOutlinePicker = null;
                    window.removeEventListener('keydown', escHandler);
                }
            } catch (err) {
                log('Pink outline picker open error', err);
                showDebugIndicator('Pink outline picker failed; check console.', 'error');
            }
        }
        // Open symbols palette picker
        function openSymbolsPicker(ta) {
            try {
                if (symbolPicker) {
                    closeSymbolsPicker();
                }
                if (colorPicker) {
                    closeColorPicker();
                }
                if (shadowPicker) {
                    closeShadowPicker();
                }
                if (pinkOutlinePicker) {
                    closePinkOutlinePicker();
                }
                // Overlay
                const overlay = document.createElement('div');
                overlay.id = 'symbol-picker-overlay';
                overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9998;';
                document.body.appendChild(overlay);
                // Picker dialog
                symbolPicker = document.createElement('div');
                symbolPicker.id = 'symbol-picker';
                symbolPicker.innerHTML = '<h3>Symbols Palette</h3>' +
                    '<div class="symbols-palette"></div>' +
                    '<div class="picker-buttons">' +
                    '<button id="picker-cancel">Close</button>' +
                    '</div>';
                document.body.appendChild(symbolPicker);
                // Populate symbols palette
                const palette = symbolPicker.querySelector('.symbols-palette');
                CONFIG.symbols.forEach(function(sym) {
                    const btn = document.createElement('button');
                    btn.innerHTML = sym;
                    btn.title = 'Insert ' + sym;
                    btn.addEventListener('click', function() {
                        insertAtCursor(ta, sym); // Insert and save state inside insertAtCursor
                        closeSymbolsPicker();
                    });
                    palette.appendChild(btn);
                });
                // Cancel button
                symbolPicker.querySelector('#picker-cancel').addEventListener('click', closeSymbolsPicker);
                // Close on overlay click
                overlay.addEventListener('click', closeSymbolsPicker);
                // ESC key to close
                const escHandler = function(e) {
                    if (e.key === 'Escape') closeSymbolsPicker();
                };
                window.addEventListener('keydown', escHandler);
                // Cleanup
                function closeSymbolsPicker() {
                    if (symbolPicker) symbolPicker.remove();
                    if (overlay) overlay.remove();
                    symbolPicker = null;
                    window.removeEventListener('keydown', escHandler);
                }
            } catch (err) {
                log('Symbols picker open error', err);
                showDebugIndicator('Symbols picker failed; check console.', 'error');
            }
        }
        // Clear formatting: Strip HTML tags
        function clearFormatting(ta) {
            try {
                const text = ta.value.replace(/<[^>]*>/g, '');
                ta.value = text;
                ta.selectionStart = ta.selectionEnd = 0;
                ta.focus();
                ta.dispatchEvent(new Event('input', { bubbles: true }));
                // Post-snapshot after clear
                saveState(ta);
                log('Formatting cleared.');
            } catch (err) {
                log('Clear formatting error', err);
            }
        }
        // Insert link prompt with intelligent defaults
        function insertLink(ta) {
            try {
                const start = ta.selectionStart;
                const end = ta.selectionEnd;
                const selected = ta.value.substring(start, end);
                const url = prompt('Enter URL:');
                if (!url) return; // No post-save if cancelled
                var text;
                if (selected) {
                    text = prompt('Enter link text (optional):', selected) || selected;
                } else {
                    text = prompt('Enter link text (optional):', '') || url;
                }
                const linkHTML = '<a href="' + url + '">' + text + '</a>';
                insertAtCursor(ta, linkHTML); // Post-save inside insertAtCursor
            } catch (err) {
                log('Link insert error', err);
            }
        }
        // Insert text at cursor
        function insertAtCursor(ta, text) {
            try {
                const start = ta.selectionStart;
                const end = ta.selectionEnd;
                ta.value = ta.value.substring(0, start) + text + ta.value.substring(end);
                ta.selectionStart = ta.selectionEnd = start + text.length;
                ta.focus();
                ta.dispatchEvent(new Event('input', { bubbles: true }));
                // Post-snapshot after insert
                saveState(ta);
            } catch (err) {
                log('Insert at cursor error', err);
            }
        }
        // History management for undo/redo - now saves {value, start, end}
        function saveState(ta) {
            try {
                const state = {
                    value: ta.value,
                    start: ta.selectionStart,
                    end: ta.selectionEnd
                };
                // Trim history to limit
                if (history.length > CONFIG.historyLimit) {
                    history.shift();
                    if (historyIndex > 0) historyIndex--;
                }
                // Push current state after current index (linear, discards branches)
                history = history.slice(0, historyIndex + 1);
                history.push(state);
                historyIndex = history.length - 1;
                log('State saved. History length: ' + history.length);
            } catch (err) {
                log('Save state error', err);
            }
        }
        function undo(ta) {
            try {
                if (historyIndex > 0) {
                    historyIndex--;
                    const state = history[historyIndex];
                    ta.value = state.value;
                    ta.selectionStart = state.start;
                    ta.selectionEnd = state.end;
                    ta.focus();
                    ta.dispatchEvent(new Event('input', { bubbles: true }));
                    log('Undo to index ' + historyIndex);
                } else {
                    log('Undo: At start of history.');
                }
            } catch (err) {
                log('Undo error', err);
            }
        }
        function redo(ta) {
            try {
                if (historyIndex < history.length - 1) {
                    historyIndex++;
                    const state = history[historyIndex];
                    ta.value = state.value;
                    ta.selectionStart = state.start;
                    ta.selectionEnd = state.end;
                    ta.focus();
                    ta.dispatchEvent(new Event('input', { bubbles: true }));
                    log('Redo to index ' + historyIndex);
                } else {
                    log('Redo: At end of history.');
                }
            } catch (err) {
                log('Redo error', err);
            }
        }
        // Setup initial history snapshot (no ongoing listener)
        function setupHistory(ta) {
            try {
                // Initial save (pre-any-changes backup)
                saveState(ta);
                log('Initial history snapshot set up.');
            } catch (err) {
                log('History setup error', err);
            }
        }
        // Keyboard shortcuts (per-tab, checks focus) - extended for undo/redo
        function setupShortcuts() {
            try {
                // Remove existing listener to avoid duplicates in multi-tab/reload
                document.removeEventListener('keydown', handleKeydown);
                document.addEventListener('keydown', handleKeydown, true);
                log('Shortcuts set up.');
            } catch (err) {
                log('Shortcuts setup error', err);
            }
        }
        function handleKeydown(e) {
            try {
                if (!textarea || document.activeElement !== textarea) return;
                if (e.ctrlKey) {
                    if (e.key === 'b') {
                        e.preventDefault();
                        CONFIG.buttons.find(function(b) { return b.id === 'bold-btn'; }).action(textarea);
                    } else if (e.key === 'i') {
                        e.preventDefault();
                        CONFIG.buttons.find(function(b) { return b.id === 'italic-btn'; }).action(textarea);
                    } else if (e.key === 'u') {
                        e.preventDefault();
                        CONFIG.buttons.find(function(b) { return b.id === 'underline-btn'; }).action(textarea);
                    } else if (e.key === 'z' && !e.shiftKey) {
                        e.preventDefault();
                        CONFIG.buttons.find(function(b) { return b.id === 'undo-btn'; }).action(textarea);
                    } else if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) {
                        e.preventDefault();
                        CONFIG.buttons.find(function(b) { return b.id === 'redo-btn'; }).action(textarea);
                    }
                }
            } catch (err) {
                log('Keydown handler error', err);
            }
        }
        // Manual force inject handler - defined early to avoid ReferenceError
        function setupManualTrigger() {
            try {
                if (manualTriggerHandler) return;
                manualTriggerHandler = function(e) {
                    if (e.ctrlKey && e.shiftKey && e.key === 'I') {
                        e.preventDefault();
                        const dialog = document.querySelector('.note-edit-dialog');
                        if (dialog) {
                            injectToolbar(dialog, true); // Force with banner
                            log('Manual trigger: Forced injection.');
                        } else {
                            showDebugIndicator('Manual trigger: No dialog open.', 'error');
                        }
                    }
                };
                document.addEventListener('keydown', manualTriggerHandler, true);
                log('Manual trigger (Ctrl+Shift+I) set up.');
            } catch (err) {
                log('Manual trigger setup error', err);
            }
        }
        // Dialog injection: Insert ribbon above textarea
        function injectToolbar(dialog, isManual) {
            if (typeof isManual === 'undefined') isManual = false;
            try {
                log('Attempting injection for dialog:', dialog);
                const content = dialog.querySelector('.ui-dialog-content');
                if (!content) {
                    log('No .ui-dialog-content found.');
                    return;
                }
                const headerSpan = content.querySelector('span');
                if (!headerSpan) {
                    log('No header span found.');
                    return;
                }
                textarea = content.querySelector('textarea');
                if (!textarea) {
                    log('No textarea found in content.');
                    return;
                }
                // Stable injection check: Use dataset attribute to prevent re-inject
                if (dialog.dataset.helperInjected === 'true') {
                    log('Dialog already injected (dataset check). Skipping.');
                    return;
                }
                // Remove any existing toolbar in content
                const existing = content.querySelector('#' + CONFIG.toolbarId);
                if (existing) {
                    existing.remove();
                    log('Removed existing toolbar.');
                }
                const tb = createToolbar();
                if (tb) {
                    content.insertBefore(tb, textarea); // Insert after header, before textarea
                    log('Formatting ribbon inserted above textarea.');
                } else {
                    log('Failed to create toolbar.');
                    return;
                }
                // Early textarea height adjustment for immediate fit
                const taFullHeight = Math.max(200, textarea.scrollHeight);
                textarea.style.height = taFullHeight + 'px';
                // Mark as injected
                dialog.dataset.helperInjected = 'true';
                setupShortcuts(); // Re-setup for this textarea
                setupHistory(textarea); // Initial snapshot only
                // Auto-resize dialog immediately after insertion (increased delay for layout settle)
                setTimeout(function() { autoResizeDialog(dialog); }, 150);
                // Cleanup on dialog close - remove dataset
                const observerCleanup = new MutationObserver(function(mutations) {
                    if (!document.body.contains(dialog)) {
                        activeDialogs.delete(dialog); // Track by reference now
                        if (tb && tb.parentNode) tb.remove();
                        delete dialog.dataset.helperInjected;
                        delete dialog.dataset.helperResized;
                        history = [];
                        historyIndex = -1;
                        if (colorPicker) closeColorPicker();
                        if (shadowPicker) closeShadowPicker();
                        if (symbolPicker) closeSymbolsPicker();
                        if (pinkOutlinePicker) closePinkOutlinePicker();
                        observerCleanup.disconnect();
                        log('Cleaned up for closed dialog.');
                    }
                });
                observerCleanup.observe(document.body, { childList: true, subtree: true });
                // Only show banner on first auto or manual
                if (isManual || !activeDialogs.has(dialog)) {
                    log('Ribbon injected above textarea for dialog.');
                    showDebugIndicator('Word-like Ribbon Injected Above Textarea! Symbols Palette added for hearts/dashes/etc. Pink Outline template available.', 'success');
                }
                activeDialogs.add(dialog); // Track by reference for multi-dialog
            } catch (err) {
                log('Injection error', err);
                showDebugIndicator('Injection Failed - Check Console. Try Ctrl+Shift+I.', 'error');
                // Autonomous recovery: Poll will retry
            }
        }
        // Debug indicator: Temporary overlay for visibility issues
        function showDebugIndicator(message, type) {
            if (typeof type === 'undefined') type = 'info';
            try {
                if (!debugMode) return;
                var indicator = document.getElementById('debug-indicator');
                if (!indicator) {
                    indicator = document.createElement('div');
                    indicator.id = 'debug-indicator';
                    document.body.appendChild(indicator);
                }
                indicator.textContent = '[NoteFmtHelper] ' + message;
                indicator.style.background = (type === 'error') ? '#ffcccc' : (type === 'success') ? '#ccffcc' : '#ffeb3b';
                indicator.style.display = 'block';
                setTimeout(function() { indicator.style.display = 'none'; }, 5000); // Shorter now to reduce spam
                log(message);
            } catch (err) {
                console.error('[NoteFmtHelper] Debug indicator error', err);
            }
        }
        // Main observer for dialog detection (subtree for nested changes)
        function setupObserver() {
            try {
                if (observer) {
                    log('Observer already set up.');
                    return;
                }
                observer = new MutationObserver(function(mutations) {
                    var detected = false;
                    mutations.forEach(function(mutation) {
                        if (mutation.type === 'childList') {
                            mutation.addedNodes.forEach(function(node) {
                                if (node.nodeType === 1 && node.matches && node.matches('.note-edit-dialog')) {
                                    log('Dialog added via observer:', node);
                                    detected = true;
                                    setTimeout(function() { injectToolbar(node); }, 500); // Keep for render stability
                                }
                            });
                        }
                    });
                    if (detected) log('Observer triggered injection.');
                });
                observer.observe(document.body, { childList: true, subtree: true });
            } catch (err) {
                log('Observer setup error', err);
            }
        }
        // Fallback poll for edge cases (e.g., observer misses, multi-tab glitches) - faster for persistence
        var pollInterval = null;
        function startPoll() {
            try {
                if (pollInterval) return;
                pollInterval = setInterval(function() {
                    const dialogs = document.querySelectorAll('.note-edit-dialog');
                    if (dialogs.length === 0) {
                        if (toolbar) {
                            toolbar.remove();
                            toolbar = null;
                            activeDialogs.clear();
                            log('No dialogs; cleaned up.');
                        }
                        return;
                    }
                    dialogs.forEach(function(dialog) {
                        // Use dataset check here too
                        if (dialog.dataset.helperInjected !== 'true') {
                            const content = dialog.querySelector('.ui-dialog-content');
                            const ta = content ? content.querySelector('textarea') : null;
                            if (content && ta) {
                                log('Poll detected eligible dialog:', dialog);
                                injectToolbar(dialog);
                            }
                        }
                    });
                }, 3000); // Slower poll (3s) now that dataset prevents retry
                log('Poll started (every 3s).');
            } catch (err) {
                log('Poll start error', err);
            }
        }
        // Init: Robust, multi-tab safe - setupManualTrigger called after definition
        function init() {
            try {
                loadAutoResizeConfig(); // Load user settings first
                injectStyles();
                setupObserver();
                startPoll(); // Always run poll for reliability
                setupManualTrigger(); // Defined early, no ReferenceError
                // Multi-tab: Listen for focus to re-inject if needed
                window.addEventListener('focus', function(e) {
                    setTimeout(function() {
                        setupObserver();
                        if (pollInterval) clearInterval(pollInterval);
                        startPoll();
                    }, 100);
                });
                showDebugIndicator('Script Loaded v' + CONFIG.version + '! Open dialog for ribbon & markup. Ctrl+Shift+I to force. AR/AC/Settings for auto-resize/center. Symbols for palette. Pink Outline for template.', 'info');
                log('Note Formatting Helper v' + CONFIG.version + ' initialized. Ready for multi-tab use. Open DevTools > Console to monitor logs.');
            } catch (err) {
                console.error('[NoteFmtHelper] Init error:', err);
                showDebugIndicator('Init error - script partially loaded. Try Ctrl+Shift+I on dialog open.', 'error');
            }
        }
        // DOM ready check
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', init);
        } else {
            init();
        }
        // Handle page navigation (Danbooru hash changes)
        window.addEventListener('hashchange', function(e) {
            setTimeout(function() {
                setupObserver();
                if (pollInterval) clearInterval(pollInterval);
                startPoll();
            }, 500);
            log('Page navigated; re-setup observers.');
        });
    } catch (globalErr) {
        console.error('[NoteFmtHelper] Global error - script failed to load:', globalErr);
        // Fallback: Create minimal debug indicator
        const indicator = document.createElement('div');
        indicator.id = 'debug-indicator';
        indicator.style.cssText = 'position:fixed;top:10px;right:10px;background:#ffcccc;color:#000;padding:5px;border:1px solid #ccc;z-index:9999;';
        indicator.textContent = '[NoteFmtHelper] Fatal error - reinstall script. Check console.';
        document.body.appendChild(indicator);
    }
})();