"use strict";

var J_UTL = 1;

if (!window.console || !console.log)
    window.console = {
        "log": function (msg) { }
    };

if (!Object.assign)
    Object.assign = function (a, b) {
        for (var k in b)
            a[k] = b[k];
    };

if (window.CGV1)
    Object.assign(window, window.CGV1);

if (window.CGV)
    Object.assign(window, window.CGV);


var wah = '',
    STG = null,
    NOAC = 'autocorrect="off" autocapitalize="off"',
    L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
    T0 = Date.now(),
    R = SR.slice(1),
    RS = R ? "/" + R : "",
    HALFMAX = 8192 * 8192 * 8192 * 8192,
    HTTPS = ('' + location).indexOf('https:') === 0,
    TOUCH = 'ontouchstart' in window,
    MOBILE = TOUCH,
    CHROME = !!window.chrome,  // safari=false
    VCHROME = CHROME ? 1 : 0,
    UA = '' + navigator.userAgent,
    IE = !!document.documentMode,
    FIREFOX = ('netscape' in window) && / rv:/.test(UA),
    IPHONE = TOUCH && /iPhone|iPad|iPod/i.test(UA),
    LINUX = /Linux/.test(UA),
    MACOS = /Macintosh/.test(UA),
    WINDOWS = /Windows/.test(UA),
    APPLE = IPHONE || MACOS,
    APPLEM = TOUCH && APPLE;

if (!window.WebAssembly || !WebAssembly.Memory)
    window.WebAssembly = false;

if (!window.Notification || !Notification.permission)
    window.Notification = false;

if (!window.FormData)
    window.FormData = false;

try {
    STG = window.localStorage;
    STG.STG;
}
catch (ex) {
    STG = null;
    if ((ex + '').indexOf('sandbox') < 0)
        console.log('no localStorage: ' + ex);
}

try {
    if (navigator.userAgentData.mobile)
        MOBILE = true;

    if (navigator.userAgentData.platform == 'Windows')
        WINDOWS = true;

    CHROME = navigator.userAgentData.brands.find(function (d) { return d.brand == 'Chromium' });
    if (CHROME)
        VCHROME = parseInt(CHROME.version);
    else
        VCHROME = 0;

    CHROME = !!CHROME;
}
catch (ex) { }


var ebi = document.getElementById.bind(document),
    QS = document.querySelector.bind(document),
    QSA = document.querySelectorAll.bind(document),
    XHR = XMLHttpRequest;


function mknod(et, eid, html) {
    var ret = document.createElement(et);

    if (eid)
        ret.id = eid;

    if (html)
        ret.innerHTML = html;

    return ret;
}


function qsr(sel) {
    var el = QS(sel);
    if (el)
        el.parentNode.removeChild(el);

    return el;
}


// error handler for mobile devices
function esc(txt) {
    return txt.replace(/[&"<>]/g, function (c) {
        return {
            '&': '&amp;',
            '"': '&quot;',
            '<': '&lt;',
            '>': '&gt;'
        }[c];
    });
}
function basenames(txt) {
    return (txt + '').replace(/https?:\/\/[^ \/]+\//g, '/').replace(/js\?_=[a-zA-Z]{4}/g, 'js');
}
if ((location + '').indexOf(',rej,') + 1)
    window.onunhandledrejection = function (e) {
        var err = e.reason;
        try {
            err += '\n' + e.reason.stack;
        }
        catch (e) { }
        err = basenames(err);
        console.log("REJ: " + err);
        try {
            toast.warn(30, err);
        }
        catch (e) { }
    };

try {
    console.hist = [];
    var CMAXHIST = MOBILE ? 9000 : 44000;
    var hook = function (t) {
        var orig = console[t].bind(console),
            cfun = function () {
                console.hist.push(Date.now() + ' ' + t + ': ' + Array.from(arguments).join(', '));
                if (console.hist.length > CMAXHIST)
                    console.hist = console.hist.slice(CMAXHIST / 4);

                orig.apply(console, arguments);
            };

        console['std' + t] = orig;
        console[t] = cfun;
    };
    hook('log');
    console.log('log-capture ok');
    hook('debug');
    hook('warn');
    hook('error');
}
catch (ex) {
    if (console.stdlog)
        console.log = console.stdlog;
    console.log('console capture failed', ex);
}
var crashed = false, ignexd = {}, evalex_fatal = false;
function vis_exh(msg, url, lineNo, columnNo, error) {
    var ekey = url + '\n' + lineNo + '\n' + msg;
    if (ignexd[ekey] || crashed)
        return;

    msg = String(msg);
    url = String(url);

    if (msg.indexOf('ResizeObserver') + 1)
        return;  // chrome issue 809574 (benign, from <video>)

    if (msg.indexOf('l2d.js') + 1)
        return;  // `t` undefined in tapEvent -> hitTestSimpleCustom

    if (!/\.js($|\?)/.exec(url))
        return;  // chrome debugger

    if (url.indexOf('extension://') + 1)
        return;

    if (url.indexOf(' > eval') + 1 && !evalex_fatal)
        return;  // md timer

    if (url.indexOf('prism.js') + 1)
        return;

    if (url.indexOf('easymde.js') + 1)
        return;  // clicking the preview pane

    if (url.indexOf('deps/marked.js') + 1 && !WebAssembly)
        return; // ff<52

    crashed = true;
    window.onerror = undefined;
    var html = [
        '<h1>you hit a bug!</h1>',
        '<p style="font-size:1.3em;margin:0;line-height:2em">try to <a href="#" id="exh_wipecfg">reset copyparty settings</a> if you are stuck here, or <a href="#" id="exh_ignex">ignore this</a> / <a href="#" id="exh_ignexa">ignore all</a> / <a href="?b=u">basic</a></p>',
        '<p style="color:#fff">please send me a screenshot arigathanks gozaimuch: <a href="<ghi>" target="_blank">new github issue</a></p>',
        '<p class="b">' + esc(url + ' @' + lineNo + ':' + columnNo), '<br />' + esc(msg).replace(/\n/g, '<br />') + '</p>',
        '<p><b>UA:</b> ' + esc(UA)
    ];

    try {
        var ua = '',
            ad = navigator.userAgentData,
            adb = ad.brands;

        for (var a = 0; a < adb.length; a++)
            if (!/Not.*A.*Brand/.exec(adb[a].brand))
                ua += adb[a].brand + '/' + adb[a].version + ', ';
        ua += ad.platform;

        html.push('<br /><b>UAD:</b> ' + esc(ua.slice(0, 100)));
    }
    catch (e) { }
    html.push('</p>');

    try {
        if (error) {
            var find = ['desc', 'stack', 'trace'];
            for (var a = 0; a < find.length; a++)
                if (String(error[find[a]]) !== 'undefined')
                    html.push('<p class="b"><b>' + find[a] + ':</b><br />' +
                        esc(String(error[find[a]])).replace(/\n/g, '<br />\n') + '</p>');
        }
        ignexd[ekey] = true;

        var ls = {},
            lsk = Object.keys(localStorage),
            nka = lsk.length,
            nk = Math.min(200, nka);

        for (var a = 0; a < nk; a++) {
            var k = lsk[a],
                v = localStorage.getItem(k);

            ls[k] = v.length > 256 ? v.slice(0, 32) + '[...' + v.length + 'b]' : v;
        }

        lsk = Object.keys(ls);
        lsk.sort();
        html.push('<p class="b"><b>' + nka + ':&nbsp;</b>');
        for (var a = 0; a < nk; a++)
            html.push(' <b>' + esc(lsk[a]) + '</b> <code>' + esc(ls[lsk[a]]) + '</code> ');

        html.push('</p>');
    }
    catch (e) { }

    if (console.hist.length) {
        html.push('<p class="b"><b>console:</b><ul><li>' + Date.now() + ' @</li>');
        for (var a = console.hist.length - 1, aa = Math.max(0, console.hist.length - 20); a >= aa; a--)
            html.push('<li>' + esc(console.hist[a]) + '</li>');
        html.push('</ul>')
    }

    try {
        var exbox = ebi('exbox');
        if (!exbox) {
            exbox = mknod('div', 'exbox');
            document.body.appendChild(exbox);

            var s = mknod('style');
            s.innerHTML = (
                '#exbox{background:#222;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em;z-index:80386;position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;overflow:auto;width:calc(100% - 2em)} ' +
                '#exbox,#exbox *{line-height:1.5em;overflow-wrap:break-word} ' +
                '#exbox code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} ' +
                '#exbox a{text-decoration:underline;color:#fc0;background:#222;border:none} ' +
                '#exbox h1{margin:.5em 1em 0 0;padding:0} ' +
                '#exbox p.b{border-top:1px solid #999;margin:1em 0 0 0;font-size:1em} ' +
                '#exbox ul, #exbox li {margin:0 0 0 .5em;padding:0} ' +
                '#exbox b{color:#fff}'
            );
            document.head.appendChild(s);
        }
        exbox.innerHTML = basenames(html.join('\n')).replace(/<ghi>/, 'https://github.com/9001/copyparty/issues/new?labels=bug&template=bug_report.md');
        exbox.style.display = 'block';
    }
    catch (e) {
        document.body.innerHTML = html.join('\n');
    }
    var x = ebi('exh_wipecfg');
    if (x) x.onclick = function () {
        localStorage.clear();
        location.reload();
    };
    x = ebi('exh_ignex'); if (x) x.onclick = ignex;
    x = ebi('exh_ignexa'); if (x) x.onclick = ignexa;
}
function ignexa() {
    var o = ebi('exbox');
    o.style.display = 'none';
    o.innerHTML = '';
    crashed = false;
}
function ignex() {
    ignexa();
    window.onerror = vis_exh;
}
window.onerror = vis_exh;


if (!window.Ls || !window.langmod)
    var Ls = {};


function noop() { }


function ctrl(e) {
    return e && (e.ctrlKey || e.metaKey);
}


function anymod(e, shift_ok) {
    return e && (e.ctrlKey || e.altKey || e.metaKey || e.isComposing || (!shift_ok && e.shiftKey));
}


var dev_fbw = sread('dev_fbw');
function ev(e) {
    if (!e && window.event) {
        e = window.event;
        if (dev_fbw == 1) {
            toast.warn(10, 'hello from fallback code ;_;\ncheck console trace');
            console.error('using window.event');
        }
    }
    if (!e)
        return;

    if (e.preventDefault)
        e.preventDefault();

    if (e.stopPropagation)
        e.stopPropagation();

    if (e.stopImmediatePropagation)
        e.stopImmediatePropagation();

    e.returnValue = false;
    return e;
}


function noope(e) {
    try { ev(e); } catch (ex) { }
}


// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
if (!String.prototype.endsWith)
    String.prototype.endsWith = function (search, this_len) {
        if (this_len === undefined || this_len > this.length) {
            this_len = this.length;
        }
        return this.substring(this_len - search.length, this_len) === search;
    };

if (!String.prototype.startsWith)
    String.prototype.startsWith = function (s, i) {
        i = i > 0 ? i | 0 : 0;
        return this.substring(i, i + s.length) === s;
    };

if (!String.prototype.trimEnd)
    String.prototype.trimEnd = String.prototype.trimRight = function () {
        return this.replace(/[ \t\r\n]+$/, '');
    };

if (!Element.prototype.matches)
    Element.prototype.matches =
        Element.prototype.oMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.webkitMatchesSelector;

var CLOSEST = !!Element.prototype.closest;
if (!CLOSEST)
    Element.prototype.closest = function (s) {
        var el = this;
        do {
            if (el.matches(s)) return el;
            el = el.parentElement || el.parentNode;
        } while (el !== null && el.nodeType === 1);
    };

if (!String.prototype.format)
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] != 'undefined' ?
                args[number] : match;
        });
    };

