/* specialchars.js
 *
 * Fixed round Omega button (top-right) that opens a panel with useful
 * special characters (arrows, superscripts, subscripts, math symbols,
 * Greek, dingbats, fractions, spaces, Unicode blocks, etc.).
 *
 * Clicking a symbol inserts it at the current cursor position of the last
 * focused input/textarea/contentEditable element. Caret stays after the
 * inserted character. Panel stays open until you close it.
 *
 * Safe for Latin1- or UTF-8-served pages, because all non-ASCII characters
 * in JavaScript strings are expressed as \uXXXX or via String.fromCharCode.
 */

(function () {
    'use strict';

    function rangeChars(start, end) {
        var out = [];
        for (var cp = start; cp <= end; cp++) {
            out.push(String.fromCharCode(cp));
        }
        return out;
    }

    var superSubLetters = (function () {
        var cps = [
            0x1D43, 0x1D47, 0x1D9C, 0x1D48, 0x1D49, 0x1DA0, 0x1D4D, 0x02B0,
            0x2071, 0x02B2, 0x1D4F, 0x02E1, 0x1D50, 0x207F, 0x1D52, 0x1D56,
            0x02B3, 0x02E2, 0x1D57, 0x1D58, 0x1D5B, 0x02B7, 0x02E3, 0x02B8,
            0x1DBB,
            0x1D2C, 0x1D2E, 0x1D30, 0x1D31, 0x1D33, 0x1D34, 0x1D35, 0x1D36,
            0x1D37, 0x1D38, 0x1D39, 0x1D3A, 0x1D3C, 0x1D3E, 0x1D3F, 0x1D40,
            0x1D41, 0x2C7D, 0x1D42,
            0x2090, 0x2091, 0x2095, 0x1D62, 0x2C7C, 0x2096, 0x2097, 0x2098,
            0x2099, 0x2092, 0x209A, 0x1D63, 0x209B, 0x209C, 0x1D64, 0x1D65,
            0x2093,
            0x1D45, 0x1D5D, 0x1D5E, 0x1D5F, 0x1D4B, 0x1DBF, 0x1DA5, 0x1DB2,
            0x1D60, 0x1D61, 0x1D66, 0x1D67, 0x1D68, 0x1D69, 0x1D6A
        ];
        var out = [];
        for (var i = 0; i < cps.length; i++) {
            out.push(String.fromCharCode(cps[i]));
        }
        return out;
    })();

    var scCharGroups = [
        {
            name: 'Dashes & punctuation (useful subset)',
            chars: [].concat(
                rangeChars(0x2010, 0x2015),
                ['\u2E17'],
                rangeChars(0x2020, 0x2027),
                rangeChars(0x2030, 0x2031),
                rangeChars(0x203C, 0x203D)
            )
        },
        {
            name: 'Quotes (useful subset)',
            chars: [].concat(
                ['\u00AB', '\u00BB'],
                rangeChars(0x2018, 0x201F),
                ['\u2039', '\u203A']
            )
        },
        {
            name: 'Superscripts & subscripts',
            chars: [].concat(
                ['\u2070', '\u00B9', '\u00B2', '\u00B3'],
                rangeChars(0x2074, 0x207E),
                ['\u207F'],
                rangeChars(0x2080, 0x208E),
                superSubLetters
            )
        },
        {
            name: 'Fractions & Roman numerals',
            chars: [].concat(
                rangeChars(0x00BC, 0x00BE),
                rangeChars(0x2150, 0x215F),
                rangeChars(0x2160, 0x216B)
            )
        },
        {
            name: 'Arrows (useful subset)',
            chars: [].concat(
                rangeChars(0x2190, 0x2199),
                ['\u21D0', '\u21D1', '\u21D2', '\u21D3', '\u21D4'],
                rangeChars(0x21A9, 0x21AA),
                ['\u21BA', '\u21BB'],
                ['\u21C4', '\u21C6'],
                rangeChars(0x21E6, 0x21E9),
                ['\u21F5'],
                rangeChars(0x27F5, 0x27FF)
            )
        },
        {
            name: 'Math (useful subset)',
            chars: [].concat(
                ['\u00B1', '\u00D7', '\u00F7'],
                ['\u2212'],
                rangeChars(0x221A, 0x221C),
                ['\u221E'],
                ['\u2211', '\u220F'],
                ['\u222B'],
                ['\u2248'],
                ['\u2260', '\u2264', '\u2265'],
                ['\u2200', '\u2203', '\u2205'],
                ['\u2208', '\u2209'],
                ['\u2227', '\u2228'],
                ['\u2229', '\u222A'],
                ['\u2282', '\u2283'],
                ['\u2286', '\u2287'],
                ['\u2261'],
                ['\u226A', '\u226B']
            )
        },
        {
            name: 'Greek (basic)',
            chars: [].concat(
                rangeChars(0x0391, 0x03A1),
                rangeChars(0x03A3, 0x03A9),
                rangeChars(0x03B1, 0x03C9)
            )
        },
        {
            name: 'Currency (useful subset)',
            chars: [].concat(
                ['\u20AC', '\u00A3', '\u00A5', '\u00A2'],
                rangeChars(0x20A0, 0x20BF)
            )
        },
        {
            name: 'Spaces (labeled)',
            chars: [
                { c: '\u0020', label: 'SP',    title: 'Space' },
                { c: '\u00A0', label: 'NBSP',  title: 'No-break space' },
                { c: '\u2000', label: 'ENQ',   title: 'En quad' },
                { c: '\u2001', label: 'EMQ',   title: 'Em quad' },
                { c: '\u2002', label: 'ENSP',  title: 'En space' },
                { c: '\u2003', label: 'EMSP',  title: 'Em space' },
                { c: '\u2004', label: '3/EM',  title: 'Three-per-em space' },
                { c: '\u2005', label: '4/EM',  title: 'Four-per-em space' },
                { c: '\u2006', label: '6/EM',  title: 'Six-per-em space' },
                { c: '\u2007', label: 'FIG',   title: 'Figure space' },
                { c: '\u2008', label: 'PUNC',  title: 'Punctuation space' },
                { c: '\u2009', label: 'THIN',  title: 'Thin space' },
                { c: '\u200A', label: 'HAIR',  title: 'Hair space' },
                { c: '\u202F', label: 'NNBSP', title: 'Narrow no-break space' },
                { c: '\u205F', label: 'MMSP',  title: 'Medium mathematical space' },
                { c: '\u200B', label: 'ZWSP',  title: 'Zero-width space' },
                { c: '\u2060', label: 'WJ',    title: 'Word joiner' },
                { c: '\u3000', label: 'IDEO',  title: 'Ideographic space' }
            ]
        },
        {
            name: 'Dingbats & icons (useful subset)',
            chars: [].concat(
                ['\u2713', '\u2714', '\u2715', '\u2717', '\u2718'],
                ['\u2605', '\u2606'],
                ['\u2708', '\u2709'],
                ['\u260E', '\u260F'],
                ['\u2610', '\u2611', '\u2612'],
                ['\u26A0', '\u26A1'],
                ['\u2764', '\u2665'],
                ['\u2660', '\u2663', '\u2666'],
                rangeChars(0x2600, 0x2603),
                ['\u2744', '\u2615']
            )
        },
        {
            name: 'Misc symbols (useful subset)',
            chars: [].concat(
                ['\u00B0', '\u00B5', '\u00A7', '\u00A9', '\u00AE', '\u2122', '\u00B6'],
                ['\u2032', '\u2033'],
                ['\u2113', '\u2116'],
                ['\u2124', '\u2115', '\u211D']
            )
        },
        { name: 'Unicode: Latin-1 Supplement (00A0-00FF)', chars: rangeChars(0x00A0, 0x00FF) },
        { name: 'Unicode: Latin Extended-A (0100-017F)', chars: rangeChars(0x0100, 0x017F) },
        { name: 'Unicode: Latin Extended-B (0180-024F)', chars: rangeChars(0x0180, 0x024F) },
        { name: 'Unicode: IPA Extensions (0250-02AF)', chars: rangeChars(0x0250, 0x02AF) },
        { name: 'Unicode: Spacing Modifier Letters (02B0-02FF)', chars: rangeChars(0x02B0, 0x02FF) },
        { name: 'Unicode: Combining Diacritical Marks (0300-036F)', chars: rangeChars(0x0300, 0x036F) },
        { name: 'Unicode: Latin Extended Additional (1E00-1EFF)', chars: rangeChars(0x1E00, 0x1EFF) },
        { name: 'Unicode: Greek & Coptic (0370-03FF)', chars: rangeChars(0x0370, 0x03FF) },
        { name: 'Unicode: Cyrillic (0400-04FF)', chars: rangeChars(0x0400, 0x04FF) },
        { name: 'Unicode: Cyrillic Supplement (0500-052F)', chars: rangeChars(0x0500, 0x052F) },
        { name: 'Unicode: Hebrew (0590-05FF)', chars: rangeChars(0x0590, 0x05FF) },
        { name: 'Unicode: Arabic (0600-06FF)', chars: rangeChars(0x0600, 0x06FF) },
        { name: 'Unicode: Arabic Supplement (0750-077F)', chars: rangeChars(0x0750, 0x077F) },
        { name: 'Unicode: Devanagari (0900-097F)', chars: rangeChars(0x0900, 0x097F) },
        { name: 'Unicode: CJK Symbols and Punctuation (3000-303F)', chars: rangeChars(0x3000, 0x303F) },
        { name: 'Unicode: Hiragana (3040-309F)', chars: rangeChars(0x3040, 0x309F) },
        { name: 'Unicode: Katakana (30A0-30FF)', chars: rangeChars(0x30A0, 0x30FF) },
        { name: 'Unicode: Hangul Compatibility Jamo (3130-318F)', chars: rangeChars(0x3130, 0x318F) },
        { name: 'Unicode: Enclosed CJK Letters and Months (3200-32FF)', chars: rangeChars(0x3200, 0x32FF) },
        { name: 'Unicode: General Punctuation (2000-206F)', chars: rangeChars(0x2000, 0x206F) },
        { name: 'Unicode: Superscripts & Subscripts (2070-209F)', chars: rangeChars(0x2070, 0x209F) },
        { name: 'Unicode: Currency Symbols (20A0-20BF)', chars: rangeChars(0x20A0, 0x20BF) },
        { name: 'Unicode: Letterlike Symbols (2100-214F)', chars: rangeChars(0x2100, 0x214F) },
        { name: 'Unicode: Number Forms (2150-218F)', chars: rangeChars(0x2150, 0x218F) },
        { name: 'Unicode: Arrows (2190-21FF)', chars: rangeChars(0x2190, 0x21FF) },
        { name: 'Unicode: Mathematical Operators (2200-22FF)', chars: rangeChars(0x2200, 0x22FF) },
        { name: 'Unicode: Misc Technical (2300-23FF)', chars: rangeChars(0x2300, 0x23FF) },
        { name: 'Unicode: Control Pictures (2400-243F)', chars: rangeChars(0x2400, 0x243F) },
        { name: 'Unicode: OCR (2440-245F)', chars: rangeChars(0x2440, 0x245F) },
        { name: 'Unicode: Enclosed Alphanumerics (2460-24FF)', chars: rangeChars(0x2460, 0x24FF) },
        { name: 'Unicode: Box Drawing (2500-257F)', chars: rangeChars(0x2500, 0x257F) },
        { name: 'Unicode: Block Elements (2580-259F)', chars: rangeChars(0x2580, 0x259F) },
        { name: 'Unicode: Geometric Shapes (25A0-25FF)', chars: rangeChars(0x25A0, 0x25FF) },
        { name: 'Unicode: Miscellaneous Symbols (2600-26FF)', chars: rangeChars(0x2600, 0x26FF) },
        { name: 'Unicode: Dingbats (2700-27BF)', chars: rangeChars(0x2700, 0x27BF) },
        { name: 'Unicode: Misc Math Symbols-A (27C0-27EF)', chars: rangeChars(0x27C0, 0x27EF) },
        { name: 'Unicode: Supplemental Arrows-A (27F0-27FF)', chars: rangeChars(0x27F0, 0x27FF) },
        { name: 'Unicode: Misc Symbols & Arrows (2B00-2BFF)', chars: rangeChars(0x2B00, 0x2BFF) },
        { name: 'Unicode: Supplemental Math Operators (2A00-2AFF)', chars: rangeChars(0x2A00, 0x2AFF) },
        { name: 'Unicode: Misc Math Symbols-B (2980-29FF)', chars: rangeChars(0x2980, 0x29FF) }
    ];

    var lastTarget = null;
    var panelId = 'sc-panel';
    var toggleId = 'sc-toggle-button';
    var styleId = 'sc-style';
    var lastNoTargetAt = 0;

    function addEvent(el, type, handler, capture) {
        if (!el) { return; }
        if (el.addEventListener) {
            el.addEventListener(type, handler, !!capture);
        } else if (el.attachEvent) {
            el.attachEvent('on' + type, handler);
        }
    }

    function isWithinSpecialPanel(el) {
        while (el) {
            if (el.id === panelId || el.id === toggleId) {
                return true;
            }
            el = el.parentNode;
        }
        return false;
    }

    function isEditableField(el) {
        if (!el) { return false; }
        var tag = el.tagName ? el.tagName.toLowerCase() : '';
        if (tag === 'textarea') { return true; }
        if (tag === 'input') {
            var type = (el.type || '').toLowerCase();
            if (!type || type === 'text' || type === 'search' ||
                type === 'email' || type === 'url' || type === 'tel' ||
                type === 'number' || type === 'password') {
                return true;
            }
        }
        if (el.isContentEditable) { return true; }
        return false;
    }

    function getCurrentEditableTarget() {
        var ae = document.activeElement;
        if (ae && !isWithinSpecialPanel(ae) && isEditableField(ae)) {
            return ae;
        }
        var sel = window.getSelection && window.getSelection();
        if (sel && sel.rangeCount) {
            var n = sel.anchorNode || (sel.getRangeAt(0) && sel.getRangeAt(0).startContainer);
            if (n && n.nodeType === 3) { n = n.parentNode; }
            while (n) {
                if (n.isContentEditable) { return n; }
                n = n.parentNode;
            }
        }
        if (lastTarget && isEditableField(lastTarget)) {
            return lastTarget;
        }
        return null;
    }

    function signalNoTarget() {
        var now = +new Date();
        if (now - lastNoTargetAt < 800) { return; }
        lastNoTargetAt = now;

        var tb = document.getElementById(toggleId);
        if (tb) {
            var oldBg = tb.style.backgroundColor;
            var oldOpacity = tb.style.opacity;
            tb.style.backgroundColor = '#b00000';
            tb.style.opacity = '1';
            setTimeout(function () {
                tb.style.backgroundColor = oldBg;
                tb.style.opacity = oldOpacity;
            }, 450);
        }

        if (window.navigator && navigator.vibrate) {
            try { navigator.vibrate(80); } catch (e) {}
        }

        alert('Click into a text field first (cursor not placed).');
    }

    function insertTextAtCursor(el, text) {
        if (!el || !text) { return; }

        if (el.isContentEditable) {
            el.focus();
            var sel = window.getSelection && window.getSelection();
            if (sel && sel.getRangeAt && sel.rangeCount) {
                var range = sel.getRangeAt(0);
                range.deleteContents();
                var node = document.createTextNode(text);
                range.insertNode(node);
                range.setStartAfter(node);
                range.setEndAfter(node);
                sel.removeAllRanges();
                sel.addRange(range);
            } else if (document.selection && document.selection.createRange) {
                var ieRange = document.selection.createRange();
                ieRange.text = text;
                ieRange.collapse(false);
                ieRange.select();
            }
            return;
        }

        var value = el.value;
        if (typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number') {
            var start = el.selectionStart;
            var end = el.selectionEnd;
            el.value = value.slice(0, start) + text + value.slice(end);
            var newPos = start + text.length;
            el.selectionStart = el.selectionEnd = newPos;
            el.focus();
        } else if (document.selection && el.tagName && el.tagName.toLowerCase() === 'textarea') {
            el.focus();
            var range2 = document.selection.createRange();
            range2.text = text;
            range2.collapse(false);
            range2.select();
        } else {
            el.value = value + text;
            el.focus();
        }
    }

    function createStyle() {
        if (document.getElementById(styleId)) { return; }

        var css = '' +
            '#' + toggleId + ' {' +
            ' position:fixed;' +
            ' top:8px;' +
            ' right:8px;' +
            ' z-index:99999;' +
            ' width:34px;' +
            ' height:34px;' +
            ' border-radius:50%;' +
            ' padding:0;' +
            ' border:none;' +
            ' background:#000000;' +
            ' color:#ffffff;' +
            ' font-size:20px;' +
            ' font-weight:bold;' +
            ' display:flex;' +
            ' align-items:center;' +
            ' justify-content:center;' +
            ' cursor:pointer;' +
            ' opacity:0.75;' +
            ' box-shadow:0 2px 6px rgba(0,0,0,0.3);' +
            ' transition:opacity 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;' +
            ' }' +
            '#' + toggleId + ':hover {' +
            ' opacity:1;' +
            ' transform:scale(1.08);' +
            ' box-shadow:0 4px 10px rgba(0,0,0,0.4);' +
            ' }' +
            '#' + panelId + ' {' +
            ' position:fixed;' +
            ' top:50px;' +
            ' right:8px;' +
            ' z-index:99998;' +
            ' background:#ffffff;' +
            ' border:1px solid #999;' +
            ' border-radius:6px;' +
            ' padding:6px 8px 8px 8px;' +
            ' max-width:520px;' +
            ' max-height:75vh;' +
            ' overflow:auto;' +
            ' box-shadow:0 4px 12px rgba(0,0,0,0.25);' +
            ' font-family:Arial,Helvetica,sans-serif;' +
            ' font-size:14px;' +
            ' display:none;' +
            ' }' +
            '#' + panelId + ' .sc-header {' +
            ' display:flex;' +
            ' justify-content:space-between;' +
            ' align-items:center;' +
            ' margin-bottom:6px;' +
            ' font-weight:bold;' +
            ' font-size:13px;' +
            ' }' +
            '#' + panelId + ' .sc-close {' +
            ' cursor:pointer;' +
            ' border:none;' +
            ' background:transparent;' +
            ' font-size:16px;' +
            ' padding:0 4px;' +
            ' }' +
            '#' + panelId + ' .sc-group {' +
            ' margin-bottom:6px;' +
            ' }' +
            '#' + panelId + ' .sc-group-title {' +
            ' font-size:12px;' +
            ' margin:3px 0;' +
            ' color:#555;' +
            ' }' +
            '#' + panelId + ' .sc-charset {' +
            ' display:flex;' +
            ' flex-wrap:wrap;' +
            ' gap:3px;' +
            ' }' +
            '#' + panelId + ' .sc-char-btn {' +
            ' min-width:28px;' +
            ' padding:3px 5px;' +
            ' text-align:center;' +
            ' border:1px solid #ddd;' +
            ' background:#fafafa;' +
            ' cursor:pointer;' +
            ' border-radius:3px;' +
            ' user-select:none;' +
            ' -webkit-user-select:none;' +
            ' font-size:16px;' +
            ' line-height:1.3;' +
            ' }' +
            '#' + panelId + ' .sc-char-btn:hover {' +
            ' background:#e6f0ff;' +
            ' border-color:#99b;' +
            ' }';

        var style = document.createElement('style');
        style.type = 'text/css';
        style.id = styleId;

        if (style.styleSheet) {
            style.styleSheet.cssText = css;
        } else {
            style.appendChild(document.createTextNode(css));
        }

        var head = document.getElementsByTagName('head')[0] || document.documentElement;
        head.appendChild(style);
    }

    function createPanel() {
        if (document.getElementById(panelId)) { return; }

        var panel = document.createElement('div');
        panel.id = panelId;

        var header = document.createElement('div');
        header.className = 'sc-header';

        var title = document.createElement('span');
        title.appendChild(document.createTextNode('Special characters'));

        var closeBtn = document.createElement('button');
        closeBtn.type = 'button';
        closeBtn.className = 'sc-close';
        closeBtn.appendChild(document.createTextNode('\u00D7'));

        header.appendChild(title);
        header.appendChild(closeBtn);
        panel.appendChild(header);

        for (var i = 0; i < scCharGroups.length; i++) {
            var g = scCharGroups[i];
            var group = document.createElement('div');
            group.className = 'sc-group';

            var groupTitle = document.createElement('div');
            groupTitle.className = 'sc-group-title';
            groupTitle.appendChild(document.createTextNode(g.name));
            group.appendChild(groupTitle);

            var charset = document.createElement('div');
            charset.className = 'sc-charset';

            for (var j = 0; j < g.chars.length; j++) {
                var item = g.chars[j];
                var chVal, label, titleText;

                if (typeof item === 'string') {
                    chVal = item;
                    label = item;
                    titleText = '';
                } else {
                    chVal = item.c;
                    label = item.label || item.c;
                    titleText = item.title || '';
                }

                var btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'sc-char-btn';
                btn.setAttribute('data-char', chVal);
                btn.appendChild(document.createTextNode(label));

                if (titleText) {
                    btn.title = titleText;
                }

                charset.appendChild(btn);
            }

            group.appendChild(charset);
            panel.appendChild(group);
        }

        document.body.appendChild(panel);

        addEvent(closeBtn, 'click', function () {
            panel.style.display = 'none';
        });

        addEvent(panel, 'click', function (evt) {
            evt = evt || window.event;
            var target = evt.target || evt.srcElement;
            if (!target) { return; }

            if (target.className && target.className.indexOf('sc-char-btn') !== -1) {
                var ch = target.getAttribute('data-char');
                var tgt = getCurrentEditableTarget();
                if (!tgt) {
                    signalNoTarget();
                    return;
                }
                lastTarget = tgt;
                insertTextAtCursor(tgt, ch);
            }
        });
    }

    function createToggleButton() {
        if (document.getElementById(toggleId)) { return; }

        var btn = document.createElement('button');
        btn.type = 'button';
        btn.id = toggleId;
        btn.appendChild(document.createTextNode('\u03A9'));
        btn.title = 'Insert special characters';

        document.body.appendChild(btn);

        addEvent(btn, 'click', function () {
            var panel = document.getElementById(panelId);
            if (!panel) { return; }
            panel.style.display = (panel.style.display === 'none' || panel.style.display === '') ? 'block' : 'none';
        });
    }

    function initFocusTracking() {
        var handler = function (evt) {
            evt = evt || window.event;
            var target = evt.target || evt.srcElement;
            if (!target) { return; }

            if (isWithinSpecialPanel(target)) {
                return;
            }

            if (isEditableField(target)) {
                lastTarget = target;
            }
        };

        if (document.addEventListener) {
            document.addEventListener('focus', handler, true);
        } else if (document.attachEvent) {
            document.attachEvent('onfocusin', handler);
        }
    }

    function init() {
        if (document.getElementById(toggleId)) {
            return;
        }

        createStyle();
        createPanel();
        createToggleButton();
        initFocusTracking();
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        init();
    } else {
        addEvent(document, 'DOMContentLoaded', init, false);
        addEvent(window, 'load', init, false);
    }
})();
 