<?php
/**
 * link-menu-system :: Portal (internal start page + inline admin)  ::  lms 19.1FBoxf
 *
 * Author     smisco <info@smisco.biz>
 * Copyright  (C) 2026 smisco
 * Website    https://smisco.biz/lms
 *
 * The "lms <build>.<ts6>" token above is the version stamp (build counter + ts6
 * timestamp); keep it in lockstep with the LMS_VERSION constant below.
 */
define('LMS_VERSION', '19.1FBoxf');
session_start();

$configFile = 'config.json';

/**
 * Inline markdown-mini for panel headings and link names. Escapes first (so the
 * stored text can never inject HTML), THEN converts a tiny emphasis vocabulary:
 *   ***x*** -> bold-italic   **x** -> bold   *x* -> italic   __x__ -> underline
 * Underline uses DOUBLE underscore so single underscores in names/filenames
 * (snake_case, my_file_name) are left alone. Markers are non-greedy and must
 * wrap non-empty, non-marker text. Anything else (stray or unmatched markers)
 * stays as escaped literal text. Editing always shows the raw markers - only the
 * rendered display is formatted.
 */
function lms_format_inline($text) {
    $s = htmlspecialchars((string)$text, ENT_QUOTES);
    $s = preg_replace('~\*\*\*(\S(?:.*?\S)?)\*\*\*~s', '<strong><em>$1</em></strong>', $s);
    $s = preg_replace('~\*\*(\S(?:.*?\S)?)\*\*~s',     '<strong>$1</strong>', $s);
    $s = preg_replace('~\*(\S(?:.*?\S)?)\*~s',         '<em>$1</em>', $s);
    $s = preg_replace('~__(\S(?:.*?\S)?)__~s',         '<u>$1</u>', $s);
    return $s;
}

/**
 * Normalize an admin-entered link URL. We assume https:// for a bare host
 * (e.g. "example.com") so it doesn't silently become a broken relative link,
 * while leaving alone anything the admin made explicit: a full scheme
 * (http://, https://, mailto:, tel: ...), a root- or protocol-relative path
 * ("/dash", "//cdn..."), or an in-page anchor ("#top"). Deliberate http://
 * intranet links are preserved on purpose - this is an internal portal.
 */
function lms_normalize_url($url) {
    $u = trim((string)$url);
    if ($u === '') return $u;
    if (preg_match('~^[a-z][a-z0-9+.\-]*://~i', $u)) return $u;    // hierarchical scheme (http://, https://, ftp://)
    if (preg_match('~^(mailto|tel|sms|callto):~i', $u)) return $u; // schemeless schemes
    if ($u[0] === '/' || $u[0] === '#') return $u;                 // relative / protocol-relative / anchor
    return 'https://' . $u;                                        // bare host -> assume https
}

// =======================================================================
// 1. AJAX SAVE HANDLERS (Called via Javascript when you edit something)
// =======================================================================
if (isset($_GET['action'])) {
    if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
        die(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
    }

    $update = json_decode(file_get_contents("php://input"), true);
    $data = json_decode(file_get_contents($configFile), true);

    if ($_GET['action'] == 'save') {
        if ($update['type'] === 'site') {
            $data['site'][$update['target']] = $update['value'];
        } elseif ($update['type'] === 'panel') {
            // "Edit Panel" dialog: heading text + the hide-panel flag, saved together
            $data['panels'][$update['panel_id']]['heading'] = $update['heading_text'];
            $data['panels'][$update['panel_id']]['hidden'] = (bool)($update['hidden'] ?? false);
        } elseif ($update['type'] === 'link') {
            $p = $update['panel_id'];
            $l = $update['link_id'];
            $data['panels'][$p]['links'][$l]['name'] = $update['name'];
            $data['panels'][$p]['links'][$l]['url'] = lms_normalize_url($update['url']);
            $data['panels'][$p]['links'][$l]['tooltip'] = $update['tooltip'];
            $data['panels'][$p]['links'][$l]['new_tab'] = $update['new_tab'];
            $data['panels'][$p]['links'][$l]['html'] = $update['html'] ?? ''; // raw HTML; used only when url is empty
        } elseif ($update['type'] === 'new_link') {
            $data['panels'][$update['panel_id']]['links'][] = [
                "name" => $update['name'], "url" => lms_normalize_url($update['url']), "tooltip" => $update['tooltip'], "new_tab" => $update['new_tab'], "html" => $update['html'] ?? ''
            ];
        }
        file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
        echo json_encode(['status' => 'success']);
        exit;
    }

    // Handler specifically for Drag-and-Drop silent background saves
    if ($_GET['action'] == 'save_layout') {
        if (isset($update['panels'])) {
            $data['panels'] = $update['panels'];
            file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
            echo json_encode(['status' => 'success']);
            exit;
        }
    }

    // Delete a single link from a panel (array_splice re-indexes the links array)
    if ($_GET['action'] == 'delete_link') {
        $p = $update['panel_id'];
        $l = (int)$update['link_id'];
        if (isset($data['panels'][$p]['links'][$l])) {
            array_splice($data['panels'][$p]['links'], $l, 1);
            file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
            echo json_encode(['status' => 'success']);
            exit;
        }
        echo json_encode(['status' => 'error', 'message' => 'Link not found']);
        exit;
    }

    // Delete an entire panel (confirmed in the Edit Panel dialog). Keys may become
    // non-contiguous; that is fine — the next drag-reorder renumbers them 1..N.
    if ($_GET['action'] == 'delete_panel') {
        $p = $update['panel_id'];
        if (isset($data['panels'][$p])) {
            unset($data['panels'][$p]);
            file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
            echo json_encode(['status' => 'success']);
            exit;
        }
        echo json_encode(['status' => 'error', 'message' => 'Panel not found']);
        exit;
    }

    // Append a new empty panel after the last one (no confirmation — easy to delete/hide).
    if ($_GET['action'] == 'add_panel') {
        $keys = array_map('intval', array_keys($data['panels'] ?? []));
        $newKey = ($keys ? max($keys) : 0) + 1;
        $data['panels'][(string)$newKey] = ["heading" => "", "links" => []];
        file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
        echo json_encode(['status' => 'success', 'panel_id' => $newKey]);
        exit;
    }

    // Rotate the admin credentials. Re-verify the CURRENT username+password against the
    // stored hash before writing the new one -- being logged in is not enough to change
    // the login (guards an unlocked/hijacked admin screen). 'fingerprint' is the cred key
    // (defined later as $credKey; the action block runs before that, so use the literal).
    if ($_GET['action'] == 'change_creds') {
        $curUser = $update['cur_username'] ?? '';
        $curPass = $update['cur_password'] ?? '';
        $newUser = trim((string)($update['new_username'] ?? ''));
        $newPass = (string)($update['new_password'] ?? '');
        if (empty($data['fingerprint']) || !password_verify($curUser . "\x1f" . $curPass, $data['fingerprint'])) {
            echo json_encode(['status' => 'error', 'message' => 'Current username or password is incorrect.']);
            exit;
        }
        if ($newUser === '' || $newPass === '') {
            echo json_encode(['status' => 'error', 'message' => 'New username and password are both required.']);
            exit;
        }
        $data['fingerprint'] = password_hash($newUser . "\x1f" . $newPass, PASSWORD_DEFAULT);
        file_put_contents($configFile, json_encode($data, JSON_PRETTY_PRINT));
        echo json_encode(['status' => 'success']);
        exit;
    }
}