var have_URL = false;
try {
    new URL('/a/', 'https://a.com/');
    have_URL = true;
}
catch (ex) {
    console.log('ie11 shim URL()');
    window.URL = function (url, base) {
        if (url.indexOf('//') < 0)
            url = base + '/' + url.replace(/^\/?/, '');
        else if (url.indexOf('//') == 0)
            url = 'https:' + url;

        var x = url.split('?');
        return {
            "pathname": '/' + x[0].split('://')[1].replace(/[^/]+\//, ''),
            "search": x.length > 1 ? x[1] : ''
        };
    }
}

if (!window.Set)
    window.Set = function () {
        var r = this;
        r.size = 0;
        r.d = {};
        r.add = function (k) {
            if (!r.d[k]) {
                r.d[k] = 1;
                r.size++;
            }
        };
        r.has = function (k) {
            return r.d[k];
        };
    };

// https://stackoverflow.com/a/950146
function import_js(url, cb, ecb) {
    var head = document.head || document.getElementsByTagName('head')[0];
    var script = mknod('script');
    script.type = 'text/javascript';
    if (window.JS_NONCE)
        script.nonce = JS_NONCE;
    script.src = url + '?_=' + (window.TS || 'a');
    script.onload = cb;
    script.onerror = ecb || function () {
        var m = 'Failed to load module:\n' + url;
        console.log(m);
        toast.err(0, m);
    };
    head.appendChild(script);
}


function unsmart(txt) {
    return !APPLEM ? txt : (txt.
        replace(/[\u2014]/g, "--").
        replace(/[\u2022]/g, "*").
        replace(/[\u2018\u2019]/g, "'").
        replace(/[\u201c\u201d]/g, '"'));
}


function namesan(txt, win, fslash) {
    if (win)
        txt = (txt.
            replace(/</g, "＜").
            replace(/>/g, "＞").
            replace(/:/g, "：").
            replace(/"/g, "＂").
            replace(/\\/g, "＼").
            replace(/\|/g, "｜").
            replace(/\?/g, "？").
            replace(/\*/g, "＊"));

    if (fslash)
        txt = txt.replace(/\//g, "／");

    return txt;
}


var NATSORT, ENATSORT;
try {
    NATSORT = new Intl.Collator([], {numeric: true});
}
catch (ex) { }


var crctab = (function () {
    var c, tab = [];
    for (var n = 0; n < 256; n++) {
        c = n;
        for (var k = 0; k < 8; k++) {
            c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
        }
        tab[n] = c;
    }
    return tab;
})();


function crc32(str) {
    var crc = 0 ^ (-1);
    for (var i = 0; i < str.length; i++) {
        crc = (crc >>> 8) ^ crctab[(crc ^ str.charCodeAt(i)) & 0xFF];
    }
    return ((crc ^ (-1)) >>> 0).toString(16);
}


function randstr(len) {
    var ret = '';
    try {
        var ar = new Uint32Array(Math.floor((len + 3) / 4));
        crypto.getRandomValues(ar);
        for (var a = 0; a < ar.length; a++)
            ret += ('000' + ar[a].toString(36)).slice(-4);
        return ret.slice(0, len);
    }
    catch (ex) {
        console.log('using unsafe randstr because ' + ex);
        while (ret.length < len)
            ret += ('000' + Math.floor(Math.random() * 1679616).toString(36)).slice(-4);
        return ret.slice(0, len);
    }
}


function clmod(el, cls, add) {
    if (!el)
        return false;

    if (el.classList) {
        var have = el.classList.contains(cls);
        if (add == 't')
            add = !have;

        if (!add == !have)
            return false;

        el.classList[add ? 'add' : 'remove'](cls);
        return true;
    }

    var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g'),
        n1 = el.className;

    if (add == 't')
        add = !re.test(n1);

    var n2 = n1.replace(re, ' ') + (add ? ' ' + cls : '');

    if (n1 == n2)
        return false;

    el.className = n2;
    return true;
}


function clgot(el, cls) {
    if (!el)
        return;

    if (el.classList)
        return el.classList.contains(cls);

    var lst = (el.className + '').split(/ /g);
    return has(lst, cls);
}


function setcvar(k, v) {
    try {
        document.documentElement.style.setProperty(k, v);
    }
    catch (e) { }
}


var ANIM = true;
try {
    var mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    mq.onchange = function () {
        ANIM = !mq.matches;
    };
    ANIM = !mq.matches;
}
catch (ex) { }


function yscroll() {
    if (document.documentElement.scrollTop) {
        return (window.yscroll = function () {
            return document.documentElement.scrollTop;
        })();
    }
    if (window.pageYOffset) {
        return (window.yscroll = function () {
            return window.pageYOffset;
        })();
    }
    return 0;
}
function xscroll() {
    if (document.documentElement.scrollLeft) {
        return (window.xscroll = function () {
            return document.documentElement.scrollLeft;
        })();
    }
    if (window.pageXOffset) {
        return (window.xscroll = function () {
            return window.pageXOffset;
        })();
    }
    return 0;
}


function showsort(tab) {
    var v, vn, v1, v2, th = tab.tHead,
        sopts = jread('fsort');

    sopts = sopts && sopts.length ? sopts : dsort;

    th && (th = th.rows[0]) && (th = th.cells);

    for (var a = sopts.length - 1; a >= 0; a--) {
        if (!sopts[a][0])
            continue;

        v2 = v1;
        v1 = sopts[a];
    }

    v = [v1, v2];
    vn = [v1 ? v1[0] : '', v2 ? v2[0] : ''];

    var ga = QSA('#ghead a[s]');
    for (var a = 0; a < ga.length; a++)
        ga[a].className = '';

    for (var a = 0; a < th.length; a++) {
        var n = vn.indexOf(th[a].getAttribute('name')),
            cl = n < 0 ? ' ' : ' s' + n + (v[n][1] > 0 ? ' ' : 'r ');

        th[a].className = th[a].className.replace(/ *s[01]r? */, ' ') + cl;
        if (n + 1) {
            ga = QS('#ghead a[s="' + vn[n] + '"]');
            if (ga)
                ga.className = cl;
        }
    }
}
function st_cmp_num(a, b) {
    a = a[0];
    b = b[0];
    return (
        a === null ? -1 :
        b === null ? 1 :
        (a - b)
    );
}
function st_cmp_nat(a, b) {
    a = a[0];
    b = b[0];
    return (
        a === null ? -1 :
        b === null ? 1 :
        NATSORT.compare(a, b)
    );
}
function st_cmp_gen(a, b) {
    a = a[0];
    b = b[0];
    return (
        a === null ? -1 :
        b === null ? 1 :
        a.localeCompare(b)
    );
}
function sortTable(table, col, cb) {
    var tb = table.tBodies[0],
        th = table.tHead.rows[0].cells,
        tr = Array.prototype.slice.call(tb.rows, 0),
        i, reverse = /s0[^r]/.exec(th[col].className + ' ') ? -1 : 1;

    var kname = th[col].getAttribute('name'),
        stype = th[col].getAttribute('sort');
    try {
        var nrules = [],
            rules = kname == 'href' ? [] : jread("fsort", []);

        rules.unshift([kname, reverse, stype || '']);
        for (var a = 0; a < rules.length; a++) {
            var add = true;
            for (var b = 0; b < a; b++)
                if (rules[a][0] == rules[b][0])
                    add = false;

            if (add)
                nrules.push(rules[a]);

            if (nrules.length >= 10)
                break;
        }
        jwrite("fsort", nrules);
        try { showsort(table); } catch (ex) { }
    }
    catch (ex) {
        console.log("failed to persist sort rules, resetting: " + ex);
        jwrite("fsort", null);
    }
    var vl = [];
    for (var a = 0; a < tr.length; a++) {
        var cell = tr[a].cells[col];
        if (!cell) {
            vl.push([null, a]);
            continue;
        }
        var v = cell.getAttribute('sortv') || cell.textContent.trim();
        if (stype == 'int') {
            v = parseInt(v.replace(/[, ]/g, '')) || 0;
        }
        vl.push([v, a]);
    }

    if (stype == 'int')
        vl.sort(st_cmp_num);
    else if (ENATSORT)
        vl.sort(st_cmp_nat);
    else
        vl.sort(st_cmp_gen);

    if (reverse < 0)
        vl.reverse();

    if (sread('dir1st') !== '0') {
        var r1 = [], r2 = [];
        for (var i = 0; i < tr.length; i++) {
            var cell = tr[vl[i][1]].cells[1],
                href = cell.getAttribute('sortv') || cell.textContent.trim();

            (href.split('?')[0].slice(-1) == '/' ? r1 : r2).push(vl[i]);
        }
        vl = r1.concat(r2);
    }
    for (i = 0; i < tr.length; ++i) tb.appendChild(tr[vl[i][1]]);
    if (cb) cb();
}
function makeSortable(table, cb) {
    var th = table.tHead, i;
    th && (th = th.rows[0]) && (th = th.cells);
    if (th) i = th.length;
    else return; // if no `<thead>` then do nothing
    while (--i >= 0) (function (i) {
        th[i].onclick = function (e) {
            ev(e);
            sortTable(table, i, cb);
        };
    }(i));
}


function assert_vp(path) {
    if (path.indexOf('//') + 1)
        throw 'nonlocal1: ' + path;

    var o = location.origin;
    if (have_URL && (new URL(path, o)).origin != o)
        throw 'nonlocal2: ' + path;
}


function linksplit(rp, base, id) {
    var ret = [],
        apath = base || '/',
        q = null;

    if (rp && rp.indexOf('?') + 1) {
        q = rp.split('?', 2);
        rp = q[0];
        q = '?' + q[1];
    }

    if (rp && rp[0] == '/')
        rp = rp.slice(1);

    while (rp) {
        var link = rp;
        var ofs = rp.indexOf('/');
        if (ofs === -1) {
            rp = null;
        }
        else {
            link = rp.slice(0, ofs + 1);
            rp = rp.slice(ofs + 1);
        }
        var vlink = esc(uricom_dec(link));

        if (link.indexOf('/') !== -1)
            vlink = vlink.slice(0, -1);

        if (!rp) {
            if (q)
                link += q;

            if (id)
                link += '" id="' + id;
        }

        ret.push('<a href="' + apath + link + '">' + vlink + '</a>');
        apath += link;
    }
    return ret;
}


function vsplit(vp) {
    if (vp.endsWith('/'))
        vp = vp.slice(0, -1);

    var ofs = vp.lastIndexOf('/') + 1,
        base = vp.slice(0, ofs),
        fn = vp.slice(ofs);

    return [base, fn];
}


function vjoin(p1, p2) {
    if (!p1)
        p1 = '';

    if (!p2)
        p2 = '';

    if (p1.endsWith('/'))
        p1 = p1.slice(0, -1);

    if (p2.startsWith('/'))
        p2 = p2.slice(1);

    if (!p1)
        return p2;

    if (!p2)
        return p1;

    return p1 + '/' + p2;
}


function addq(url, q) {
    var uh = url.split('#', 1),
        u = uh[0],
        h = uh.length == 1 ? '' : '#' + uh[1];

    return u + (u.indexOf('?') < 0 ? '?' : '&') + (q === undefined ? '' : q) + h;
}


function uricom_enc(txt, do_fb_enc) {
    try {
        return encodeURIComponent(txt);
    }
    catch (ex) {
        console.log("uce-err [" + txt + "]");
        if (do_fb_enc)
            return esc(txt);

        return txt;
    }
}

function url_enc(txt) {
    var parts = txt.split('/'),
        ret = [];

    for (var a = 0; a < parts.length; a++)
        ret.push(uricom_enc(parts[a]));

    return ret.join('/');
}

function uri2txt(txt, unslash) {
    try {
      txt = decodeURIComponent(txt.split('?')[0]);
    }
    catch (ex) {
        console.log("ucd-err [" + txt + "]");
    }
    return unslash ? txt.replace(/\/$/, '') : txt;
}


function uricom_dec(txt) {
    try {
        return decodeURIComponent(txt);
    }
    catch (ex) {
        console.log("ucd-err [" + txt + "]");
        return txt;
    }
}


function uricom_sdec(txt) {
    try {
        return [decodeURIComponent(txt), true];
    }
    catch (ex) {
        console.log("ucd-err [" + txt + "]");
        return [txt, false];
    }
}


function uricom_adec(arr, li) {
    var ret = [];
    for (var a = 0; a < arr.length; a++) {
        var txt = uricom_dec(arr[a]);
        ret.push(li ? '<li>' + esc(txt) + '</li>' : txt);
    }

    return ret;
}


function get_evpath() {
    var ret = location.pathname;

    if (ret.indexOf('/') !== 0)
        ret = '/' + ret;

    if (ret.lastIndexOf('/') !== ret.length - 1)
        ret += '/';

    return ret;
}


function noq_href(el) {
    return el.getAttribute('href').split('?')[0];
}


function pad2(v) {
    return ('0' + v).slice(-2);
}


function unix2iso(ts) {
    return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
}


function unix2iso_localtime(ts) {
    var o = new Date(ts * 1000),
        p = pad2;
    return "{0}-{1}-{2} {3}:{4}:{5}".format(
        o.getFullYear(),
        p(o.getMonth() + 1),
        p(o.getDate()),
        p(o.getHours()),
        p(o.getMinutes()),
        p(o.getSeconds()));
}


function s2ms(s) {
    s = Math.floor(s);
    var m = Math.floor(s / 60);
    return m + ":" + ("0" + (s - m * 60)).slice(-2);
}


var isNum = function (v) {
    var n = parseFloat(v);
    return !isNaN(v - n) && n === v;
};
if (window.Number && Number.isFinite)
    isNum = Number.isFinite;


function f2f(val, nd) {
    // 10.toFixed(1) returns 10.00 for certain values of 10
    if (!isNum(val)) {
        val = parseFloat(val);
        if (!isNum(val))
            val = 999;
    }
    val = (val * Math.pow(10, nd)).toFixed(0).split('.')[0];
    return nd ? (val.slice(0, -nd) || '0') + '.' + val.slice(-nd) : val;
}


var HSZ_U = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
var HSZ_U2 = ['B', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi'];
var HSZ_UD = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
function humansize(b, tersity) {
    var i = 0;
    while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
    return (f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) +
        ' ' + (tersity ? HSZ_U[i].slice(0, tersity) : HSZ_U[i]));
}
function humansize_su(b) {
    var i = 0;
    while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
    return [b, HSZ_U2[i]];
}
function humansize_sud(b) {
    var i = 0;
    while (b >= 1000 && i < 5) { b /= 1000; i += 1; }
    return [b, HSZ_UD[i]];
}
function humansize_0(b) {
    return '' + b;
}
function humansize_1(b) {
    return ('' + b).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
function humansize_2g(b) {
    var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
    return [f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) + ' ' + u, u];
}
function humansize_3g(b) {
    var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
    return [f2f(b, b >= 10 ? 0 : 1) + ' ' + u, u];
}
function humansize_4g(b) {
    var z = humansize_su(b), u = z[1]; b = z[0];
    return [parseFloat(b.toFixed(b >= 100 ? 0 : b >= 10 ? 1 : 2)) + ' ' + u, u.charAt(0)];
}
function humansize_5g(b) {
    var z = humansize_su(b), u = z[1]; b = z[0];
    return [parseFloat(b.toFixed(b >= 10 ? 0 : 1)) + ' ' + u, u.charAt(0)];
}
function humansize_6g(b) {
    var z = humansize_sud(b), u = z[1]; b = z[0];
    return [parseFloat(b.toFixed(b >= 100 ? 0 : b >= 10 ? 1 : 2)) + ' ' + u, u.charAt(0)];
}
function humansize_7g(b) {
    var z = humansize_sud(b), u = z[1]; b = z[0];
    return [parseFloat(b.toFixed(b >= 10 ? 0 : 1)) + ' ' + u, u.charAt(0)];
}
function humansize_2(b) {
    return humansize_2g(b)[0];
}
function humansize_3(b) {
    return humansize_3g(b)[0];
}
function humansize_4(b) {
    return humansize_4g(b)[0];
}
function humansize_5(b) {
    return humansize_5g(b)[0];
}
function humansize_6(b) {
    return humansize_6g(b)[0];
}
function humansize_7(b) {
    return humansize_7g(b)[0];
}
function humansize_2c(b) {
    var v = humansize_2g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_3c(b) {
    var v = humansize_3g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_4c(b) {
    var v = humansize_4g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_5c(b) {
    var v = humansize_5g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_6c(b) {
    var v = humansize_6g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_7c(b) {
    var v = humansize_7g(b);
    return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_fuzzy(b) {
    if (b <= 0) return "yes";
	if (b <= 80) return "hullkort";
	if (b <= 368640) return "5¼ DD";
	if (b <= 1474560) return "save icon";
	if (b <= 2880000) return "3½ Extended";
	if (b <= 13107200) return "C90 Tape";
	if (b <= 21000000) return "Floptical";
	if (b <= 33554432) return "MPMan F10";
	if (b <= 50000000) return "creditcardCD";
	if (b <= 100663296) return "Zipdisk";
	if (b <= 170000000) return "MD";
	if (b <= 220200960) return "8cm CD";
	if (b <= 737280000) return "CD-R";
	if (b <= 900000000) return "UMD";
	if (b <= 1300000000) return "GD-ROM";
	if (b <= 4700000000) return "DVD";
	if (b <= 9400000000) return "DVD-DL";
	if (b <= 25025000000) return "BluRei";
	if (b <= 50050000000) return "BD-DL";
	return "LTO";
}
var humansize_fmts = ['0', '1', '2', '2c', '3', '3c', '4', '4c', '5', '5c', '6', '6c', '7', '7c', 'fuzzy'];
window.filesizefun = (function () {
    var v = sread('fszfmt', humansize_fmts);
    return window['humansize_' + (v || window.dfszf)] || humansize_1;
})();


function humantime(v) {
    if (v >= 60 * 60 * 24)
        return shumantime(v);

    try {
        return /.*(..:..:..).*/.exec(new Date(v * 1000).toUTCString())[1];
    }
    catch (ex) {
        return v;
    }
}


function shumantime(v, long) {
    if (v < 10)
        return f2f(v, 2) + 's';
    if (v < 60)
        return f2f(v, 1) + 's';

    v = parseInt(v);
    var st = [[60 * 60 * 24, 60 * 60, 'd'], [60 * 60, 60, 'h'], [60, 1, 'm']];

    for (var a = 0; a < st.length; a++) {
        var m1 = st[a][0],
            m2 = st[a][1],
            ch = st[a][2];

        if (v < m1)
            continue;

        var v1 = parseInt(v / m1),
            v2 = ('0' + parseInt((v % m1) / m2)).slice(-2);

        return v1 + ch + (v1 >= 10 || v2 == '00' ? '' : v2 + (
            long && a < st.length - 1 ? st[a + 1][2] : ''));
    }
}


function lhumantime(v) {
    var t = shumantime(v, 1);
    if (/[0-9]$/.exec(t))
        t += 's';

    var tp = t.replace(/([a-z])/g, " $1 ").split(/ /g).slice(0, -1);

    if (!L || tp.length < 2 || tp[1].indexOf('$') + 1)
        return t;

    var u, n, ret = '';
    for (var a = 0; a < tp.length; a += 2) {
        n = tp[a];
        u = L.ht_h5 ? (n==1 ? 1 : (n>1&&n<5) ? 2 : 5) :
            (n==1 ? 1 : 2);
        ret += tp[a] + ' ' + L['ht_' + tp[a + 1] + u] + L.ht_and;
    }

    return ret.slice(0, -L.ht_and.length);
}


function clamp(v, a, b) {
    return Math.min(Math.max(v, a), b);
}


function has(haystack, needle) {
    try { return haystack.includes(needle); } catch (ex) { }

    for (var a = 0; a < haystack.length; a++)
        if (haystack[a] == needle)
            return true;

    return false;
}


function apop(arr, v) {
    var ofs = arr.indexOf(v);
    if (ofs !== -1)
        arr.splice(ofs, 1);
}


function jcp1(obj) {
    return JSON.parse(JSON.stringify(obj));
}


function jcp2(src) {
    if (Array.isArray(src)) {
        var ret = [];
        for (var a = 0; a < src.length; ++a) {
            var sub = src[a];
            ret.push((sub === null) ? sub : (sub instanceof Date) ? new Date(sub.valueOf()) : (typeof sub === 'object') ? jcp2(sub) : sub);
        }
    } else {
        var ret = {};
        for (var key in src) {
            var sub = src[key];
            ret[key] = sub === null ? sub : (sub instanceof Date) ? new Date(sub.valueOf()) : (typeof sub === 'object') ? jcp2(sub) : sub;
        }
    }
    return ret;
};


// jcp1 50% faster on android-chrome, jcp2 7x everywhere else
var jcp = MOBILE && CHROME ? jcp1 : jcp2;


function sdrop(key) {
    try {
        STG.removeItem(key);
    }
    catch (ex) { }
}

function sread(key, al) {
    try {
        var ret = STG.getItem(key);
        return (!al || has(al, ret)) ? ret : null;
    }
    catch (e) {
        return null;
    }
}

function swrite(key, val) {
    try {
        if (val === undefined || val === null)
            STG.removeItem(key);
        else
            STG.setItem(key, val);
    }
    catch (e) { }
}

function jread(key, fb) {
    var str = sread(key);
    if (!str)
        return fb;

    try {
        // '' throws, null is ok, sasuga
        return JSON.parse(str);
    }
    catch (e) {
        return fb;
    }
}

function jwrite(key, val) {
    if (!val)
        swrite(key);
    else
        swrite(key, JSON.stringify(val));
}

function icfg_get(name, defval) {
    return parseInt(fcfg_get(name, defval));
}

function fcfg_get(name, defval) {
    var o = ebi(name),
        val = parseFloat(sread(name));

    if (!isNum(val))
        return parseFloat(o && o.value !== '' ? o.value : defval);

    if (o)
        o.value = val;

    return val;
}

function scfg_get(name, defval) {
    var o = ebi(name),
        val = sread(name);

    if (val === null)
        val = defval;

    if (o)
        o.value = val;

    return val;
}

function bcfg_get(name, defval) {
    var o = ebi(name);
    if (!o)
        return defval;

    var val = sread(name);
    if (val === null)
        val = defval;
    else
        val = (val == '1');

    bcfg_upd_ui(name, val);
    return val;
}

function bcfg_set(name, val) {
    swrite(name, val ? '1' : '0');
    bcfg_upd_ui(name, val);
    return val;
}

function bcfg_upd_ui(name, val) {
    var o = ebi(name);
    if (!o)
        return val;

    if (o.getAttribute('type') == 'checkbox')
        o.checked = val;
    else if (o) {
        clmod(o, 'on', val);
    }
    return val;
}

function bcfg_bind(obj, oname, cname, defval, cb, un_ev) {
    var v = bcfg_get(cname, defval),
        el = ebi(cname);

    obj[oname] = v;
    if (el)
        el.onclick = function (e) {
            if (un_ev !== false)
                ev(e);

            obj[oname] = bcfg_set(cname, !obj[oname]);
            if (cb)
                cb(obj[oname]);
        };

    return v;
}

function scfg_bind(obj, oname, cname, defval, cb) {
    var v = scfg_get(cname, defval),
        el = ebi(cname);

    obj[oname] = v;
    if (el)
        el.oninput = function (e) {
            swrite(cname, obj[oname] = this.value);
            if (cb)
                cb(obj[oname]);
        };

    return v;
}

function setck(v, cb) {
    var xhr = new XHR();
    xhr.open('GET', SR + '/?setck=' + v, true);
    xhr.onload = cb;
    xhr.send();
}

window.unix2ui = (function () {
    var v = sread('utctid');
    v = v ? (v === '0') : (window.dutc === false);
    return v ? unix2iso_localtime : unix2iso;
})();


function hist_push(url) {
    console.log("h-push " + url);
    try {
        history.pushState(url, url, url);
    }
    catch (ex) { }
}

function hist_replace(url) {
    console.log("h-repl " + url);
    try {
        history.replaceState(url, url, url);
    }
    catch (ex) { }  // ff "The operation is insecure." on rapid switches
}

function sethash(hv) {
    if (window.history && history.replaceState) {
        hist_replace(location.pathname + location.search + '#' + hv);
    }
    else {
        location.hash = hv;
    }
}


function dl_file(url) {
    console.log('DL [%s]', url);
    qsr('#dlfth');
    var o = mknod('a', 'dlfth');
    o.setAttribute('href', url);
    o.setAttribute('download', '');
    document.body.appendChild(o);
    ebi('dlfth').click();
    qsr('#dlfth');
}


function cliptxt(txt, ok) {
    var fb = function () {
        console.log('clip-fb');
        var o = mknod('textarea');
        o.value = txt;
        document.body.appendChild(o);
        o.focus();
        o.select();
        document.execCommand("copy");
        document.body.removeChild(o);
        ok();
    };
    try {
        if (!window.isSecureContext)
            throw 1;
        navigator.clipboard.writeText(txt).then(ok, fb);
    }
    catch (ex) { fb(); }
}


function Debounce(delay) {
    var r = this;
    r.delay = delay;
    r.timer = 0;
    r.t_hit = 0;
    r.t_run = 0;
    r.q = [];

    r.add = function (fun, run) {
        r.rm(fun);
        r.q.push(fun);

        if (run)
            fun();
    };

    r.rm = function (fun) {
        apop(r.q, fun);
    };

    r.run = function () {
        if (crashed)
            return;

        r.t_run = Date.now();

        var q = r.q.slice(0);
        for (var a = 0; a < q.length; a++)
            q[a]();
    };

    r.hit = function () {
        if (crashed)
            return;

        var now = Date.now(),
            td_hit = now - r.t_hit,
            td_run = now - r.t_run;

        if (td_run >= r.delay * 2)
            r.t_run = now;

        if (td_run >= r.delay && td_run <= r.delay * 2) {
            // r.delay is also deadline
            clearTimeout(r.timer);
            return r.run();
        }

        if (td_hit < r.delay / 5)
            return;

        clearTimeout(r.timer);
        r.timer = setTimeout(r.run, r.delay);
        r.t_hit = now;
    };
};

var onresize100 = new Debounce(100);
window.addEventListener('resize', onresize100.hit);


var timer = (function () {
    var r = {};
    r.q = [];
    r.last = 0;
    r.fs = 0;
    r.fc = 0;

    r.add = function (fun, run) {
        r.rm(fun);
        r.q.push(fun);

        if (run)
            fun();
    };

    r.rm = function (fun) {
        apop(r.q, fun);
    };

    var doevents = function () {
        if (crashed)
            return;

        if (Date.now() - r.last < 69)
            return;

        var q = r.q.slice(0);
        for (var a = 0; a < q.length; a++)
            q[a]();

        r.last = Date.now();
        //r.fc++; if (r.last - r.fs >= 2000) { console.log(r.last - r.fs, r.fc); r.fs = r.last; r.fc = 0; }
    }
    setInterval(doevents, 100);

    return r;
})();


var tt = (function () {
    var r = {
        "tt": mknod("div", 'tt'),
        "th": mknod("div", 'tth'),
        "en": !window.notooltips,
        "el": null,
        "skip": false,
        "lvis": 0
    };

    r.th.innerHTML = '?';
    document.body.appendChild(r.tt);
    document.body.appendChild(r.th);

    var prev = null;
    r.cshow = function () {
        if (this !== prev)
            r.show.call(this);

        prev = this;
    };

    var tev, vh;
    r.dshow = function (e) {
        clearTimeout(tev);
        if (!r.getmsg(this))
            return;

        if (Date.now() - r.lvis < 400)
            return r.show.call(this);

        tev = setTimeout(r.show.bind(this), 800);
        if (TOUCH)
            return;

        vh = window.innerHeight;
        this.addEventListener('mousemove', r.move);
        clmod(r.th, 'act', 1);
        r.move(e);
    };

    r.getmsg = function (el) {
        if (APPLEM && QS('body.bbox-open'))
            return;

        var cfg = sread('tooltips');
        if (cfg !== null && cfg != '1')
            return;

        return el.getAttribute('tt');
    };

    r.show = function () {
        clearTimeout(tev);
        if (r.skip) {
            r.skip = false;
            return;
        }
        var msg = r.getmsg(this);
        if (!msg)
            return;

        if (msg.startsWith('`')) {
            var x = false;
            msg = msg.slice(1);
            while (msg.indexOf('`') + 1)
                msg = msg.replace('`', (x = !x) ? '<code>' : '</code>')
        }

        r.el = this;
        var pos = this.getBoundingClientRect(),
            dir = this.getAttribute('ttd') || '',
            margin = parseFloat(this.getAttribute('ttm') || 0),
            top = pos.top < window.innerHeight / 2,
            big = this.className.indexOf(' ttb') !== -1;

        if (dir.indexOf('u') + 1) top = false;
        if (dir.indexOf('d') + 1) top = true;

        clmod(r.th, 'act');
        clmod(r.tt, 'b', big);
        r.tt.style.left = '0';
        r.tt.style.top = '0';

        r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
        r.el.addEventListener('mouseleave', r.hide);
        window.addEventListener('scroll', r.hide);
        clmod(r.tt, 'show', 1);

        var tw = r.tt.offsetWidth,
            x = pos.left + (pos.right - pos.left) / 2 - tw / 2;

        if (x + tw >= window.innerWidth - 24)
            x = window.innerWidth - tw - 24;

        if (x < 0)
            x = 12;

        r.tt.style.left = x + 'px';
        r.tt.style.top = top ? (margin + pos.bottom) + 'px' : 'auto';
        r.tt.style.bottom = top ? 'auto' : (margin + window.innerHeight - pos.top) + 'px';
    };

    r.hide = function (e) {
        //ev(e);  // eats checkbox-label clicks
        clearTimeout(tev);
        window.removeEventListener('scroll', r.hide);

        clmod(r.tt, 'b');
        clmod(r.th, 'act');
        if (clmod(r.tt, 'show'))
            r.lvis = Date.now();

        if (r.el)
            r.el.removeEventListener('mouseleave', r.hide);

        if (e && e.target)
            e.target.removeEventListener('mousemove', r.move);
    };

    r.move = function (e) {
        var sy = e.clientY + 128 > vh ? -1 : 1;
        r.th.style.left = (e.pageX + 12) + 'px';
        r.th.style.top = (e.pageY + 12 * sy) + 'px';
    };

    if (TOUCH) {
        var f1 = r.show,
            f2 = r.hide,
            q = [];

        // if an onclick-handler creates a new timer,
        // webkits delay the entire handler by up to 401ms,
        // win by using a shared timer instead

        timer.add(function () {
            while (q.length && Date.now() >= q[0][0])
                q.shift()[1]();
        });

        r.show = function () {
            q.push([Date.now() + 100, f1.bind(this)]);
        };
        r.hide = function () {
            q.push([Date.now() + 100, f2.bind(this)]);
        };
    }

    r.tt.onclick = r.hide;

    r.att = function (ctr) {
        var _cshow = r.en ? r.cshow : null,
            _dshow = r.en ? r.dshow : null,
            _hide = r.en ? r.hide : null,
            o = ctr.querySelectorAll('*[tt]');

        for (var a = o.length - 1; a >= 0; a--) {
            o[a].addEventListener('focus', _cshow);
            o[a].addEventListener('blur', _hide);
            o[a].addEventListener('mouseenter', _dshow);
            o[a].addEventListener('mouseleave', _hide);
        }
        r.hide();
    }

    r.init = function () {
        bcfg_bind(r, 'en', 'tooltips', r.en, r.init);
        r.att(document);
    };

    return r;
})();


function lf2br(txt) {
    var html = '', hp = txt.split(/(?=<.?pre>)/i);
    for (var a = 0; a < hp.length; a++)
        html += hp[a].startsWith('<pre>') ? hp[a] :
            hp[a].replace(/<br ?.?>\n/g, '\n').replace(/\n<br ?.?>/g, '\n').replace(/\n/g, '<br />\n');

    return html;
}


function hunpre(txt) {
    return ('' + txt).replace(/^<pre>/, '');
}
function unpre(txt) {
    return esc(hunpre(txt));
}


var toast = (function () {
    var r = {},
        te = null,
        scrolling = false,
        obj = mknod('div', 'toast');

    document.body.appendChild(obj);
    r.visible = false;
    r.txt = null;
    r.tag = obj;  // filler value (null is scary)
    r.p_txt = '';
    r.p_sec = 0;
    r.p_t = 0;

    var scrollchk = function () {
        if (scrolling)
            return;

        var tb = ebi('toastb'),
            vis = tb.offsetHeight,
            all = tb.scrollHeight;

        if (8 + vis >= all)
            return;

        clmod(obj, 'scroll', 1);
        scrolling = true;
    }

    var unscroll = function () {
        timer.rm(scrollchk);
        clmod(obj, 'scroll');
        scrolling = false;
    }

    r.hide = function (e) {
        if (this === ebi('toastc'))
            ev(e);

        unscroll();
        clearTimeout(te);
        clmod(obj, 'vis');
        r.visible = false;
        r.tag = obj;
        if (!WebAssembly)
            te = setTimeout(function () {
                obj.className = 'hide';
            }, 500);
    };

    r.show = function (cl, sec, txt, tag) {
        txt = (txt + '').slice(0, 16384);

        var same = r.visible && txt == r.p_txt && r.p_sec == sec,
            delta = Date.now() - r.p_t;

        if (same && delta < 100)
            return;

        r.p_txt = txt;
        r.p_sec = sec;
        r.p_t = Date.now();

        clearTimeout(te);
        if (sec)
            te = setTimeout(r.hide, sec * 1000);

        if (same && delta < 1000) {
            var tb = ebi('toastt');
            if (tb) {
                tb.style.animation = 'none';
                tb.offsetHeight;
                tb.style.animation = null;
            }
            return;
        }

        if (txt.indexOf('<body>') + 1)
            txt = txt.slice(0, txt.indexOf('<')) + ' [...]';

        var html = '';
        if (sec) {
            setcvar('--tmtime', (sec - 0.15) + 's');
            setcvar('--tmstep', Math.floor(sec * 20));
            html += '<div id="toastt"></div>';
        }
        obj.innerHTML = html + '<a href="#" id="toastc">x</a><div id="toastb">' + lf2br(txt) + '</div>';
        obj.className = cl;
        sec += obj.offsetWidth;
        obj.className += ' vis';
        ebi('toastc').onclick = r.hide;
        timer.add(scrollchk);
        r.visible = true;
        r.txt = txt;
        r.tag = tag;
    };

    r.ok = function (sec, txt, tag, cls) {
        r.show('ok ' + (cls || ''), sec, txt, tag);
    };
    r.inf = function (sec, txt, tag, cls) {
        r.show('inf ' + (cls || ''), sec, txt, tag);
    };
    r.warn = function (sec, txt, tag, cls) {
        r.show('warn ' + (cls || ''), sec, txt, tag);
    };
    r.err = function (sec, txt, tag, cls) {
        r.show('err ' + (cls || ''), sec, txt, tag);
    };

    return r;
})();


var modal = (function () {
    var r = {},
        q = [],
        o = null,
        scrolling = null,
        cb_up = null,
        cb_ok = null,
        cb_ng = null,
        sel_0 = 0,
        sel_1 = 0,
        tok, tng, prim, sec, ok_cancel;

    r.load = function () {
        tok = (L && L.m_ok) || 'OK';
        tng = (L && L.m_ng) || 'Cancel';
        prim = '<a href="#" id="modal-ok">' + tok + '</a>';
        sec = '<a href="#" id="modal-ng">' + tng + '</a>';
        ok_cancel = WINDOWS ? prim + sec : sec + prim;
    };
    r.load();

    r.busy = false;
    r.nofocus = 0;

    r.show = function (html) {
        tt.hide();
        o = mknod('div', 'modal');
        o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
        document.body.appendChild(o);
        document.addEventListener('keydown', onkey);
        r.busy = true;

        var a = ebi('modal-ng');
        if (a)
            a.onclick = ng;

        a = ebi('modal-ok');
        a.addEventListener('blur', onblur);
        a.onclick = ok;

        var inp = ebi('modali');
        (inp || a).focus();
        if (inp)
            setTimeout(function () {
                inp.setSelectionRange(sel_0, sel_1, "forward");
            }, 0);

        document.addEventListener('focus', onfocus);
        document.addEventListener('selectionchange', onselch);
        timer.add(scrollchk, 1);
        timer.add(onfocus);
        if (cb_up)
            setTimeout(cb_up, 1);
    };

    r.hide = function () {
        timer.rm(onfocus);
        timer.rm(scrollchk);
        scrolling = null;
        try {
            ebi('modal-ok').removeEventListener('blur', onblur);
        }
        catch (ex) { }
        document.removeEventListener('selectionchange', onselch);
        document.removeEventListener('focus', onfocus);
        document.removeEventListener('keydown', onkey);
        try {
            o.parentNode.removeChild(o);
        }
        catch (ex) {
            if (r.busy)
                alert('WARNING: you have a browser-extension which is causing problems');
        }
        r.busy = false;
        setTimeout(next, 50);
    };
    var ok = function (e) {
        ev(e);
        var v = ebi('modali');
        v = v ? v.value : true;
        r.hide();
        if (cb_ok)
            cb_ok(v);
    };
    var ng = function (e) {
        ev(e);
        r.hide();
        if (cb_ng)
            cb_ng(null);
    };

    var scrollchk = function () {
        if (scrolling === true)
            return;

        var o = ebi('modalc'),
            vis = o.offsetHeight,
            all = o.scrollHeight,
            nsc = 8 + vis < all;

        if (scrolling !== nsc)
            clmod(o, 'yk', !nsc);

        scrolling = nsc;
    };

    var onselch = function () {
        try {
            if (window.getSelection() + '')
                r.nofocus = 15;
        }
        catch (ex) { }
    };

    var onblur = function () {
        r.nofocus = 3;
    };

    var onfocus = function (e) {
        if (MOBILE)
            return;

        var ctr = ebi('modalc');
        if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement))
            return;

        setTimeout(function () {
            if (--r.nofocus >= 0)
                return;

            if (ctr = ebi('modal-ok'))
                ctr.focus();
        }, 20);
        ev(e);
    };

    var onkey = function (e) {
        var k = (e.key || e.code) + '',
            eok = ebi('modal-ok'),
            eng = ebi('modal-ng'),
            ae = document.activeElement;

        if ((k == 'Space' || k == 'Spacebar' || k == ' ') && ae && (ae === eok || ae === eng))
            k = 'Enter';

        if (k.endsWith('Enter')) {
            if (ae && ae == eng)
                return ng(e);

            return ok(e);
        }

        if ((k == 'ArrowLeft' || k == 'ArrowRight' || k == 'Left' || k == 'Right') && eng && (ae == eok || ae == eng))
            return (ae == eok ? eng : eok).focus() || ev(e);

        if (k == 'Escape' || k == 'Esc')
            return ng(e);
    }

    var next = function () {
        if (!r.busy && q.length)
            q.shift()();
    }

    r.alert = function (html, cb, fun) {
        q.push(function () {
            _alert(lf2br(html), cb, fun);
        });
        next();
    };
    var _alert = function (html, cb, fun) {
        cb_ok = cb_ng = cb;
        cb_up = fun;
        html += '<div id="modalb"><a href="#" id="modal-ok">OK</a></div>';
        r.show(html);
    }

    r.confirm = function (html, cok, cng, fun, btns) {
        q.push(function () {
            _confirm(lf2br(html), cok, cng, fun, btns);
        });
        next();
    }
    var _confirm = function (html, cok, cng, fun, btns) {
        cb_ok = cok;
        cb_ng = cng === undefined ? cok : cng;
        cb_up = fun;
        html += '<div id="modalb">' + (btns || ok_cancel) + '</div>';
        r.show(html);
    }

    r.prompt = function (html, v, cok, cng, fun, so0, so1) {
        q.push(function () {
            _prompt(lf2br(html), v, cok, cng, fun, so0, so1);
        });
        next();
    }
    var _prompt = function (html, v, cok, cng, fun, so0, so1) {
        cb_ok = cok;
        cb_ng = cng === undefined ? cok : null;
        cb_up = fun;
        sel_0 = so0 || 0;
        sel_1 = so1 === undefined ? v.length : so1;
        html += '<input id="modali" type="text" ' + NOAC + ' /><div id="modalb">' + ok_cancel + '</div>';
        r.show(html);

        ebi('modali').value = v || '';
    }

    return r;
})();


function winpopup(txt) {
    fetch(get_evpath(), {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        },
        body: 'msg=' + uricom_enc(Date.now() + ', ' + txt)
    });
}


var last_repl = null;
function repl_load() {
    var ipre = ebi('repl_pre'),
        tb = ebi('modali');

    function getpres() {
        var o, ret = jread("repl_pre", []);
        if (!ret.length)
            ret = [
                'var v=Object.keys(localStorage); v.sort(); JSON.stringify(v)',
                "for (var a of QSA('#files a[id]')) a.setAttribute('download','')",
                'console.hist.slice(-50).join("\\n")'
            ];

        ipre.innerHTML = '<option value=""></option>';
        for (var a = 0; a < ret.length; a++) {
            o = mknod('option');
            o.setAttribute('value', ret[a]);
            o.textContent = ret[a];
            ipre.appendChild(o);
        }
        last_repl = ipre.value = (last_repl || (ret.length ? ret.slice(-1)[0] : ''));
        return ret;
    }
    ebi('repl_pdel').onclick = function (e) {
        var val = ipre.value,
            pres = getpres();

        apop(pres, val);
        jwrite('repl_pre', pres);
        getpres();
    };
    ebi('repl_pnew').onclick = function (e) {
        var val = tb.value,
            pres = getpres();

        apop(pres, ipre.value);
        pres.push(val);
        jwrite('repl_pre', pres);
        getpres();
        ipre.value = val;
    };
    ipre.oninput = ipre.onchange = function () {
        tb.value = last_repl = ipre.value;
    };
    tb.oninput = function () {
        last_repl = this.value;
    };
    getpres();
    tb.value = last_repl;
    setTimeout(function () {
        tb.setSelectionRange(0, tb.value.length, "forward");
    }, 10);
}
function repl(e) {
    ev(e);
    var html = [
        '<p>js repl (prefix with <code>,</code> to allow raise)</p>',
        '<p><select id="repl_pre"></select>',
        ' &nbsp; <button id="repl_pdel">❌ del</button>',
        ' &nbsp; <button id="repl_pnew">💾 SAVE</button></p>'
    ];

    modal.prompt(html.join(''), '', function (cmd) {
        if (!cmd)
            return toast.inf(3, 'eval aborted');

        cmd = unsmart(cmd);

        if (cmd.startsWith(',')) {
            evalex_fatal = true;
            return modal.alert(esc(eval(cmd.slice(1)) + ''));
        }

        try {
            modal.alert(esc(eval(cmd) + ''));
        }
        catch (ex) {
            modal.alert('<h6>exception</h6>' + esc(ex + ''));
        }
    }, undefined, repl_load);
}
if (ebi('repl'))
    ebi('repl').onclick = repl;


var md_plug = {};
var md_plug_err = function (ex, js) {
    if (ex)
        console.log(ex, js);
};
function load_md_plug(md_text, plug_type, defer) {
    if (defer)
        md_plug[plug_type] = null;

    if (plug_type == 'pre')
        try {
            md_text = md_thumbs(md_text);
        }
        catch (ex) {
            toast.warn(30, '' + ex);
        }

    if (!have_emp)
        return md_text;

    var find = '\n```copyparty_' + plug_type + '\n',
        md = '\n' + md_text.replace(/\r/g, '') + '\n',
        ofs = md.indexOf(find),
        ofs2 = md.indexOf('\n```', ofs + 1);

    if (ofs < 0 || ofs2 < 0)
        return md_text;

    var js = md.slice(ofs + find.length, ofs2 + 1);
    md = md.slice(0, ofs + 1) + md.slice(ofs2 + 4);
    md = md.replace(/$/g, '\r');

    if (defer) { // insert into sandbox
        md_plug[plug_type] = js;
        return md;
    }

    var old_plug = md_plug[plug_type];
    if (!old_plug || old_plug[1] != js) {
        js = 'const loc = new URL("' + location.href + '"), x = { ' + js + ' }; x;';
        try {
            var x = eval(js);
            if (x['ctor']) {
                x['ctor']();
                delete x['ctor'];
            }
        }
        catch (ex) {
            md_plug[plug_type] = null;
            md_plug_err(ex, js);
            return md;
        }
        md_plug[plug_type] = [x, js];
    }

    return md;
}
function md_thumbs(md) {
    if (!/(^|\n)<!-- th -->/.exec(md))
        return md;

    // `!th[flags](some.jpg)`
    // flags: nothing or "l" or "r"

    md = md.split(/!th\[/g);
    for (var a = 1; a < md.length; a++) {
        if (!/^[^\]!()]*\]\([^\][!()]+\)/.exec(md[a])) {
            md[a] = '!th[' + md[a];
            continue;
        }

        var o1 = md[a].indexOf(']('),
            o2 = md[a].indexOf(')', o1),
            alt = md[a].slice(0, o1),
            flags = alt.split(','),
            url = md[a].slice(o1 + 2, o2),
            float = has(flags, 'l') ? 'left' : has(flags, 'r') ? 'right' : '';

        if (!/[?&]cache/.exec(url))
            url = addq(url, 'cache=i');

        md[a] = '<a href="' + url + '" class="mdth mdth' + float.slice(0, 1) + '"><img src="' + url + '&th=w" alt="' + alt + '" /></a>' + md[a].slice(o2 + 1);
    }
    return md.join('');
}
function md_th_set() {
    var els = QSA('.mdth');
    for (var a = 0, aa = els.length; a < aa; a++)
        els[a].onclick = md_th_click;
}
function md_th_click(e) {
    ev(e);
    var url = this.getAttribute('href').split('?')[0];
    if (window.sb_md)
        window.parent.postMessage("imshow " + url, "*");
    else
        thegrid.imshow(url);
}


var svg_decl = '<?xml version="1.0" encoding="UTF-8"?>\n';


var favico = (function () {
    var r = {};
    r.en = true;
    r.tag = null;

    var gx = function (txt) {
        return (svg_decl +
            '<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">\n' +
            (r.bg ? '<rect width="100%" height="100%" rx="16" fill="#' + r.bg + '" />\n' : '') +
            '<text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle"' +
            ' font-family="sans-serif" font-weight="bold" font-size="64px"' +
            ' fill="#' + r.fg + '">' + txt + '</text></svg>'
        );
    }

    r.upd = function (txt, svg) {
        if (!r.txt)
            return;

        var b64;
        try {
            b64 = btoa(svg ? svg_decl + svg : gx(r.txt));
        }
        catch (e1) {
            try {
                b64 = btoa(gx(encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g,
                    function x(m, v) { return String.fromCharCode('0x' + v); })));
            }
            catch (e2) {
                try {
                    b64 = btoa(gx(unescape(encodeURIComponent(r.txt))));
                }
                catch (e3) {
                    return;
                }
            }
        }

        if (!r.tag) {
            r.tag = mknod('link');
            r.tag.rel = 'icon';
            document.head.appendChild(r.tag);
        }
        r.tag.href = 'data:image/svg+xml;base64,' + b64;
    };

    r.init = function () {
        clearTimeout(r.to);
        var dv = (window.dfavico || '').trim().split(/ +/),
            fg = dv.length < 2 ? 'fc5' : dv[1].toLowerCase() == 'none' ? '' : dv[1],
            bg = dv.length < 3 ? '222' : dv[2].toLowerCase() == 'none' ? '' : dv[2];

        scfg_bind(r, 'txt', 'icot', dv[0], r.upd);
        scfg_bind(r, 'fg', 'icof', fg, r.upd);
        scfg_bind(r, 'bg', 'icob', bg, r.upd);
        r.upd();
    };

    r.to = setTimeout(r.init, 100);
    return r;
})();


function cprop(name) {
    return getComputedStyle(document.documentElement).getPropertyValue(name);
}


function bchrome() {
    var v, o = QS('meta[name=theme-color]');
    if (!o)
        return;

    try {
        v = cprop('--bg-u3');
    }
    catch (ex) { }
    o.setAttribute('content', v ? v : document.documentElement.className.indexOf('y') + 1 ? '#eee' : '#333');
}
bchrome();

var XC_CMSG = {
    502: "bad gateway (server offline)",
    503: "server offline",
    504: "gateway timeout (server busy)",
    529: "gateway timeout (server busy)",
    520: "unknown error from server",
    521: "server offline",
    523: "server offline",
    522: "proxy timeout (server busy)",
    524: "proxy timeout (server busy)",
    598: "proxy timeout (server busy)",
    599: "proxy timeout (server busy)",
};
var cf_cha_t = 0;
function xhrchk(xhr, prefix, e404, lvl, tag) {
    if (xhr.status < 400 && xhr.status >= 200)
        return true;

    if (tag === undefined)
        tag = prefix;

    var errtxt = ((xhr.response && xhr.response.err) || xhr.responseText) || '',
        suf = '',
        fun = toast[lvl || 'err'],
        is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);

    if (errtxt.startsWith('<pre>'))
        suf = '\n\nerror-details: «' + unpre(errtxt).split('\n')[0].trim() + '»';
    else
        errtxt = esc(errtxt).slice(0, 32768);

    if (xhr.status == 403 && !is_cf)
        return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out") + suf, tag);

    if (xhr.status == 404)
        return toast.err(0, prefix + e404 + suf, tag);

    if (!xhr.status && !errtxt)
        return toast.err(0, prefix + L.xhr0);

    if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
        var now = Date.now(), td = now - cf_cha_t;
        if (td < 15000)
            return;

        cf_cha_t = now;
        errtxt = 'Clou' + wah + 'dflare protection kicked in\n\n<strong>trying to fix it...</strong>';
        fun = toast.warn;

        qsr('#cf_frame');
        var fr = mknod('iframe', 'cf_frame');
        fr.src = SR + '/?cf_challenge';
        document.body.appendChild(fr);
    }

    if (XC_CMSG[xhr.status] && (errtxt.indexOf('<html') + 1))
        errtxt = XC_CMSG[xhr.status];

    return fun(0, prefix + xhr.status + ": " + errtxt, tag);
}

J_UTL = 2;
