<style>
    #contentarea {
        position: relative !important;
    }
    #puq-page-preloader {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(255, 255, 255, 0.5);
        z-index: 999;
        display: none;
        justify-content: center;
        align-items: center;
        pointer-events: all;
    }
    #puq-page-preloader i {
        font-size: 48px;
        color: #007bff;
    }
    /* Container-level loading overlay — added by puqLoading() / puqLoadingDone() */
    .puq-loading-overlay {
        position: absolute; top: 0; left: 0; right: 0; bottom: 0;
        background: rgba(255, 255, 255, 0.75);
        z-index: 500;
        display: flex; align-items: center; justify-content: center;
        min-height: 80px;
        animation: puq-fadein 0.18s ease-out;
    }
    .puq-loading-inner {
        text-align: center; padding: 16px;
    }
    .puq-loading-caption {
        margin-top: 8px; color: #6c757d; font-size: 13px;
    }
    @keyframes puq-fadein { from { opacity: 0; } to { opacity: 1; } }
    .puq-navbar {
        margin-bottom: 15px;
    }
    .navbar-nav > li.active > a {
        font-weight: bold;
    }
    .dropdown-menu > li.active > a {
        font-weight: bold;
        background-color: #e7e7e7;
    }

    /* ---------- Combined server cell (name + hostname + health + metrics) ---------- */
    .puq-server-cell { line-height: 1.4; }
    .puq-server-cell .srv-head {
        display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
        margin-bottom: 4px;
    }
    .puq-server-cell .srv-head .label { margin-right: 0; }
    .puq-server-cell .srv-name { font-weight: 700; font-size: 14px; color: #212529; }
    .puq-server-cell .srv-host { font-family: monospace; font-size: 12px; color: #6c757d; }
    .puq-server-cell .srv-driver { font-size: 10px; color: #999; text-transform: uppercase; letter-spacing: 0.3px; }
    .puq-server-cell .srv-health-ts { font-size: 11px; color: #888; margin-left: auto; }

    /* 4 metric chips side-by-side — one thin row */
    .puq-metrics {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 10px;
        background: #fafbfc;
        border: 1px solid #e5e7eb;
        border-radius: 4px;
        padding: 6px 10px;
        margin: 4px 0;
    }
    .puq-metrics .m-cell {
        display: grid;
        grid-template-columns: auto 1fr auto;
        align-items: center;
        gap: 6px;
        font-size: 11px;
        min-width: 0;
    }
    .puq-metrics .m-label {
        color: #555;
        white-space: nowrap;
        font-weight: 500;
    }
    .puq-metrics .m-label i { width: 12px; text-align: center; margin-right: 3px; color: #888; }
    .puq-metrics .m-bar {
        height: 7px;
        background: #e9ecef;
        border-radius: 3px;
        overflow: hidden;
        min-width: 30px;
    }
    .puq-metrics .m-bar > div {
        height: 100%;
        transition: width 0.3s ease;
    }
    .puq-metrics .m-pct {
        text-align: right;
        font-variant-numeric: tabular-nums;
        color: #333;
        font-weight: 600;
        min-width: 42px;
    }
    .puq-metrics .m-bar-success > div { background: #5cb85c; }
    .puq-metrics .m-bar-info    > div { background: #5bc0de; }
    .puq-metrics .m-bar-warning > div { background: #f0ad4e; }
    .puq-metrics .m-bar-danger  > div { background: #d9534f; }
    .puq-metrics .m-pct-danger  { color: #c9302c; }
    .puq-metrics .m-pct-warning { color: #ec971f; }

    .puq-inventory {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
        font-size: 11px;
        margin-top: 2px;
    }
    .puq-inventory .pill {
        display: inline-flex;
        align-items: center;
        padding: 2px 8px;
        background: #f0f2f5;
        border-radius: 10px;
        font-size: 10px;
        color: #555;
        line-height: 1.4;
    }
    .puq-inventory .pill i { color: #888; margin-right: 4px; font-size: 10px; }

    /* Shared overlay used by puqConfirm / puqPrompt / puqModal. */
    .puq-confirm-overlay {
        position: fixed; top: 0; left: 0; right: 0; bottom: 0;
        background: rgba(0,0,0,.4); z-index: 10001;
        display: flex; justify-content: center; align-items: center;
        opacity: 0; transition: opacity .2s ease;
    }
    .puq-confirm-overlay.puq-confirm-show { opacity: 1; }
    .puq-confirm-box {
        background: #fff; border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,.2);
        min-width: 360px; max-width: 520px; padding: 18px 0 0;
        transform: scale(.96); transition: transform .2s ease;
    }
    .puq-confirm-overlay.puq-confirm-show .puq-confirm-box { transform: scale(1); }
    .puq-confirm-icon { text-align: center; font-size: 42px; margin-bottom: 8px; }
    .puq-confirm-title { text-align: center; font-size: 16px; font-weight: 600; padding: 0 24px; }
    .puq-confirm-text { text-align: center; color: #555; padding: 10px 24px 4px; font-size: 13px; }
    .puq-confirm-btns { display: flex; gap: 8px; padding: 12px 16px 14px; }
    .puq-confirm-btns > button {
        flex: 1; border: none; border-radius: 4px; padding: 8px 12px;
        font-size: 13px; font-weight: 500; cursor: pointer;
        transition: filter .15s ease;
    }
    .puq-confirm-btns > button:hover:not(:disabled) { filter: brightness(.95); }
    .puq-confirm-btns > button:disabled { opacity: .6; cursor: wait; }
    .puq-confirm-btn-cancel { background: #f0f2f5; color: #333; }
    .puq-confirm-btn-ok     { color: #fff; }
</style>

<div id="puq-page-preloader">
    <div>
        <i class="fa fa-spinner fa-spin"></i>
    </div>
</div>

<nav class="navbar navbar-default puq-navbar">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#puq-navbar-menu" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="{$meta.admin_module_url}&page=home">
                <img src="{$meta.logo_url}" alt="PUQ Web Hosting" style="height:20px; margin-top:-2px; display:inline-block;">
            </a>
        </div>
        <div class="collapse navbar-collapse" id="puq-navbar-menu">
            <ul class="nav navbar-nav">
                {foreach from=$nav_items item=item}
                    {if $item.position != 'right'}
                        {if $item.dropdown}
                            <li class="dropdown{if $item.active} active{/if}" id="nav_{$item.id}">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
                                    <i class="{$item.icon}" style="margin-right:3px;"></i> {$item.label} <span class="caret"></span>
                                </a>
                                <ul class="dropdown-menu">
                                    {foreach from=$item.items item=subitem}
                                        {if isset($subitem.divider) && $subitem.divider}
                                            <li role="separator" class="divider"></li>
                                        {elseif isset($subitem.external) && $subitem.external}
                                            <li>
                                                <a href="{$subitem.url}" target="_blank" rel="noopener">
                                                    <i class="{$subitem.icon}" style="margin-right:3px;"></i> {$subitem.label}
                                                    <i class="fa fa-external-link-alt" style="font-size:10px;margin-left:4px;color:#999;"></i>
                                                </a>
                                            </li>
                                        {else}
                                            <li{if $subitem.active} class="active"{/if}>
                                                <a href="{$subitem.url}" class="puq-nav-link">
                                                    <i class="{$subitem.icon}" style="margin-right:3px;"></i> {$subitem.label}
                                                </a>
                                            </li>
                                        {/if}
                                    {/foreach}
                                </ul>
                            </li>
                        {else}
                            <li{if $item.active} class="active"{/if} id="nav_{$item.id}">
                                <a href="{$item.url}" class="puq-nav-link"><i class="{$item.icon}" style="margin-right:3px;"></i> {$item.label}</a>
                            </li>
                        {/if}
                    {/if}
                {/foreach}
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {foreach from=$nav_items item=item}
                    {if $item.position == 'right'}
                        {if $item.dropdown}
                            <li class="dropdown{if $item.active} active{/if}" id="nav_{$item.id}">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
                                    <i class="{$item.icon}" style="margin-right:3px;"></i> {$item.label} <span class="caret"></span>
                                </a>
                                <ul class="dropdown-menu">
                                    {foreach from=$item.items item=subitem}
                                        {if isset($subitem.divider) && $subitem.divider}
                                            <li role="separator" class="divider"></li>
                                        {elseif isset($subitem.external) && $subitem.external}
                                            <li>
                                                <a href="{$subitem.url}" target="_blank" rel="noopener">
                                                    <i class="{$subitem.icon}" style="margin-right:3px;"></i> {$subitem.label}
                                                    <i class="fa fa-external-link-alt" style="font-size:10px;margin-left:4px;color:#999;"></i>
                                                </a>
                                            </li>
                                        {else}
                                            <li{if $subitem.active} class="active"{/if}>
                                                <a href="{$subitem.url}" class="puq-nav-link">
                                                    <i class="{$subitem.icon}" style="margin-right:3px;"></i> {$subitem.label}
                                                </a>
                                            </li>
                                        {/if}
                                    {/foreach}
                                </ul>
                            </li>
                        {else}
                            <li{if $item.active} class="active"{/if} id="nav_{$item.id}">
                                <a href="{$item.url}" class="puq-nav-link"><i class="{$item.icon}" style="margin-right:3px;"></i> {$item.label}</a>
                            </li>
                        {/if}
                    {/if}
                {/foreach}
                <li><a href="#" style="cursor:default;color:#999;"><i class="far fa-code-branch fa-fw"></i> v{$meta.version}</a></li>
            </ul>
        </div>
    </div>
</nav>

{literal}
<script>
(function(){
    // ==========================================
    // Notification: growl -> gritter -> toastr -> alert
    // ==========================================
    window.showNotification = function(message, type) {
        if (typeof jQuery.growl !== 'undefined') {
            if (type === 'success') {
                jQuery.growl.notice({ message: message });
            } else {
                jQuery.growl.error({ message: message });
            }
        } else if (typeof $.gritter !== 'undefined') {
            $.gritter.add({
                title: type === 'error' ? 'Error' : 'Success',
                text: message,
                class_name: type === 'error' ? 'gritter-error' : 'gritter-success'
            });
        } else if (typeof toastr !== 'undefined') {
            toastr[type === 'error' ? 'error' : 'success'](message);
        } else {
            alert(message);
        }
    };

    // ==========================================
    // Container-spinner helpers — overlay any element with a centered
    // spinner while content is loading. Use for tables, panels, iframe
    // containers, etc. Pair every puqLoading call with puqLoadingDone
    // in the .complete or .always callback.
    //
    // Usage:
    //   puqLoading('#my-table'); $.ajax({...}).always(() => puqLoadingDone('#my-table'));
    //   puqLoading($('#my-panel'));     // jQuery selector also accepted
    //   puqLoading($el, 'Refreshing…'); // optional caption
    // ==========================================
    window.puqLoading = function(target, caption) {
        var $t = target instanceof jQuery ? target : $(target);
        if (!$t.length) return;
        $t.css('position', $t.css('position') === 'static' ? 'relative' : $t.css('position'));
        // Remove any previous overlay before adding a fresh one — prevents
        // stacking when a poll fires faster than the previous response.
        $t.find('> .puq-loading-overlay').remove();
        var $o = $('<div class="puq-loading-overlay">'
            + '<div class="puq-loading-inner">'
            + '<i class="fa fa-spinner fa-spin fa-3x text-info"></i>'
            + (caption ? '<div class="puq-loading-caption">' + caption + '</div>' : '')
            + '</div></div>');
        $t.append($o);
    };
    window.puqLoadingDone = function(target) {
        var $t = target instanceof jQuery ? target : $(target);
        if (!$t.length) return;
        $t.find('> .puq-loading-overlay').remove();
    };

    // Render a visible error INTO a loaded container instead of leaving it
    // spinning / showing a false "empty" state. Tables get an error <tr>
    // (spanning the header columns); any other element gets a danger alert.
    window.puqContainerError = function(target, msg) {
        var $c = (target instanceof jQuery) ? target : $(target);
        if (!$c || !$c.length) return;
        var safe = (window.esc ? window.esc(msg || 'Failed to load.') : (msg || 'Failed to load.'));
        var inner = '<i class="fa fa-exclamation-triangle"></i> ' + safe;
        var $table = $c.is('table') ? $c : $c.find('table').first();
        // Skip tables that a caller manages with DataTables — injecting a raw
        // <tr> would fight DataTables' own DOM/empty-state. Those callers already
        // render an (empty) table on error rather than an endless spinner.
        var isDt = $table.length && window.jQuery && jQuery.fn.dataTable && jQuery.fn.dataTable.isDataTable($table[0]);
        if ($table.length && $table.find('tbody').length && !isDt) {
            var cols = $table.find('thead th').length || 1;
            $table.find('tbody').html('<tr><td colspan="' + cols + '" class="text-danger text-center" style="padding:14px;">' + inner + '</td></tr>');
        } else if (!$table.length) {
            $c.html('<div class="alert alert-danger" style="margin:8px;">' + inner + '</div>');
        }
    };

    // puqAjax — drop-in wrapper around $.ajax that auto-shows / hides the
    // container spinner AND surfaces failures. Use whenever a non-button
    // trigger loads content.
    //
    //   puqAjax({ container: '#my-table', url: '...', success: function(r){...} });
    //
    // On a transport error OR an app-level {status:'error'} body (the backend
    // returns those with HTTP 200, so $.ajax's own error: never fires), the
    // container shows the reason rather than spinning forever or rendering a
    // misleading "No rows". The caller's success still runs first, so any
    // bespoke per-widget handling keeps working; we only OVERLAY the error.
    // The success call is try/caught so an undefined helper / bad data shows
    // the error too instead of silently aborting mid-render.
    // Everything else is forwarded to $.ajax. dataType defaults to 'json'.
    window.puqAjax = function(opts) {
        var $c = opts.container ? (opts.container instanceof jQuery ? opts.container : $(opts.container)) : null;
        if ($c && $c.length) puqLoading($c, opts.loadingCaption);
        var origComplete = opts.complete, origSuccess = opts.success, origError = opts.error;
        opts.success = function(response) {
            var ok = response && response.status === 'success';
            try { if (origSuccess) origSuccess.apply(this, arguments); }
            catch (e) {
                if (window.console) console.error('puqAjax handler threw', e);
                if ($c && $c.length) puqContainerError($c, 'Render error: ' + ((e && e.message) ? e.message : e));
                return;
            }
            if (!ok && $c && $c.length) puqContainerError($c, (response && response.msg) ? response.msg : 'Failed to load.');
        };
        opts.error = function(xhr, status) {
            if ($c && $c.length) puqContainerError($c, 'Request failed (' + ((xhr && xhr.status) ? xhr.status : (status || 'error')) + ').');
            if (origError) origError.apply(this, arguments);
        };
        opts.complete = function(xhr, status) {
            if ($c && $c.length) puqLoadingDone($c);
            if (origComplete) origComplete.call(this, xhr, status);
        };
        if (!opts.dataType) opts.dataType = 'json';
        return $.ajax(opts);
    };

    // ==========================================
    // Button spinner helpers
    // ==========================================
    window.btnSpinner = function(btn, text) {
        if (btn instanceof jQuery) btn = btn[0];
        if (!btn) return;
        btn.disabled = true;
        btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>' + (text ? ' ' + text : '');
    };

    window.btnRestore = function(btn, icon, text) {
        if (btn instanceof jQuery) btn = btn[0];
        if (!btn) return;
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-' + icon + '"></i> ' + text;
    };

    // ==========================================
    // Form validation error handling
    // ==========================================
    window.formClearErrors = function(form) {
        var $form = $(form);
        $form.find('.form-group').removeClass('has-error');
        $form.find('.help-block.puq-error').remove();
    };

    window.formShowErrors = function(form, errors) {
        var $form = $(form);
        formClearErrors($form);
        if (!errors || typeof errors !== 'object') return;
        $.each(errors, function(field, msg) {
            var $group = $form.find('[data-field="' + field + '"]');
            if ($group.length) {
                $group.addClass('has-error');
                $group.append('<span class="help-block puq-error text-danger"><i class="fa fa-exclamation-circle"></i> ' + msg + '</span>');
            }
        });
    };

    window.formSubmit = function(opts) {
        var btn = opts.btn;
        var $form = $(opts.form);
        var action = opts.action;
        var baseUrl = opts.baseUrl;
        var onSuccess = opts.onSuccess;
        var btnIcon = opts.btnIcon || 'save';
        var btnText = opts.btnText || 'Save';

        formClearErrors($form);
        btnSpinner(btn, opts.savingText || 'Saving...');

        $.ajax({
            url: baseUrl + action,
            type: 'POST',
            data: $form.serialize(),
            dataType: 'json',
            success: function(response) {
                if (response.status === 'success') {
                    showNotification(response.msg || 'Saved successfully.', 'success');
                    if (onSuccess) onSuccess(response);
                } else {
                    if (response.errors) {
                        formShowErrors($form, response.errors);
                    }
                    showNotification(response.msg || 'Validation failed.', 'error');
                }
            },
            error: function(xhr) {
                try {
                    var response = JSON.parse(xhr.responseText);
                    if (response.errors) {
                        formShowErrors($form, response.errors);
                    }
                    showNotification(response.msg || 'An error occurred.', 'error');
                } catch(e) {
                    showNotification('An error occurred.', 'error');
                }
            },
            complete: function() {
                btnRestore(btn, btnIcon, btnText);
            }
        });
    };

    // ==========================================
    // Shared admin helpers — small DOM/format utilities used across pages.
    // ==========================================
    window.esc = function(s){ return jQuery('<div>').text(s == null ? '' : s).html(); };
    window.escAttr = function(s){ return esc(s).replace(/"/g, '&quot;').replace(/'/g, '&#39;'); };

    window.fmtBytes = function(bytes, precision){
        if (bytes == null) return '—';
        var n = Number(bytes); if (!isFinite(n)) return '—';
        if (n === 0) return '0 B';
        var u = ['B','KB','MB','GB','TB','PB'];
        var i = Math.min(u.length - 1, Math.floor(Math.log(Math.abs(n)) / Math.log(1024)));
        var v = n / Math.pow(1024, i);
        var p = (precision != null) ? precision : (v < 10 ? 2 : (v < 100 ? 1 : 0));
        return v.toFixed(p) + ' ' + u[i];
    };

    window.timeAgo = function(ts){
        if (!ts) return '';
        var t = Date.parse(String(ts).replace(' ', 'T') + 'Z');
        if (isNaN(t)) return '';
        var d = Math.floor((Date.now() - t) / 1000);
        if (d < 60)    return d + ' s ago';
        if (d < 3600)  return Math.floor(d / 60) + ' min ago';
        if (d < 86400) return Math.floor(d / 3600) + ' h ago';
        return Math.floor(d / 86400) + ' d ago';
    };

    /** Render a colored Bootstrap label. badge(text, kind) where kind ∈
     *  {default, primary, success, info, warning, danger}. */
    window.badge = function(text, kind){
        return '<span class="label label-' + (kind || 'default') + '">' + esc(text) + '</span>';
    };

    /** Map a deploy/task/probe/flag status string → bootstrap label class.
     *  Union map covering every status vocabulary used across admin views
     *  (services lifecycle, task queue, DNS, probes, on/off flags). */
    window.statusClass = function(s){
        var map = {
            active:'success', success:'success', ok:'success', enabled:'success', yes:'success',
            queued:'warning', deploying:'info', running:'info', collecting:'info', info:'info',
            error:'danger', failed:'danger', expired:'danger', stopped:'danger',
            cancelled:'default', pending:'default', terminated:'default',
            disabled:'default', no:'default', skipped:'default', never:'default',
            pending_delete:'warning', deleting:'warning',
            pending_terminate:'warning', terminating:'warning',
            rolling_back:'warning', suspended:'warning'
        };
        return map[s] || 'default';
    };

    /** Render a status label in one call. Picks color from statusClass. */
    window.statusBadge = function(s){
        return '<span class="label label-' + window.statusClass(s) + '">' + esc(s == null ? '' : s) + '</span>';
    };

    /** Build a loading placeholder row for a tbody. Usage:
     *      $tb.html(loadingRow(5, 'Loading…')); */
    window.loadingRow = function(colspan, label){
        return '<tr><td colspan="' + (colspan || 5) + '" class="text-center" style="padding:18px;">' +
            '<i class="fa fa-spinner fa-spin"></i> ' + esc(label || 'Loading…') + '</td></tr>';
    };

    // ==========================================
    // puqConfirm / puqPrompt / puqModal — overlay-based replacements for
    // native confirm()/alert()/prompt(). Same API as the client-side
    // helpers so future developers don't have to learn two patterns.
    //
    //   puqConfirm({type:'danger', title:'Delete?', text:'Lose all data',
    //               ok:'Delete', cancel:'Keep',
    //               onConfirm: function(){ return ajaxPromise; }});
    //
    //   puqPrompt({title:'…', placeholder:'…', validator: fn, onSubmit: fn});
    //   puqModal({title:'…', body:'<form>…</form>', onConfirm: fn});
    // ==========================================
    var _puqConfirmResolve = null;
    window.puqConfirm = function(opts){
        if (typeof opts === 'string') opts = { text: opts };
        var type      = opts.type      || 'warning';
        var title     = opts.title     || '';
        var text      = opts.text      || '';
        var okTxt     = opts.ok        || 'OK';
        var cancel    = opts.cancel    || 'Cancel';
        var onConfirm = opts.onConfirm || null;
        var colors = { warning:'#faa61a', danger:'#f04747', info:'#5865F2', success:'#43b581' };
        var icons  = { warning:'fa-exclamation-triangle', danger:'fa-trash-alt', info:'fa-question-circle', success:'fa-check-circle' };
        var color  = colors[type] || colors.warning;
        var icon   = opts.icon || icons[type] || icons.warning;

        var old = document.getElementById('puq-confirm-el');
        if (old) { if (_puqConfirmResolve) _puqConfirmResolve(false); old.remove(); }

        var overlay = document.createElement('div');
        overlay.className = 'puq-confirm-overlay';
        overlay.id = 'puq-confirm-el';
        overlay.innerHTML =
            '<div class="puq-confirm-box">' +
                '<div class="puq-confirm-icon" style="color:' + color + ';"><i class="fas ' + icon + '"></i></div>' +
                (title ? '<div class="puq-confirm-title">' + title + '</div>' : '') +
                '<div class="puq-confirm-text">' + text + '</div>' +
                '<div class="puq-confirm-err text-danger" style="text-align:center;font-size:12px;padding:0 24px 6px;min-height:0;"></div>' +
                '<div class="puq-confirm-btns">' +
                    '<button type="button" class="puq-confirm-btn-cancel">' + cancel + '</button>' +
                    '<button type="button" class="puq-confirm-btn-ok" style="background:' + color + ';">' + okTxt + '</button>' +
                '</div>' +
            '</div>';
        document.body.appendChild(overlay);
        var $okBtn  = jQuery(overlay).find('.puq-confirm-btn-ok');
        var $cnBtn  = jQuery(overlay).find('.puq-confirm-btn-cancel');
        var $errBox = jQuery(overlay).find('.puq-confirm-err');
        var okOrig  = $okBtn.html();

        requestAnimationFrame(function(){ requestAnimationFrame(function(){ overlay.classList.add('puq-confirm-show'); }); });

        return new Promise(function(resolve){
            _puqConfirmResolve = resolve;
            var pending = false;
            function close(result){
                if (pending) return;
                document.removeEventListener('keydown', onKey);
                overlay.classList.remove('puq-confirm-show');
                setTimeout(function(){ if (overlay.parentNode) overlay.remove(); }, 200);
                _puqConfirmResolve = null;
                resolve(result);
            }
            function onKey(e){ if (e.key === 'Escape') close(false); if (e.key === 'Enter') onOk(); }
            function onOk(){
                if (pending) return;
                if (!onConfirm) { close(true); return; }
                pending = true;
                $errBox.text('').css('padding','0 24px 6px');
                $okBtn.html('<i class="fas fa-circle-notch fa-spin"></i> ' + okTxt).prop('disabled', true);
                $cnBtn.prop('disabled', true);
                Promise.resolve(onConfirm()).then(function(){
                    pending = false;
                    $okBtn.html(okOrig).prop('disabled', false);
                    $cnBtn.prop('disabled', false);
                    close(true);
                }).catch(function(err){
                    pending = false;
                    $okBtn.html(okOrig).prop('disabled', false);
                    $cnBtn.prop('disabled', false);
                    var msg = (err && err.message) ? err.message : (typeof err === 'string' ? err : 'Failed');
                    $errBox.text(msg).css('padding','6px 24px 10px');
                    if (window.showNotification) showNotification(msg, 'error');
                });
            }
            $cnBtn.on('click', function(){ close(false); });
            $okBtn.on('click', onOk);
            overlay.addEventListener('click', function(e){ if (e.target === overlay) close(false); });
            document.addEventListener('keydown', onKey);
        });
    };

    window.puqPrompt = function(opts){
        opts = opts || {};
        var title       = opts.title       || '';
        var text        = opts.text        || '';
        var placeholder = opts.placeholder || '';
        var inputType   = opts.inputType   || 'text';
        var inputValue  = opts.value       || '';
        var validator   = opts.validator   || null;
        var validatorMsg= opts.validatorMsg|| 'Invalid value';
        var onSubmit    = opts.onSubmit    || null;
        var okTxt       = opts.ok          || 'OK';
        var cancel      = opts.cancel      || 'Cancel';
        var type        = opts.type        || 'info';
        var colors = { warning:'#faa61a', danger:'#f04747', info:'#5865F2', success:'#43b581' };
        var color  = colors[type] || colors.info;
        var icon   = opts.icon || 'fa-pencil-alt';

        var overlay = document.createElement('div');
        overlay.className = 'puq-confirm-overlay';
        overlay.innerHTML =
            '<div class="puq-confirm-box">' +
                '<div class="puq-confirm-icon" style="color:' + color + ';"><i class="fas ' + icon + '"></i></div>' +
                (title ? '<div class="puq-confirm-title">' + title + '</div>' : '') +
                (text  ? '<div class="puq-confirm-text">' + text + '</div>' : '') +
                '<div style="padding:0 24px 4px;"><input type="' + inputType + '" class="form-control puq-prompt-input"' +
                    ' placeholder="' + escAttr(placeholder) + '" value="' + escAttr(inputValue) + '"></div>' +
                '<div class="puq-confirm-err text-danger" style="text-align:left;font-size:12px;padding:6px 24px 0;min-height:14px;"></div>' +
                '<div class="puq-confirm-btns">' +
                    '<button type="button" class="puq-confirm-btn-cancel">' + cancel + '</button>' +
                    '<button type="button" class="puq-confirm-btn-ok" style="background:' + color + ';">' + okTxt + '</button>' +
                '</div>' +
            '</div>';
        document.body.appendChild(overlay);
        var $input  = jQuery(overlay).find('.puq-prompt-input');
        var $okBtn  = jQuery(overlay).find('.puq-confirm-btn-ok');
        var $cnBtn  = jQuery(overlay).find('.puq-confirm-btn-cancel');
        var $errBox = jQuery(overlay).find('.puq-confirm-err');
        var okOrig  = $okBtn.html();
        requestAnimationFrame(function(){ requestAnimationFrame(function(){ overlay.classList.add('puq-confirm-show'); }); });
        setTimeout(function(){ $input.focus(); }, 50);

        return new Promise(function(resolve){
            var pending = false;
            function close(val){
                if (pending) return;
                document.removeEventListener('keydown', onKey);
                overlay.classList.remove('puq-confirm-show');
                setTimeout(function(){ if (overlay.parentNode) overlay.remove(); }, 200);
                resolve(val);
            }
            function onKey(e){ if (e.key === 'Escape') close(null); if (e.key === 'Enter') onOk(); }
            function onOk(){
                if (pending) return;
                var v = $input.val();
                if (validator && !validator(v)) { $errBox.text(validatorMsg); return; }
                $errBox.text('');
                if (!onSubmit) { close(v); return; }
                pending = true;
                $okBtn.html('<i class="fas fa-circle-notch fa-spin"></i> ' + okTxt).prop('disabled', true);
                $cnBtn.prop('disabled', true);
                Promise.resolve(onSubmit(v)).then(function(){
                    pending = false;
                    $okBtn.html(okOrig).prop('disabled', false);
                    $cnBtn.prop('disabled', false);
                    close(v);
                }).catch(function(err){
                    pending = false;
                    $okBtn.html(okOrig).prop('disabled', false);
                    $cnBtn.prop('disabled', false);
                    var msg = (err && err.message) ? err.message : (typeof err === 'string' ? err : 'Failed');
                    $errBox.text(msg);
                    if (window.showNotification) showNotification(msg, 'error');
                });
            }
            $cnBtn.on('click', function(){ close(null); });
            $okBtn.on('click', onOk);
            overlay.addEventListener('click', function(e){ if (e.target === overlay) close(null); });
            document.addEventListener('keydown', onKey);
        });
    };

    window.puqModal = function(opts){
        opts = opts || {};
        var colors = { warning:'#faa61a', danger:'#f04747', info:'#5865F2', success:'#43b581' };
        var icons  = { warning:'fa-exclamation-triangle', danger:'fa-trash-alt', info:'fa-question-circle', success:'fa-check-circle' };
        var color  = colors[opts.type || 'info'] || colors.info;
        var icon   = opts.icon || icons[opts.type || 'info'] || icons.info;
        var ov = document.createElement('div');
        ov.className = 'puq-confirm-overlay';
        ov.innerHTML =
            '<div class="puq-confirm-box" style="max-width:680px;width:90%;">' +
              '<div class="puq-confirm-icon" style="color:' + color + ';"><i class="fas ' + icon + '"></i></div>' +
              (opts.title ? '<div class="puq-confirm-title">' + opts.title + '</div>' : '') +
              '<div class="puq-confirm-text" style="text-align:left;">' + (opts.body || '') + '</div>' +
              '<div class="puq-confirm-err text-danger" style="text-align:left;font-size:12px;padding:0 24px 6px;min-height:0;"></div>' +
              '<div class="puq-confirm-btns">' +
                '<button type="button" class="puq-confirm-btn-cancel">' + (opts.cancel || 'Cancel') + '</button>' +
                '<button type="button" class="puq-confirm-btn-ok" style="background:' + color + ';">' + (opts.ok || 'OK') + '</button>' +
              '</div>' +
            '</div>';
        document.body.appendChild(ov);
        var $ok = jQuery(ov).find('.puq-confirm-btn-ok');
        var $cn = jQuery(ov).find('.puq-confirm-btn-cancel');
        var $err= jQuery(ov).find('.puq-confirm-err');
        var $box= jQuery(ov).find('.puq-confirm-box');
        var pending = false;
        function close(){
            if (pending) return;
            ov.classList.remove('puq-confirm-show');
            document.removeEventListener('keydown', onKey);
            setTimeout(function(){ if (ov.parentNode) ov.remove(); }, 200);
        }
        function onKey(e){ if (e.key === 'Escape') close(); }
        $cn.on('click', close);
        ov.addEventListener('click', function(e){ if (e.target === ov) close(); });
        document.addEventListener('keydown', onKey);
        $ok.on('click', function(){
            if (pending) return;
            if (!opts.onConfirm) { close(); return; }
            pending = true;
            $err.text('').css('padding','0 24px 6px');
            var okHtml = $ok.html();
            $ok.html('<i class="fas fa-circle-notch fa-spin"></i> ' + (opts.ok || 'OK')).prop('disabled', true);
            $cn.prop('disabled', true);
            Promise.resolve(opts.onConfirm($box[0])).then(function(){
                pending = false;
                $ok.html(okHtml).prop('disabled', false);
                $cn.prop('disabled', false);
                close();
            }).catch(function(err){
                pending = false;
                $ok.html(okHtml).prop('disabled', false);
                $cn.prop('disabled', false);
                var msg = (err && err.message) ? err.message : (typeof err === 'string' ? err : 'Failed');
                $err.text(msg).css('padding','6px 24px 10px');
                if (window.showNotification) showNotification(msg, 'error');
            });
        });
        requestAnimationFrame(function(){ requestAnimationFrame(function(){ ov.classList.add('puq-confirm-show'); }); });
        if (typeof opts.onOpen === 'function') opts.onOpen($box[0]);
        return { close: close, box: $box[0] };
    };

    /** Auto-refresh handle. Usage:
     *      var ar = puqAutoRefresh(load);
     *      // inside load():
     *      ar.schedule(anyPending);
     *      ar.stop();
     */
    window.puqAutoRefresh = function(fn, interval){
        var ms = interval || 5000, t = null;
        return {
            schedule: function(anyPending){
                if (t) { clearTimeout(t); t = null; }
                if (anyPending) t = setTimeout(fn, ms);
            },
            stop: function(){ if (t) { clearTimeout(t); t = null; } }
        };
    };

    /** Click-to-copy on any element with class="puq-copy" (data-copy="…" wins
     *  over text content). Single delegated listener; works on dynamic nodes. */
    jQuery(document).on('click', '.puq-copy', function(){
        var $el = jQuery(this);
        var txt = $el.attr('data-copy') != null ? $el.attr('data-copy') : $el.text();
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(txt).then(function(){
                if (window.showNotification) showNotification('Copied: ' + (txt.length > 60 ? txt.substring(0,60) + '…' : txt), 'success');
            }).catch(function(){ if (window.showNotification) showNotification('Clipboard blocked — copy manually', 'error'); });
        } else {
            var $ta = jQuery('<textarea>').val(txt).css({position:'fixed',top:0,left:0,opacity:0}).appendTo('body').select();
            try { document.execCommand('copy'); if (window.showNotification) showNotification('Copied', 'success'); }
            catch (e) { if (window.showNotification) showNotification('Copy failed — select manually', 'error'); }
            $ta.remove();
        }
    });

    // ==========================================
    // Page preloader on navigation clicks
    // ==========================================
    var preloader = document.getElementById('puq-page-preloader');
    var contentArea = document.getElementById('contentarea');

    if (contentArea && preloader) {
        contentArea.insertBefore(preloader, contentArea.firstChild);
    }

    var navLinks = document.querySelectorAll('.puq-nav-link');

    navLinks.forEach(function(link) {
        link.addEventListener('click', function(e) {
            e.preventDefault();
            if (preloader) {
                preloader.style.display = 'flex';
            }
            setTimeout(function() {
                window.location.href = link.getAttribute('href');
            }, 100);
        });
    });
})();
</script>
{/literal}