// =======================================================================
// 2. AUTO-GENERATE DEFAULT CONFIGURATION (If it doesn't exist)
// =======================================================================
if (!file_exists($configFile)) {
    $defaultConfig = [
        "_lms_version" => LMS_VERSION, // ties this data file to the code that generated it
        "site" => [
            "menu_title" => "Main Menu",
            "banner_image" => "lms_banner.png",
            "footer_html" => "Link Menu System &copy; 2026 smisco<br/>Your copyright and details go here.",
            "theme" => "default",
            "max_columns" => "3",
            "external_url" => "https://smisco.dev/lms/",
            "default_new_tab" => "true",
            "use_card_effect" => "false",
            "width_pct" => "85"
        ],
        // Synthetic demo seed (generic team start page). 6 categories; links on 5
        // panels; panel 2 ("Pinned") is intentionally left empty.
        "panels" => [
            "1" => ["heading" => "Productivity", "links" => [
                ["name" => "Email", "url" => "https://mail.google.com/", "tooltip" => "Team inbox"],
                ["name" => "Calendar", "url" => "https://calendar.google.com/", "tooltip" => "Shared calendar"],
                ["name" => "Drive", "url" => "https://drive.google.com/", "tooltip" => "Shared files"],
                ["name" => "Docs", "url" => "https://docs.google.com/", "tooltip" => ""]
            ]],
            "2" => ["heading" => "Pinned", "links" => []],
            "3" => ["heading" => "Development", "links" => [
                ["name" => "GitHub", "url" => "https://github.com/", "tooltip" => "Repositories and issues"],
                ["name" => "Stack Overflow", "url" => "https://stackoverflow.com/", "tooltip" => ""],
                ["name" => "MDN Web Docs", "url" => "https://developer.mozilla.org/", "tooltip" => "Web platform reference"],
                ["name" => "npm", "url" => "https://www.npmjs.com/", "tooltip" => ""]
            ]],
            "4" => ["heading" => "Operations", "links" => [
                ["name" => "Status Page", "url" => "https://status.example.com/", "tooltip" => "Live service status"],
                ["name" => "Monitoring", "url" => "https://grafana.com/", "tooltip" => "Dashboards and alerts"],
                ["name" => "Cloud Console", "url" => "https://console.cloud.google.com/", "tooltip" => ""],
                ["name" => "On-Call", "url" => "https://www.pagerduty.com/", "tooltip" => "Incident schedule"]
            ]],
            "5" => ["heading" => "Support", "links" => [
                ["name" => "Help Desk", "url" => "https://support.example.com/", "tooltip" => "Open a ticket"],
                ["name" => "Knowledge Base", "url" => "https://kb.example.com/", "tooltip" => "How-to articles"],
                ["name" => "Ticket Queue", "url" => "https://tickets.example.com/", "tooltip" => ""]
            ]],
            "6" => ["heading" => "Reference", "links" => [
                ["name" => "Company Wiki", "url" => "https://wiki.example.com/", "tooltip" => "Internal documentation"],
                ["name" => "HR Portal", "url" => "https://hr.example.com/", "tooltip" => "Time off and benefits"],
                ["name" => "Directory", "url" => "https://directory.example.com/", "tooltip" => "Staff phone list"],
                ["name" => "Brand Assets", "url" => "https://brand.example.com/", "tooltip" => "Logos and templates"]
            ]]
        ]
    ];
    file_put_contents($configFile, json_encode($defaultConfig, JSON_PRETTY_PRINT));
}

// =======================================================================
// 3. AUTHENTICATION & FIRST-RUN SETUP
// =======================================================================
$config = json_decode(file_get_contents($configFile), true);

// Admin credentials are a SINGLE bcrypt hash of (username + "\x1f" + password), stored
// under an innocuous config key -- no separate file, no plaintext username, no obvious
// indication which part is which, and an attacker must crack both halves together.
// NOTE: obscurity is not the real safeguard (this is open source) -- moving instance
// state out of the webroot is (see TODO). bcrypt truncates at 72 bytes (fine for creds).
$credKey = 'fingerprint';
$setupMode = empty($config[$credKey]);
$loginError = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_POST['setup_admin']) && $setupMode) {
        $config[$credKey] = password_hash($_POST['username'] . "\x1f" . $_POST['password'], PASSWORD_DEFAULT);
        file_put_contents($configFile, json_encode($config, JSON_PRETTY_PRINT));
        $_SESSION['is_admin'] = true;
        header("Location: index.php"); exit;
    } elseif (isset($_POST['login'])) {
        if (!$setupMode && password_verify($_POST['username'] . "\x1f" . $_POST['password'], $config[$credKey])) {
            $_SESSION['is_admin'] = true;
        } else {
            $loginError = "Invalid credentials!";
        }
    }
}

if (isset($_GET['logout'])) {
    session_destroy();
    header("Location: index.php"); exit;
}

$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'];

// Safely inject defaults for legacy config compatibility
$siteMenuTitle = $config['site']['menu_title'] ?? 'Main Menu';
$siteTheme = $config['site']['theme'] ?? 'default';
$siteMaxColumns = $config['site']['max_columns'] ?? '3';
$siteExternalUrl = $config['site']['external_url'] ?? 'https://smisco.dev/lms/';
$siteDefaultNewTab = $config['site']['default_new_tab'] ?? 'true';
$siteUseCardEffect = $config['site']['use_card_effect'] ?? 'false';
// Grid container width as a % of viewport (centered). Per-install display preference,
// config-only (no admin UI); defaults to 85 if unset or out of the 1-100 range.
$siteWidthPct = (float)($config['site']['width_pct'] ?? '85');
if ($siteWidthPct <= 0 || $siteWidthPct > 100) { $siteWidthPct = 85; }

?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($siteMenuTitle ?: 'Main Menu') ?></title>
<style type="text/css">
    /* GLOBAL THEME VARIABLES */
    :root {
        --bg-page: white;
        --bg-header: skyblue;
        --bg-panel: mediumblue;
        --bg-panel-hover: darkred;
        --text-primary: mediumblue;
        --text-link: ivory;
        --text-link-hover: gold;
    }

    BODY { FONT-SIZE: large; COLOR: var(--text-primary); FONT-FAMILY: "trebuchet ms", Verdana, Arial, sans-serif; BACKGROUND-COLOR: var(--bg-page); MARGIN: 0; }
    H1 { FONT-SIZE: x-large; }
    A { COLOR: var(--text-link); TEXT-DECORATION: none; }
    A:hover { COLOR: var(--text-link-hover); }

    .footer { TEXT-ALIGN: center; FONT-SIZE: small; margin-top: 30px; margin-bottom: 20px;}
    a.afooter { color: var(--text-primary); }
    a.afooter:hover { color: darkgoldenrod; }

    /* CSS GRID LAYOUT — fluid: fills site.width_pct% of the window (centered), holding at
       max_columns tracks on wide displays and stepping down one column at a time as the
       viewport narrows (...->4->3->2->1). Each track's minimum is a fair share of the
       container (100%/cols, gap-adjusted) so no more than max_columns ever fit; 1fr lets
       those tracks grow to fill the container width. The 250px floor sets the step-down
       points on narrow screens. width_pct (config.json site block) sets the fill width. */
    .portal-grid {
        --cols: <?= max(1, (int)$siteMaxColumns) ?>;
        --gap: 0px;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(max(250px, (100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr));
        width: <?= $siteWidthPct ?>%;
        margin: 0 auto;
        border: 0;
    }

    /* MOBILE RESPONSIVENESS */
    @media (max-width: 768px) {
        BODY { padding: 10px; box-sizing: border-box; }
        H1 img { max-width: 100%; height: auto; }
    }
    /* User mode: drop empty spacer panels once the grid is below 3 columns (< 3 * min track) */
    @media (max-width: 749px) {
        .portal-grid.user-mode .panel.is-empty { display: none; }
    }

    .panel { display: flex; flex-direction: column; background: var(--bg-panel); transition: transform 0.2s; }
    .panel-header { background: var(--bg-header); padding: 10px; text-align: center; font-weight: bold; min-height: 24px; transition: background-color 0.2s, color 0.2s;}
    .panel-header.editable:hover { background-color: var(--bg-panel); color: var(--text-link-hover); }
    .panel-content { padding: 15px; text-align: center; height: 100%; transition: background-color 0.2s; min-height: 50px; display: flex; flex-direction: column;}
    .panel-content:hover { background-color: var(--bg-panel-hover); }
    .panel.is-empty .panel-content:hover { background-color: transparent; }
    /* Admin-only: a panel hidden from users shows here dimmed, with a HIDDEN tag so it
       can still be unhidden/edited/deleted. (In user mode it is not rendered at all.) */
    .panel.is-hidden { opacity: 0.5; position: relative; }
    .panel.is-hidden .hidden-tag {
        position: absolute; top: 6px; right: 6px; z-index: 2;
        background: darkred; color: ivory; font-size: 10px; font-weight: bold;
        letter-spacing: 1px; padding: 2px 6px; border-radius: 3px; pointer-events: none;
    }

    /* CARD EFFECT (toggled by the "Use Card Effect?" admin setting) */
    .portal-grid.cards { --gap: 18px; gap: var(--gap); padding: 10px; box-sizing: border-box; }
    .portal-grid.cards .panel { border-radius: 10px; overflow: hidden; }

    .link-item { margin: 8px 0; padding: 2px; }
    .add-btn-wrapper { margin-top: auto; padding-top: 15px; }

    /* DRAG AND DROP STYLES */
    .drag-over { border: 2px dashed gold !important; opacity: 0.8; }
    .is-dragging { opacity: 0.4; }

    /* ADMIN & UI STYLES */
    .admin-bar { background: #222; color: #ddd; padding: 10px; text-align: right; font-size: 14px; }
    .admin-bar input { margin: 0 5px; }

    .editable { cursor: pointer; outline: 1px dashed transparent; transition: all 0.2s; position: relative;}
    .editable:hover { outline: 1px dashed gold; }

    .edit-badge { display: none; position: absolute; top: -10px; right: -10px; background: #222; color: gold; font-size: 11px; padding: 3px 6px; cursor: pointer; z-index: 10; border-radius: 3px; border: 1px solid gold; font-weight: normal; font-family: sans-serif; box-shadow: 0 2px 5px rgba(0,0,0,0.5);}
    .editable:hover > .edit-badge,
    .edit-badge:hover { display: inline-block; }
    .editable-inline > .edit-badge { top: 0; right: auto; left: 100%; margin-left: 0; }
    .setting-row > .edit-badge { top: 10px; right: 15px; left: auto; }
    .panel-header > .edit-badge { top: 50%; transform: translateY(-50%); right: 10px; left: auto; }

    .add-btn { background: #444; color: white; border: none; padding: 2px 8px; cursor: pointer; font-size: 11px; border-radius: 3px; }
    .add-btn:hover { background: #666; }

    .admin-settings-block { margin-top: 50px; padding: 20px; background: #eef2f5; border-top: 3px solid #ccc; font-size: 14px; color: #333; max-width: 800px; margin-left: auto; margin-right: auto;}
    .setting-row { padding: 10px 15px; margin-bottom: 5px; border-radius: 4px; text-align: left; position: relative; }

    /* BILLIARD-BALL COLUMN SELECTOR */
    .col-selector { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
    .col-ball { width: 34px; height: 34px; padding: 0; border-radius: 50%; border: 2px solid var(--text-primary); background: white; color: var(--text-primary); font-weight: bold; font-size: 14px; line-height: 1; cursor: pointer; box-shadow: inset 0 -2px 4px rgba(0,0,0,0.15); transition: background-color 0.15s, color 0.15s; }
    .col-ball:hover { background: #e6e9ff; }
    .col-ball.selected { background: var(--text-primary); color: white; box-shadow: inset 0 2px 4px rgba(0,0,0,0.35); }

    /* BILLIARD-STYLE TOGGLE (boolean settings): empty circle = off, filled + check = on */
    .ball-check { -webkit-appearance: none; appearance: none; width: 28px; height: 28px; flex: none; margin: 0; padding: 0; border-radius: 50%; border: 2px solid var(--text-primary); background: white; cursor: pointer; position: relative; box-shadow: inset 0 -2px 4px rgba(0,0,0,0.15); transition: background-color 0.15s; }
    .ball-check:hover { background: #e6e9ff; }
    .ball-check:checked { background: var(--text-primary); box-shadow: inset 0 2px 4px rgba(0,0,0,0.35); }
    .ball-check:checked::after { content: "\2713"; color: white; font-size: 16px; font-weight: bold; line-height: 1; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }

    /* MODAL STYLES */
    .modal { display: none; position: fixed; top: 15%; left: 50%; transform: translateX(-50%); background: white; padding: 25px; border: 3px solid var(--text-primary); box-shadow: 0 10px 25px rgba(0,0,0,0.5); z-index: 1000; width: 450px; color: #333; border-radius: 5px; }
    .modal input[type="text"], .modal input[type="password"], .modal textarea { width: 100%; margin-bottom: 15px; padding: 8px; box-sizing: border-box; font-family: inherit; border: 1px solid #ccc; border-radius: 3px; }
    .modal label { display: block; font-weight: bold; margin-bottom: 5px; font-size: 14px;}
    #modalOverlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 999; }
    .btn-save { background: mediumblue; color: white; padding: 8px 15px; border: none; cursor: pointer; border-radius: 3px; font-weight: bold; }
    .btn-cancel { background: #888; color: white; padding: 8px 15px; border: none; cursor: pointer; border-radius: 3px; margin-left: 10px;}
    .btn-delete { background: #b00020; color: white; padding: 8px 15px; border: none; cursor: pointer; border-radius: 3px; font-weight: bold; }
    .btn-delete:hover { background: #d32f2f; }
</style>

<?php if ($siteTheme !== 'default'): ?>
    <!-- INJECT CUSTOM THEME IF NOT DEFAULT -->
    <link rel="stylesheet" href="themes/<?= htmlspecialchars($siteTheme) ?>.css">
<?php endif; ?>

</head>
<body>

<!-- SYSTEM TOP BAR (Only visible to Admin or on First-Run) -->
<?php if ($setupMode || $isAdmin): ?>
<div class="admin-bar">
    <?php if ($setupMode): ?>
        <strong style="color: gold;">First Run Setup:</strong> Create your Admin account
        <form method="POST" style="display:inline; margin-left: 10px;">
            User: <input type="text" name="username" required>
            Pass: <input type="password" name="password" required>
            <input type="submit" name="setup_admin" value="Secure Site">
        </form>
    <?php elseif ($isAdmin): ?>
        <strong>Logged in as Admin.</strong> Double-click any header, link, banner, or setting to edit. Drag and drop panels or links to reorder.
        <a href="?logout=1" style="color: #ff6666;margin-left: 1rem;">Logout</a>
    <?php endif; ?>
</div>
<?php endif; ?>

<center>
    <!-- EDITABLE BANNER -->
    <h1>
        <a href="<?= htmlspecialchars($siteExternalUrl) ?>" target="_blank" style="display:inline-block;" <?php if($isAdmin) echo 'onclick="return false;"'; ?>>
            <span style="position:relative; display:inline-block;" <?php if($isAdmin) echo "class='editable editable-inline' data-type='site' data-target='banner_image'"; ?>>
                <img src="<?= htmlspecialchars($config['site']['banner_image']) ?>" height="91">
                <?php if($isAdmin) echo '<span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>'; ?>
            </span>
        </a>
    </h1>

    <!-- CSS GRID PANELS -->
    <div class="portal-grid <?php echo filter_var($siteUseCardEffect, FILTER_VALIDATE_BOOLEAN) ? 'cards' : ''; ?> <?php echo $isAdmin ? '' : 'user-mode'; ?>">
        <?php foreach ($config['panels'] as $id => $panel): ?>
            <?php
                $panelEmpty = ($panel['heading'] === '' && empty($panel['links']));
                $panelHidden = filter_var($panel['hidden'] ?? false, FILTER_VALIDATE_BOOLEAN);
                // User mode: a hidden panel is not rendered at all (admin still sees it, dimmed).
                if ($panelHidden && !$isAdmin) continue;
                // User mode: omit empty spacer panels when the desktop grid is < 3 columns
                // (the mobile single-column case is handled by CSS). Spacers only earn their keep at 3-up.
                if ($panelEmpty && !$isAdmin && (int)$siteMaxColumns < 3) continue;
            ?>
            <div class="panel <?php echo $panelEmpty ? 'is-empty' : ''; ?> <?php echo ($panelHidden && $isAdmin) ? 'is-hidden' : ''; ?>" <?php if($isAdmin) echo 'draggable="true"'; ?> data-hidden="<?= $panelHidden ? 'true' : 'false' ?>">
                <?php if($panelHidden && $isAdmin) echo '<span class="hidden-tag">HIDDEN</span>'; ?>
                <div class="panel-header <?php if($isAdmin) echo 'editable editable-inline'; ?>" data-type="panel" data-panel="<?= $id ?>" data-heading-val="<?= htmlspecialchars($panel['heading']) ?>">
                    <?= lms_format_inline($panel['heading']) ?>
                    <?php if($isAdmin) echo '<span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>'; ?>
                </div>
                <div class="panel-content">
                    <?php foreach ($panel['links'] as $linkId => $link): ?>
                        <?php
                            // Determine new tab behavior (fallback to global default if not explicitly set on this link)
                            $isNewTab = isset($link['new_tab']) ? filter_var($link['new_tab'], FILTER_VALIDATE_BOOLEAN) : filter_var($siteDefaultNewTab, FILTER_VALIDATE_BOOLEAN);
                            $targetStr = $isNewTab ? '_blank' : '_self';
                            // URL wins; an HTML block is an item with no URL but raw HTML content.
                            $linkUrl  = trim((string)($link['url'] ?? ''));
                            $linkHtml = (string)($link['html'] ?? '');
                            $isHtmlBlock = ($linkUrl === '' && trim($linkHtml) !== '');
                        ?>
                        <?php if ($isHtmlBlock): ?>
                            <?php if ($isAdmin): ?>
                                <!-- Admin sees an editable placeholder, not the live widget,
                                     so its own JS/clicks can't fight drag-reorder or editing. -->
                                <div class="link-item html-block-admin editable editable-inline" draggable="true"
                                     style="position:relative; cursor:move;"
                                     data-type="link"
                                     data-panel="<?= $id ?>"
                                     data-link="<?= $linkId ?>"
                                     data-name="<?= htmlspecialchars($link['name'] ?? '') ?>"
                                     data-url=""
                                     data-html="<?= htmlspecialchars($linkHtml) ?>"
                                     data-tooltip="<?= htmlspecialchars($link['tooltip'] ?? '') ?>"
                                     data-new-tab="<?= $isNewTab ? 'true' : 'false' ?>">
                                    &#129513; HTML block <span style="opacity:.55;">&mdash; double-click to edit</span>
                                    <span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>
                                </div>
                            <?php else: ?>
                                <div class="link-item html-block"><?= $linkHtml ?></div>
                            <?php endif; ?>
                        <?php else: ?>
                        <p class="link-item" <?php if($isAdmin) echo 'draggable="true"'; ?> style="position:relative; cursor: <?= $isAdmin ? 'move' : 'default' ?>;">
                            <a href="<?= htmlspecialchars($linkUrl) ?>"
                               title="<?= htmlspecialchars($link['tooltip'] ?? '') ?>"
                               target="<?= $targetStr ?>"
                               <?php if($isAdmin): ?>
                                   class="editable editable-inline"
                                   onclick="return false;"
                                   data-type="link"
                                   data-panel="<?= $id ?>"
                                   data-link="<?= $linkId ?>"
                                   data-name="<?= htmlspecialchars($link['name'] ?? '') ?>"
                                   data-url="<?= htmlspecialchars($linkUrl) ?>"
                                   data-html="<?= htmlspecialchars($link['html'] ?? '') ?>"
                                   data-tooltip="<?= htmlspecialchars($link['tooltip'] ?? '') ?>"
                                   data-new-tab="<?= $isNewTab ? 'true' : 'false' ?>"
                               <?php endif; ?>
                            >
                               <?= lms_format_inline($link['name'] ?? '') ?>
                               <?php if($isAdmin) echo '<span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>'; ?>
                            </a>
                        </p>
                        <?php endif; ?>
                    <?php endforeach; ?>

                    <?php if($isAdmin): ?>
                        <div class="add-btn-wrapper">
                            <button class="add-btn" onclick="openNewLinkModal(this)">+ Add Link</button>
                        </div>
                    <?php endif; ?>
                </div>
            </div>
        <?php endforeach; ?>
    </div>

    <?php if ($isAdmin): ?>
        <!-- ADMIN SETTINGS BLOCK -->
        <div class="admin-settings-block">
            <h4 style="margin-top: 0; text-align: center;">⚙️ Administrator Settings</h4>

            <div class="editable setting-row" data-type="site" data-target="menu_title" data-value="<?= htmlspecialchars($siteMenuTitle) ?>">
                <strong>Menu Title (browser tab):</strong> <span style="color: var(--text-primary);"><?= htmlspecialchars($siteMenuTitle) ?></span>
                <span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>
            </div>

            <div class="editable setting-row" data-type="site" data-target="external_url" data-value="<?= htmlspecialchars($siteExternalUrl) ?>">
                <strong>Banner Link (Home URL):</strong> <span style="color: var(--text-primary);"><?= htmlspecialchars($siteExternalUrl) ?></span>
                <span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>
            </div>

            <div class="setting-row">
                <strong>Theme:</strong>
                <select onchange="setTheme(this.value)" style="font-family:inherit; font-size:14px; padding:6px 10px; border:2px solid var(--text-primary); border-radius:6px; background:white; color:var(--text-primary); cursor:pointer; margin-top:4px;">
                    <option value="default"<?= $siteTheme === 'default' ? ' selected' : '' ?>>default</option>
                    <?php foreach (glob('themes/*.css') ?: [] as $themeFile):
                        $tname = basename($themeFile, '.css');
                        if ($tname === 'default') continue; ?>
                        <option value="<?= htmlspecialchars($tname) ?>"<?= $siteTheme === $tname ? ' selected' : '' ?>><?= htmlspecialchars($tname) ?></option>
                    <?php endforeach; ?>
                </select>
                <div style="font-size:11px; color:#666; margin-top:2px;">Loads <code>themes/{name}.css</code>; drop new theme CSS files in the <code>themes/</code> folder.</div>
            </div>

            <div class="setting-row">
                <strong>Maximum Display Columns:</strong>
                <div class="col-selector">
                    <?php for ($n = 1; $n <= 10; $n++): ?>
                        <button type="button" class="col-ball <?php echo ((int)$siteMaxColumns === $n) ? 'selected' : ''; ?>" onclick="setColumns(<?= $n ?>)"><?= $n ?></button>
                    <?php endfor; ?>
                </div>
            </div>

            <div class="setting-row">
                <label style="cursor:pointer; font-weight:bold; display:flex; align-items:center; gap:8px;">
                    <input type="checkbox" class="ball-check" <?= filter_var($siteDefaultNewTab, FILTER_VALIDATE_BOOLEAN) ? 'checked' : '' ?> onchange="toggleBool('default_new_tab', this.checked)">
                    Default Open Links in New Tab
                </label>
            </div>

            <div class="setting-row">
                <label style="cursor:pointer; font-weight:bold; display:flex; align-items:center; gap:8px;">
                    <input type="checkbox" class="ball-check" <?= filter_var($siteUseCardEffect, FILTER_VALIDATE_BOOLEAN) ? 'checked' : '' ?> onchange="toggleBool('use_card_effect', this.checked)">
                    Use Card Effect (rounded corners + spacing between panels)
                </label>
            </div>

            <div class="setting-row" style="text-align:center; border-top: 1px solid #ddd; padding-top: 15px;">
                <button type="button" class="add-btn" onclick="addPanel()">+ Add Panel</button>
                <div style="font-size:11px; color:#666; margin-top:4px;">Adds an empty panel after the last one. Use drag-and-drop to reposition it.</div>
            </div>

            <div class="setting-row" style="text-align:center;">
                <button type="button" class="add-btn" onclick="openCredsModal()">Change Login</button>
                <div style="font-size:11px; color:#666; margin-top:4px;">Set a new admin username and password. Requires your current login.</div>
            </div>

            <div style="text-align:center; margin-top: 20px; font-size: 11px; color: #999;">
                Link Menu System <?= LMS_VERSION ?> &copy; 2026 smisco
            </div>
        </div>
    <?php endif; ?>

    <!-- EDITABLE FOOTER WITH HIDDEN LOGIN -->
    <h5 class="footer">
        <span style="position:relative; display:inline-block;" <?php if($isAdmin) echo "class='editable editable-inline' data-type='site' data-target='footer_html'"; ?>>
            <?= $config['site']['footer_html'] ?>
            <?php if($isAdmin) echo '<span class="edit-badge" onclick="triggerEdit(event, this.parentElement)">✏️ Edit</span>'; ?>
        </span>

        <?php /* Optional per-install attribution: rendered (after footer_html) only if set in
                 config.json's site.copyright. No admin UI — hand-edited per install. */ ?>
        <?php if (!empty($config['site']['copyright'])): ?>
            <br><br><?= $config['site']['copyright'] ?>
        <?php endif; ?>

        <?php if (!$isAdmin && !$setupMode): ?>
            <!-- Discreet Lock Icon for standard users -->
            <br><br>
            <span style="cursor:pointer; opacity: 0.15; font-size: 14px;" onclick="openLoginModal()" title="Admin Login">🔒</span>
        <?php endif; ?>
    </h5>

</center>

<!-- ======================================================= -->
<!-- MODALS -->
<!-- ======================================================= -->
<div id="modalOverlay"></div>

<?php if (!$isAdmin && !$setupMode): ?>
<!-- LOGIN MODAL -->
<div id="loginModal" class="modal">
    <h2 style="margin-top:0; border-bottom: 2px solid #ccc; padding-bottom:10px;">Admin Login</h2>
    <form method="POST">
        <?php if(isset($loginError)) echo "<p style='color:red; margin-top:0;'>$loginError</p>"; ?>
        <label>Username:</label>
        <input type="text" id="loginUsername" name="username" required>
        <label>Password:</label>
        <input type="password" name="password" required>
        <div style="margin-top: 20px; text-align: right;">
            <button type="button" class="btn-cancel" onclick="document.getElementById('loginModal').style.display='none'; document.getElementById('modalOverlay').style.display='none';">Cancel</button>
            <button type="submit" name="login" class="btn-save">Login</button>
        </div>
    </form>
</div>
<script>
    function openLoginModal() {
        document.getElementById('loginModal').style.display='block';
        document.getElementById('modalOverlay').style.display='block';
        setTimeout(function() {
            var u = document.getElementById('loginUsername');
            if (u) { u.focus(); u.select(); }
        }, 0);
    }
    <?php if(isset($loginError)): ?>
    openLoginModal();
    <?php endif; ?>
</script>
<?php endif; ?>

<?php if ($isAdmin): ?>
<!-- CHANGE CREDENTIALS MODAL -->
<div id="credsModal" class="modal">
    <h2 style="margin-top:0; border-bottom: 2px solid #ccc; padding-bottom:10px;">Change Login</h2>
    <p id="credsError" style="color:red; margin-top:0; display:none;"></p>

    <label>Current Username:</label>
    <input type="text" id="credsCurUser" autocomplete="off">
    <label>Current Password:</label>
    <input type="password" id="credsCurPass" autocomplete="off">

    <hr style="border:none; border-top:1px solid #ddd; margin:16px 0;">

    <label>New Username:</label>
    <input type="text" id="credsNewUser" autocomplete="off">
    <label>New Password:</label>
    <input type="password" id="credsNewPass" autocomplete="off">
    <label>Confirm New Password:</label>
    <input type="password" id="credsNewPass2" autocomplete="off">

    <div style="margin-top: 20px; text-align: right;">
        <button type="button" class="btn-cancel" onclick="closeCredsModal()">Cancel</button>
        <button type="button" class="btn-save" onclick="submitCreds()">Save New Login</button>
    </div>
</div>
<script>
    function openCredsModal() {
        document.getElementById('credsError').style.display = 'none';
        ['credsCurUser','credsCurPass','credsNewUser','credsNewPass','credsNewPass2']
            .forEach(function(id){ document.getElementById(id).value = ''; });
        document.getElementById('credsModal').style.display = 'block';
        document.getElementById('modalOverlay').style.display = 'block';
        setTimeout(function(){ document.getElementById('credsCurUser').focus(); }, 0);
    }
    function closeCredsModal() {
        document.getElementById('credsModal').style.display = 'none';
        document.getElementById('modalOverlay').style.display = 'none';
    }
    function credsErr(msg) {
        var e = document.getElementById('credsError');
        e.innerText = msg; e.style.display = 'block';
    }
    function submitCreds() {
        var curUser = document.getElementById('credsCurUser').value;
        var curPass = document.getElementById('credsCurPass').value;
        var newUser = document.getElementById('credsNewUser').value.trim();
        var newPass = document.getElementById('credsNewPass').value;
        var newPass2 = document.getElementById('credsNewPass2').value;
        if (!newUser || !newPass) { credsErr('New username and password are both required.'); return; }
        if (newPass !== newPass2) { credsErr('New passwords do not match.'); return; }
        fetch('?action=change_creds', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ cur_username: curUser, cur_password: curPass, new_username: newUser, new_password: newPass })
        }).then(function(res){ return res.json(); }).then(function(data){
            if (data.status === 'success') {
                closeCredsModal();
                alert('Login updated. Use your new username and password next time you log in.');
            } else {
                credsErr(data.message || 'Could not update login.');
            }
        }).catch(function(){ credsErr('Network error - try again.'); });
    }
</script>

<!-- EDIT DATA MODAL -->
<div id="editModal" class="modal">
    <h2 id="modalTitle" style="margin-top:0; border-bottom: 2px solid #ccc; padding-bottom:10px;">Edit Item</h2>

    <input type="hidden" id="editType">
    <input type="hidden" id="editPanel">
    <input type="hidden" id="editLink">
    <input type="hidden" id="editTarget">

    <!-- Field: Single Text Input -->
    <div id="singleFieldBlock" style="display:none;">
        <label id="singleFieldLabel">Value:</label>
        <input type="text" id="editSingleValue">
        <textarea id="editSingleTextarea" rows="5" style="display:none;"></textarea>
    </div>

    <!-- Field: Panel options (Edit Panel dialog only) -->
    <div id="panelOptionsBlock" style="display:none;">
        <label style="margin-top: 10px; cursor: pointer; font-weight: normal;">
            <input type="checkbox" id="editPanelHidden" style="width: auto; margin-bottom: 0;"> Hide panel? (hidden from users; still visible to you here)
        </label>
    </div>

    <!-- Field: Link Details -->
    <div id="linkFieldsBlock" style="display:none;">
        <label>Link Name:</label>
        <input type="text" id="editLinkName">

        <label>URL (https:// recommended; or / for a local path):</label>
        <input type="text" id="editLinkUrl">

        <label>Raw HTML (optional &mdash; used only when URL is empty; renders a widget in place of a link):</label>
        <textarea id="editLinkHtml" rows="4" placeholder="e.g. an &lt;iframe&gt;, &lt;img&gt;, or small widget. Trusted admin HTML, rendered as-is &mdash; no sanitizing."></textarea>
        <div style="font-size:11px; color:#666; margin:-4px 0 4px;">If both URL and HTML are filled, the URL wins and this is ignored.</div>

        <label>Tooltip / Help Text (Optional):</label>
        <textarea id="editLinkTooltip" rows="3"></textarea>

        <label style="margin-top: 10px; cursor: pointer; font-weight: normal;">
            <input type="checkbox" id="editLinkNewTab" style="width: auto; margin-bottom: 0;"> Open in new tab/window?
        </label>
    </div>

    <div style="margin-top: 20px; display: flex; justify-content: space-between; align-items: center;">
        <button id="btnDelete" class="btn-delete" style="display:none;" onclick="deleteCurrent()">Delete</button>
        <div style="margin-left: auto;">
            <button class="btn-cancel" onclick="closeModal()">Cancel</button>
            <button class="btn-save" onclick="saveChanges()">Save Changes</button>
        </div>
    </div>
</div>

<script>
    // ==========================================
    // 1. MODAL EDIT ENGINE
    // ==========================================
    const modal = document.getElementById('editModal');
    const overlay = document.getElementById('modalOverlay');

    function triggerEdit(e, el) {
        e.preventDefault();
        e.stopPropagation();
        openEditorForElement(el);
    }

    document.querySelectorAll('.editable').forEach(el => {
        el.addEventListener('dblclick', function(e) {
            e.preventDefault();
            e.stopPropagation(); // prevent nested clicks from bubbling up
            openEditorForElement(this);
        });
    });

    function openEditorForElement(el) {
        resetModal();

        const type = el.getAttribute('data-type');
        document.getElementById('editType').value = type;

        if (type === 'site') {
            const target = el.getAttribute('data-target');
            document.getElementById('editTarget').value = target;
            document.getElementById('singleFieldBlock').style.display = 'block';

            if (target === 'footer_html') {
                document.getElementById('editSingleValue').style.display = 'none';
                document.getElementById('editSingleTextarea').style.display = 'block';
                document.getElementById('singleFieldLabel').innerText = 'Footer HTML:';

                // Clone the element and remove the edit badge to get clean HTML
                let clone = el.cloneNode(true);
                let badge = clone.querySelector('.edit-badge');
                if(badge) clone.removeChild(badge);
                document.getElementById('editSingleTextarea').value = clone.innerHTML.trim().replace(/<br\s*[\/]?>/gi, '\n');
            } else {
                document.getElementById('editSingleValue').style.display = 'block';
                document.getElementById('editSingleTextarea').style.display = 'none';

                if (target === 'banner_image') {
                    document.getElementById('singleFieldLabel').innerText = 'Banner Image URL/Path:';
                    document.getElementById('editSingleValue').value = el.querySelector('img').getAttribute('src');
                } else {
                    let labelText = 'Value:';
                    if (target === 'menu_title') labelText = 'Menu Title (shown in the browser tab):';
                    if (target === 'theme') labelText = 'Theme Name (e.g. "dark_mode" or "default"):';
                    if (target === 'external_url') labelText = 'Banner Link (Home URL):';
                    if (target === 'max_columns') labelText = 'Max Desktop Columns (e.g. "1", "2", "3"):';
                    if (target === 'default_new_tab') labelText = 'Default Open in New Tab ("true" or "false"):';
                    if (target === 'use_card_effect') labelText = 'Use Card Effect ("true" or "false"):';

                    document.getElementById('singleFieldLabel').innerText = labelText;
                    document.getElementById('editSingleValue').value = el.getAttribute('data-value');
                }
            }
            document.getElementById('modalTitle').innerText = "Edit Site Setting";
        }
        else if (type === 'panel') {
            document.getElementById('editPanel').value = el.getAttribute('data-panel');
            document.getElementById('singleFieldBlock').style.display = 'block';
            document.getElementById('singleFieldLabel').innerText = 'Panel Heading Text:';
            document.getElementById('editSingleValue').value = el.getAttribute('data-heading-val');
            document.getElementById('panelOptionsBlock').style.display = 'block';
            const panelEl = el.closest('.panel');
            document.getElementById('editPanelHidden').checked = panelEl && panelEl.getAttribute('data-hidden') === 'true';
            document.getElementById('modalTitle').innerText = "Edit Panel";
            const bdH = document.getElementById('btnDelete');
            bdH.style.display = 'inline-block';
            bdH.innerText = 'Delete Panel';
        }
        else if (type === 'link') {
            document.getElementById('editPanel').value = el.getAttribute('data-panel');
            document.getElementById('editLink').value = el.getAttribute('data-link');
            document.getElementById('linkFieldsBlock').style.display = 'block';
            document.getElementById('editLinkName').value = el.getAttribute('data-name');
            document.getElementById('editLinkUrl').value = el.getAttribute('data-url');
            document.getElementById('editLinkHtml').value = el.getAttribute('data-html') || '';
            document.getElementById('editLinkTooltip').value = el.getAttribute('data-tooltip');
            document.getElementById('editLinkNewTab').checked = el.getAttribute('data-new-tab') === 'true';
            document.getElementById('modalTitle').innerText = "Edit Link";
            const bdL = document.getElementById('btnDelete');
            bdL.style.display = 'inline-block';
            bdL.innerText = 'Delete Link';
        }
        showModal();
    }

    function openNewLinkModal(arg) {
        resetModal();
        document.getElementById('editType').value = 'new_link';
        // Read the panel id live from the DOM (the button passes `this`) so it stays
        // correct after a drag-reorder. Tolerate a bare id for any legacy caller.
        let panelId = arg;
        if (arg && typeof arg === 'object') {
            const panel = arg.closest('.panel');
            const header = panel && panel.querySelector('.panel-header');
            panelId = header ? header.getAttribute('data-panel') : '';
        }
        document.getElementById('editPanel').value = panelId;
        document.getElementById('linkFieldsBlock').style.display = 'block';

        // Check the box by default based on the global admin setting
        const globalNewTab = <?= json_encode(filter_var($siteDefaultNewTab, FILTER_VALIDATE_BOOLEAN)) ?>;
        document.getElementById('editLinkNewTab').checked = globalNewTab;

        document.getElementById('modalTitle').innerText = "Add New Link";
        showModal();
    }

    function resetModal() {
        document.getElementById('btnDelete').style.display = 'none';
        document.getElementById('singleFieldBlock').style.display = 'none';
        document.getElementById('linkFieldsBlock').style.display = 'none';
        document.getElementById('panelOptionsBlock').style.display = 'none';
        document.getElementById('editSingleValue').style.display = 'block';
        document.getElementById('editSingleTextarea').style.display = 'none';
        document.getElementById('editSingleValue').value = '';
        document.getElementById('editSingleTextarea').value = '';
        document.getElementById('editLinkName').value = '';
        document.getElementById('editLinkUrl').value = '';
        document.getElementById('editLinkHtml').value = '';
        document.getElementById('editLinkTooltip').value = '';
        document.getElementById('editLinkNewTab').checked = false;
        document.getElementById('editPanelHidden').checked = false;
    }

    function showModal() { modal.style.display = 'block'; overlay.style.display = 'block'; }
    function closeModal() { modal.style.display = 'none'; overlay.style.display = 'none'; }

    function saveChanges() {
        const type = document.getElementById('editType').value;
        const payload = { type: type, panel_id: document.getElementById('editPanel').value };

        if (type === 'site') {
            payload.target = document.getElementById('editTarget').value;
            if (payload.target === 'footer_html') {
                payload.value = document.getElementById('editSingleTextarea').value.replace(/\n/g, '<br/>');
            } else {
                payload.value = document.getElementById('editSingleValue').value;
            }
        } else if (type === 'panel') {
            payload.heading_text = document.getElementById('editSingleValue').value;
            payload.hidden = document.getElementById('editPanelHidden').checked;
        } else if (type === 'link' || type === 'new_link') {
            if(type === 'link') payload.link_id = document.getElementById('editLink').value;
            payload.name = document.getElementById('editLinkName').value;
            payload.url = document.getElementById('editLinkUrl').value;
            payload.html = document.getElementById('editLinkHtml').value;
            payload.tooltip = document.getElementById('editLinkTooltip').value;
            payload.new_tab = document.getElementById('editLinkNewTab').checked;
        }

        fetch('?action=save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        }).then(res => res.json()).then(data => {
            if (data.status === 'success') location.reload();
            else alert('Error saving: ' + data.message);
        });
    }

    function deleteCurrent() {
        const type = document.getElementById('editType').value;
        if (type === 'link') {
            if (!confirm('Delete this link? This cannot be undone.')) return;
            fetch('?action=delete_link', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    panel_id: document.getElementById('editPanel').value,
                    link_id: document.getElementById('editLink').value
                })
            }).then(res => res.json()).then(data => {
                if (data.status === 'success') location.reload();
                else alert('Error deleting: ' + data.message);
            });
        } else if (type === 'panel') {
            if (!confirm('Delete this entire panel and all its links? This cannot be undone.')) return;
            fetch('?action=delete_panel', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    panel_id: document.getElementById('editPanel').value
                })
            }).then(res => res.json()).then(data => {
                if (data.status === 'success') location.reload();
                else alert('Error deleting: ' + data.message);
            });
        }
    }

    // Add a new empty panel (no confirmation — easy to delete or hide if it was a slip)
    function addPanel() {
        fetch('?action=add_panel', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: '{}'
        }).then(res => res.json()).then(data => {
            if (data.status === 'success') location.reload();
            else alert('Error adding panel: ' + data.message);
        });
    }

    // Inline checkbox toggles for boolean site settings (no dialog)
    function toggleBool(target, checked) {
        fetch('?action=save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ type: 'site', target: target, value: checked ? 'true' : 'false' })
        }).then(res => res.json()).then(data => {
            if (data.status === 'success') location.reload();
            else alert('Error saving: ' + data.message);
        });
    }

    // Billiard-ball column selector (replaces the max-columns dialog)
    function setColumns(n) {
        fetch('?action=save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ type: 'site', target: 'max_columns', value: String(n) })
        }).then(res => res.json()).then(data => {
            if (data.status === 'success') location.reload();
            else alert('Error saving: ' + data.message);
        });
    }

    // Theme dropdown: save the chosen theme name (reloads to apply themes/<name>.css)
    function setTheme(name) {
        fetch('?action=save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ type: 'site', target: 'theme', value: name })
        }).then(res => res.json()).then(data => {
            if (data.status === 'success') location.reload();
            else alert('Error saving: ' + data.message);
        });
    }

    // ==========================================
    // 2. DRAG AND DROP ENGINE (Vanilla JS)
    // ==========================================
    let draggedItem = null;

    function handleDragStart(e) {
        draggedItem = this;
        e.dataTransfer.effectAllowed = "move";
        // Required for Firefox
        e.dataTransfer.setData("text/html", this.innerHTML);
        setTimeout(() => this.classList.add('is-dragging'), 0);
        e.stopPropagation();
    }

    function handleDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = "move";
        this.classList.add('drag-over');
        return false;
    }

    function handleDragLeave(e) {
        this.classList.remove('drag-over');
    }

    // Drop Link onto another Link
    function handleDropLink(e) {
        e.stopPropagation();
        this.classList.remove('drag-over');
        if (draggedItem !== this && draggedItem.classList.contains('link-item')) {
            const siblings = Array.from(this.parentNode.querySelectorAll('.link-item'));
            if (siblings.indexOf(draggedItem) > -1 && siblings.indexOf(draggedItem) < siblings.indexOf(this)) {
                this.parentNode.insertBefore(draggedItem, this.nextSibling);
            } else {
                this.parentNode.insertBefore(draggedItem, this);
            }
            triggerSilentSave();
        }
        return false;
    }

    // Drop Link onto a Panel Content area (empty space or bottom)
    function handleDropPanelContent(e) {
        e.stopPropagation();
        this.classList.remove('drag-over');
        if (draggedItem && draggedItem.classList.contains('link-item')) {
            const addBtnWrapper = this.querySelector('.add-btn-wrapper');
            if (addBtnWrapper) this.insertBefore(draggedItem, addBtnWrapper);
            else this.appendChild(draggedItem);
            triggerSilentSave();
        }
    }

    // Drop Panel onto another Panel
    function handleDropPanel(e) {
        e.stopPropagation();
        this.classList.remove('drag-over');
        if (draggedItem !== this && draggedItem.classList.contains('panel')) {
            const siblings = Array.from(this.parentNode.querySelectorAll('.panel'));
            if (siblings.indexOf(draggedItem) < siblings.indexOf(this)) {
                this.parentNode.insertBefore(draggedItem, this.nextSibling);
            } else {
                this.parentNode.insertBefore(draggedItem, this);
            }
            triggerSilentSave();
        }
        return false;
    }

    function handleDragEnd(e) {
        this.classList.remove('is-dragging');
        document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
        draggedItem = null;
    }

    // Attach all D&D events
    document.querySelectorAll('.link-item').forEach(link => {
        link.addEventListener('dragstart', handleDragStart, false);
        link.addEventListener('dragover', handleDragOver, false);
        link.addEventListener('dragleave', handleDragLeave, false);
        link.addEventListener('drop', handleDropLink, false);
        link.addEventListener('dragend', handleDragEnd, false);
    });

    document.querySelectorAll('.panel-content').forEach(pc => {
        pc.addEventListener('dragover', handleDragOver, false);
        pc.addEventListener('dragleave', handleDragLeave, false);
        pc.addEventListener('drop', handleDropPanelContent, false);
    });

    document.querySelectorAll('.panel').forEach(panel => {
        panel.addEventListener('dragstart', handleDragStart, false);
        panel.addEventListener('dragover', handleDragOver, false);
        panel.addEventListener('dragleave', handleDragLeave, false);
        panel.addEventListener('drop', handleDropPanel, false);
        panel.addEventListener('dragend', handleDragEnd, false);
    });

    // Reconstructs the layout from the DOM and saves it silently
    function triggerSilentSave() {
        const newPanels = {};
        let pIndex = 1;

        document.querySelectorAll('.panel').forEach(panel => {
            const headerEl = panel.querySelector('.panel-header');
            const headingText = headerEl.getAttribute('data-heading-val');

            const links = [];
            panel.querySelectorAll('.link-item').forEach(item => {
                // The data-* live on the editable element: the <a> for a normal link,
                // or the .link-item itself for an HTML-block placeholder (no anchor).
                // Iterating '.link-item a' would silently drop HTML blocks on reorder.
                const el = item.matches('.editable') ? item : item.querySelector('.editable');
                if (!el) return;
                const link = {
                    name: el.getAttribute('data-name') || '',
                    url: el.getAttribute('data-url') || '',
                    tooltip: el.getAttribute('data-tooltip') || '',
                    new_tab: el.getAttribute('data-new-tab') === 'true'
                };
                const html = el.getAttribute('data-html') || '';
                if (html !== '') link.html = html; // omit when empty to keep config tidy
                links.push(link);
            });

            // Preserve the hide flag across reorders (only emit it when set, to keep config clean)
            const obj = { heading: headingText, links: links };
            if (panel.getAttribute('data-hidden') === 'true') obj.hidden = true;
            newPanels[pIndex++] = obj;
        });

        fetch('?action=save_layout', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ panels: newPanels })
        }).then(res => res.json()).then(data => {
            if (data.status !== 'success') { alert('Failed to auto-save layout arrangement.'); return; }
            reindexLayout(); // mirror the server's renumber so a later edit/add hits the right item
        });
    }

    // After save_layout, the server renumbers panels 1..N and links 0..M (both in
    // DOM order). Rewrite the DOM's data-panel/data-link to match, so a subsequent
    // edit/add without a page reload doesn't target a stale index.
    function reindexLayout() {
        let p = 1;
        document.querySelectorAll('.panel').forEach(panel => {
            const header = panel.querySelector('.panel-header');
            if (header) header.setAttribute('data-panel', p);
            let l = 0;
            panel.querySelectorAll('.link-item').forEach(item => {
                const el = item.matches('.editable') ? item : item.querySelector('.editable');
                if (!el) return;
                el.setAttribute('data-panel', p);
                el.setAttribute('data-link', l);
                l++;
            });
            p++;
        });
    }
</script>
<?php endif; ?>
</body>
</html>

