OpsCheck

Multi-checklist action panel for Torn.com

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

Advertisement:

// ==UserScript==
// @name         OpsCheck
// @version      2.0.2
// @description  Multi-checklist action panel for Torn.com
// @author       BBSmalls
// @match        https://www.torn.com/*
// @grant        none
// @namespace    https://www.torn.com/
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'opsCheck';
    const MIN_WIDTH = 180;
    const DEFAULT_LIST = {
        id: 'war-energy',
        name: 'War Energy',
        buttons: [
            { name: 'Refill', url: 'https://www.torn.com/page.php?sid=points', done: false, enabled: true },
            { name: 'Stocks', url: 'https://www.torn.com/page.php?sid=stocks', done: false, enabled: true },
            { name: 'Xanax', url: 'https://www.torn.com/factions.php?step=your&type=1#/tab=armoury&start=0&sub=drugs', done: false, enabled: true },
            { name: 'Energy Cans', url: 'https://www.torn.com/item.php', done: false, enabled: true },
            { name: 'Job Points', url: 'https://www.torn.com/companies.php?step=your&type=1', done: false, enabled: true },
        ]
    };

    // ─── Load State ───────────────────────────────────────────────────────────
    function loadState() {
        let saved = JSON.parse(localStorage.getItem(STORAGE_KEY));

        // ── Backward compatibility: old warEnergy format ──
        if (!saved) {
            const old = JSON.parse(localStorage.getItem('warEnergy'));
            if (old && old.buttons && old.buttons.length > 0) {
                saved = {
                    lists: [{ id: 'war-energy', name: 'War Energy', buttons: old.buttons }],
                    activeListId: 'war-energy',
                    pos: old.pos || { x: 20, y: 20 },
                    size: old.size || { w: 280, h: 300 },
                    locked: old.locked || false,
                    minimized: old.minimized || false
                };
                localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
                return saved;
            }
        }

        // ── Fresh install ──
        if (!saved) {
            const fresh = {
                lists: [{ id: 'war-energy', name: 'War Energy', buttons: DEFAULT_LIST.buttons.map(b => ({ ...b })) }],
                activeListId: 'war-energy',
                pos: { x: 20, y: 20 },
                size: { w: 280, h: 300 },
                locked: false,
                minimized: false
            };
            localStorage.setItem(STORAGE_KEY, JSON.stringify(fresh));
            return fresh;
        }

        // ── Existing opsCheck save ──
        if (!saved.lists) saved.lists = [{ id: 'war-energy', name: 'War Energy', buttons: [] }];
        if (!saved.activeListId) saved.activeListId = saved.lists[0].id;
        return saved;
    }

    let state = loadState();
    function activeList() {
        return state.lists.find(l => l.id === state.activeListId) || state.lists[0];
    }

    function saveState() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
    }

    // ─── Styles ───────────────────────────────────────────────────────────────
    const style = document.createElement('style');
    style.textContent = `
#we-panel {
      position: fixed;
      z-index: 99999;
      background: #111416;
      border: 1px solid #2a2f35;
      border-radius: 6px;
      box-shadow: 0 4px 24px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.04);
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      font-size: 12px;
      color: #c8cdd4;
      display: flex;
      flex-direction: column;
      min-width: ${MIN_WIDTH}px;
      overflow: hidden;
      user-select: none;
    }
    #we-titlebar {
      background: #1a1e23;
      border-bottom: 1px solid #2a2f35;
      padding: 5px 8px;
      cursor: grab;
      display: flex;
      align-items: center;
      gap: 6px;
      flex-shrink: 0;
    }
    #we-titlebar:active { cursor: grabbing; }
    #we-title-icon {
      width: 12px; height: 12px;
      background: linear-gradient(135deg, #e8a020, #c06000);
      border-radius: 2px;
      flex-shrink: 0;
    }
    #we-title-text {
      font-size: 11px;
      font-weight: 600;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: #9da6b0;
      flex: 1;
    }

    /* Brighter Minimize Button */
    #we-btn-minimize {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 15px;
      padding: 0 2px 2px;
      line-height: 1;
      color: #b8c8ff;
      font-weight: bold;
      opacity: 0.9;
      transition: all 0.15s;
    }
    #we-btn-minimize:hover {
      color: #ffffff;
      opacity: 1;
      transform: scale(1.1);
    }

    #we-upper {
      flex: 1;
      overflow-y: auto;
      padding: 6px 6px 2px;
      display: flex;
      flex-direction: column;
      gap: 4px;
      min-height: 40px;
    }
    #we-selector {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 6px;
  border-bottom: 1px solid #1e2328;
  background: #0e1114;
  flex-shrink: 0;
}
#we-selector select {
  flex: 1;
  background: #1a1e23;
  border: 1px solid #2a2f35;
  border-radius: 4px;
  color: #c8cdd4;
  font-size: 11px;
  padding: 4px 6px;
  cursor: pointer;
  outline: none;
}
#we-selector select:focus { border-color: #5080b0; }
#we-selector option { background: #1a1e23; color: #c8cdd4; }
.we-sel-btn {
  width: 24px; height: 24px;
  border: 1px solid #2a2f35;
  border-radius: 4px;
  background: #1a1e23;
  color: #7a8490;
  font-size: 12px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.1s, color 0.1s;
  padding: 0;
  flex-shrink: 0;
}
.we-sel-btn:hover { background: #252d38; color: #c8cdd4; }
.we-sel-btn.danger:hover { background: #2a1a1a; color: #c96b6b; border-color: #5a3a3a; }
.we-row.drag-over-top { border-top: 2px solid #5080b0; }
.we-row.drag-over-bot { border-bottom: 2px solid #5080b0; }
.we-edit-controls {
  display: flex;
  flex-direction: row;
  gap: 3px;
  flex-shrink: 0;
}
#we-panel.minimized {
  min-height: 0;
}
    .we-row {
      display: flex;
      gap: 6px;
      align-items: center;
    }

    /* Main Panel Buttons - Dark Green */
   .we-action-btn {
  flex: 1;
  padding: 7px 10px;
  border: 2px solid #006633;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
      border-radius: 6px;
      background: #0f2a1f;
      color: #00ff99;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.15s ease;
      box-shadow: 0 0 8px rgba(0, 170, 68, 0.35);
    }
    .we-action-btn:hover {
      background: #163829;
      border-color: #00aa66;
      color: #ffffff;
      transform: translateY(-1px);
      box-shadow: 0 0 12px rgba(0, 204, 102, 0.5);
    }
    .we-action-btn:disabled {
      border-color: #666677;
      background: #1f2228;
      color: #778899;
      text-decoration: line-through;
      opacity: 0.75;
    }


    /* Edit Mode */
    .we-action-btn-edit {
  flex: 1;
  padding: 7px 10px;
  border: 2px solid #557799;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
      border-radius: 6px;
      background: #1c242f;
      color: #bbddff;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.15s ease;
    }
    .we-action-btn-edit.enabled {
      border: 2px solid #006633;
      background: #0f2a1f;
      color: #00ff99;
      box-shadow: 0 0 8px rgba(0, 170, 68, 0.35);
    }
    .we-action-btn-edit.enabled:hover {
      background: #163829;
      border-color: #00aa66;
      color: #ffffff;
    }
    .we-action-btn-edit.disabled {
      border-color: #555566;
      background: #1a1f26;
      color: #778899;
      opacity: 0.55;
    }

    /* Bright Eyeball Toggle */
    .we-toggle-btn {
      width: 26px; height: 26px;
      border: 2px solid #334455;
      border-radius: 5px;
      background: #1a212b;
      font-size: 14px;
      color: #77ffaa;
      cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      transition: all 0.15s ease;
    }
    .we-toggle-btn:hover {
      background: #2a3f2f;
      border-color: #00ff99;
      color: #bbffdd;
      transform: scale(1.1);
    }

    .we-del-btn {
      width: 26px; height: 26px;
      border: 2px solid #334455;
      border-radius: 5px;
      background: #1a212b;
      color: #ff7777;
      cursor: pointer;
      display: flex; align-items: center; justify-content: center;
    }
    .we-del-btn:hover {
      background: #3a1f1f;
      border-color: #ff5555;
      color: #ffaaaa;
    }

    #we-lower {
      border-top: 1px solid #1e2328;
      padding: 5px 6px;
      display: flex;
      gap: 4px;
      flex-shrink: 0;
      background: #0e1114;
    }
    .we-ctrl-btn {
      flex: 1;
      padding: 5px 6px;
      border: 1px solid #2a2f35;
      border-radius: 4px;
      background: #1a1e23;
      color: #8a9098;
      font-size: 11px;
      font-weight: 600;
      cursor: pointer;
    }
    .we-ctrl-btn:hover { background: #222830; color: #c8cdd4; }

    #we-resize-handle {
  position: absolute;
  bottom: 0; right: 0;
  width: 28px; height: 28px;
      cursor: se-resize;
      background: transparent;
      z-index: 10;
    }
    #we-resize-handle::after {
      content: '';
      position: absolute;
      bottom: 3px; right: 3px;
      width: 6px; height: 6px;
      border-right: 2px solid #3a4050;
      border-bottom: 2px solid #3a4050;
      border-radius: 1px;
    }

    /* Modals */
    .we-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,0.65);
      z-index: 100000;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .we-modal {
      background: #14181e;
      border: 1px solid #2a3040;
      border-radius: 8px;
      box-shadow: 0 8px 40px rgba(0,0,0,0.8);
      padding: 20px;
      min-width: 280px;
      max-width: 360px;
      color: #c8cdd4;
    }
    .we-modal h3 {
      margin: 0 0 14px;
      font-size: 13px;
      font-weight: 600;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: #8098b8;
    }
    .we-modal label {
      display: block;
      font-size: 11px;
      color: #6a7888;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      margin-bottom: 4px;
    }
    .we-modal input[type="text"] {
      width: 100%;
      box-sizing: border-box;
      background: #1c2128;
      border: 1px solid #2e3848;
      border-radius: 4px;
      color: #d8e0e8;
      font-size: 12px;
      padding: 7px 9px;
      margin-bottom: 12px;
      outline: none;
      transition: border-color 0.15s;
    }
    .we-modal input[type="text"]:focus { border-color: #5080b0; }
    .we-modal-actions {
      display: flex;
      gap: 8px;
      justify-content: flex-end;
      margin-top: 4px;
    }
    .we-btn-primary {
      padding: 7px 16px;
      background: #1e3a5a;
      border: 1px solid #3060a0;
      border-radius: 4px;
      color: #80b8f0;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
    }
    .we-btn-primary:hover { background: #254570; }
    .we-btn-cancel {
      padding: 7px 14px;
      background: #1a1e23;
      border: 1px solid #2a2f35;
      border-radius: 4px;
      color: #6a7888;
      font-size: 12px;
      cursor: pointer;
    }
    .we-btn-cancel:hover { background: #22282e; }
    .we-btn-danger {
      padding: 7px 14px;
      background: #2a1818;
      border: 1px solid #6a2828;
      border-radius: 4px;
      color: #c06060;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
    }
    .we-btn-danger:hover { background: #3a2020; }
    `;
    document.head.appendChild(style);

    // ─── Panel Construction ───────────────────────────────────────────────────
    const panel = document.createElement('div');
    panel.id = 'we-panel';
    panel.style.left = state.pos.x + 'px';
    panel.style.top = state.pos.y + 'px';
    panel.style.width = state.size.w + 'px';
    panel.style.height = state.minimized ? 'auto' : state.size.h + 'px';
    if (state.minimized) panel.classList.add('minimized');

    panel.innerHTML = `
    <div id="we-titlebar">
      <div id="we-title-icon"></div>
      <span id="we-title-text">Ops Check</span>
      <button id="we-btn-minimize">—</button>
      <button id="we-btn-lock">🔓</button>
    </div>
    <div id="we-selector">
      <button class="we-sel-btn danger" id="we-sel-delete" title="Delete list">✕</button>
      <button class="we-sel-btn" id="we-sel-rename" title="Rename list">✎</button>
      <select id="we-sel-dropdown"></select>
    </div>
    <div id="we-upper"></div>
    <div id="we-lower">
      <button class="we-ctrl-btn" id="we-btn-edit">Edit</button>
      <button class="we-ctrl-btn" id="we-btn-reset">Reset</button>
    </div>
    <div id="we-resize-handle"></div>
  `;

    document.body.appendChild(panel);

    if (/[?&]sid=attack/.test(window.location.search)) {
        panel.style.display = 'none';
    }

    const upper = panel.querySelector('#we-upper');
    const btnEdit = panel.querySelector('#we-btn-edit');
    const btnReset = panel.querySelector('#we-btn-reset');
    const btnLock = panel.querySelector('#we-btn-lock');
    const btnMinimize = panel.querySelector('#we-btn-minimize');
    const titlebar = panel.querySelector('#we-titlebar');
    const resizeHandle = panel.querySelector('#we-resize-handle');
    const selDropdown = panel.querySelector('#we-sel-dropdown');
    const selDelete = panel.querySelector('#we-sel-delete');
    const selRename = panel.querySelector('#we-sel-rename');

    let editMode = false;

    function renderSelector() {
        selDropdown.innerHTML = '';

        // Sort lists alphabetically, keeping "New Checklist" at bottom
        const sorted = [...state.lists].sort((a, b) => a.name.localeCompare(b.name));

        sorted.forEach(list => {
            const opt = document.createElement('option');
            opt.value = list.id;
            opt.textContent = list.name;
            if (list.id === state.activeListId) opt.selected = true;
            selDropdown.appendChild(opt);
        });

        // "New Checklist" at bottom
        const newOpt = document.createElement('option');
        newOpt.value = '__new__';
        newOpt.textContent = '+ New Checklist';
        selDropdown.appendChild(newOpt);
    }

    // ─── Render Upper ─────────────────────────────────────────────────────────
    function renderUpper() {
        renderSelector();
        upper.innerHTML = '';
        if (!activeList().buttons.length) {
            const hint = document.createElement('div');
            hint.className = 'we-empty-hint';
            hint.textContent = 'No buttons — enter Edit mode to add one';
            upper.appendChild(hint);
            return;
        }

        activeList().buttons.forEach((btn, idx) => {
            if (!editMode && !btn.enabled) return;

            const row = document.createElement('div');
            row.className = 'we-row';

            const actionBtn = document.createElement('button');
            actionBtn.className = 'we-action-btn';
             const nameSpan = document.createElement('span');
            nameSpan.textContent = btn.name;
            nameSpan.style.cssText = 'overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;';
            actionBtn.appendChild(nameSpan);
            if (btn.url) actionBtn.classList.add('has-link');

            if (editMode) {
                // Edit Mode
                actionBtn.classList.add('we-action-btn-edit');
                if (btn.enabled) actionBtn.classList.add('enabled');
                else actionBtn.classList.add('disabled');
                actionBtn.addEventListener('click', () => showEditModal(idx));
            } else {
                // Normal Mode
                if (btn.done) {
                    // COMPLETED button - clicking anywhere on row re-enables it
                    actionBtn.disabled = true;           // keep visual "done" look
                    actionBtn.style.pointerEvents = 'none'; // prevent button itself from stealing clicks
                    row.style.cursor = 'pointer';
                    row.addEventListener('click', () => toggleDone(idx));
                } else {
                    // Active button
                    actionBtn.disabled = false;
                    actionBtn.addEventListener('click', () => handleActionClick(idx));
                }
            }

            if (editMode && !btn.enabled) row.style.opacity = '0.45';

            if (editMode) {
                row.setAttribute('draggable', 'true');
                row.dataset.idx = idx;

                // Drag & Drop (unchanged)
                row.addEventListener('dragstart', e => {
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/plain', idx);
                    setTimeout(() => row.style.opacity = '0.4', 0);
                });
                row.addEventListener('dragend', () => {
                    row.style.opacity = '';
                    document.querySelectorAll('.we-row').forEach(r => r.classList.remove('drag-over-top', 'drag-over-bot'));
                });
                row.addEventListener('dragover', e => {
                    e.preventDefault();
                    const rect = row.getBoundingClientRect();
                    const midY = rect.top + rect.height / 2;
                    row.classList.toggle('drag-over-top', e.clientY < midY);
                    row.classList.toggle('drag-over-bot', e.clientY >= midY);
                });
                row.addEventListener('dragleave', () => row.classList.remove('drag-over-top', 'drag-over-bot'));
                row.addEventListener('drop', e => {
                    e.preventDefault();
                    row.classList.remove('drag-over-top', 'drag-over-bot');
                    const fromIdx = parseInt(e.dataTransfer.getData('text/plain'));
                    const rect = row.getBoundingClientRect();
                    const insertBefore = e.clientY < rect.top + rect.height / 2;
                    let toIdx = insertBefore ? idx : idx + 1;
                    if (fromIdx === toIdx || fromIdx === toIdx - 1) return;
                    const item = activeList().buttons.splice(fromIdx, 1)[0];
                    if (fromIdx < toIdx) toIdx--;
                    activeList().buttons.splice(toIdx, 0, item);
                    saveState();
                    renderUpper();
                });

                const toggleBtn = document.createElement('button');
                toggleBtn.className = 'we-toggle-btn';
                toggleBtn.title = btn.enabled ? 'Hide from panel' : 'Show on panel';
                toggleBtn.textContent = btn.enabled ? '👁' : '🚫';
                toggleBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    activeList().buttons[idx].enabled = !activeList().buttons[idx].enabled;
                    saveState();
                    renderUpper();
                });

                const delBtn = document.createElement('button');
                delBtn.className = 'we-del-btn';
                delBtn.title = 'Delete';
                delBtn.textContent = '✕';
                delBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    showDeleteConfirm(idx);
                });

                row.appendChild(toggleBtn);
                row.appendChild(actionBtn);
                row.appendChild(delBtn);
            } else {
                row.appendChild(actionBtn);
            }

            upper.appendChild(row);
        });
    }

    // ─── Actions ──────────────────────────────────────────────────────────────
    function handleActionClick(idx) {
        const btn = activeList().buttons[idx];
        if (!btn) return;
        if (btn.done) { toggleDone(idx); return; }

        if (!btn.url) {
            btn.done = true;
            saveState();
            renderUpper();
            return;
        }

        try {
            const targetUrl = new URL(btn.url, window.location.origin);
            const current = new URL(window.location.href);
            const onPage = current.origin + current.pathname + current.search ===
                  targetUrl.origin + targetUrl.pathname + targetUrl.search;

            btn.done = true;
            saveState();
            renderUpper();

            if (!onPage) window.location.href = btn.url;
        } catch (e) {
            btn.done = true;
            saveState();
            renderUpper();
            window.location.href = btn.url;
        }
    }

    function toggleDone(idx) {
        activeList().buttons[idx].done = !activeList().buttons[idx].done;
        saveState();
        renderUpper();
    }

    // ─── Modals ───────────────────────────────────────────────────────────────
    function makeOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'we-overlay';
        return overlay;
    }

    function escHtml(str) {
        return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    function showAddModal() {
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>Add Button</h3>
      <label>Name</label>
      <input type="text" autocomplete="off" id="we-input-name" placeholder="e.g. Attack Target" maxlength="40" />
      <label>URL</label>
       <input type="text" autocomplete="off" id="we-input-url" placeholder="https://www.torn.com/... (leave blank for no navigation)" />
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-primary" id="we-modal-save">Add</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const nameInput = modal.querySelector('#we-input-name');
        const urlInput = modal.querySelector('#we-input-url');
        nameInput.focus();

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => overlay.remove());
        modal.querySelector('#we-modal-save').addEventListener('click', () => {
            const name = nameInput.value.trim();
            const url = urlInput.value.trim();
            if (!name) return;
            activeList().buttons.push({ name, url, done: false, enabled: true });
            saveState();
            renderUpper();
            overlay.remove();
        });

        [nameInput, urlInput].forEach(el => el.addEventListener('keydown', e => {
            if (e.key === 'Enter') modal.querySelector('#we-modal-save').click();
            if (e.key === 'Escape') overlay.remove();
        }));

        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
    }

    function showEditModal(idx) {
        const btn = activeList().buttons[idx];
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>Edit Button</h3>
      <label>Name</label>
      <input type="text" autocomplete="off" id="we-input-name" value="${escHtml(btn.name)}" maxlength="40" />
      <label>URL</label>
      <input type="text" autocomplete="off" id="we-input-url" value="${escHtml(btn.url || '')}" placeholder="https://www.torn.com/... (leave blank for no navigation)" />
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-primary" id="we-modal-save">Save</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const nameInput = modal.querySelector('#we-input-name');
        const urlInput = modal.querySelector('#we-input-url');
        nameInput.focus();
        nameInput.select();

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => overlay.remove());
        modal.querySelector('#we-modal-save').addEventListener('click', () => {
            const name = nameInput.value.trim();
            const url = urlInput.value.trim();
            if (!name) return;
            activeList().buttons[idx].name = name;
            activeList().buttons[idx].url = url;
            saveState();
            renderUpper();
            overlay.remove();
        });

        [nameInput, urlInput].forEach(el => el.addEventListener('keydown', e => {
            if (e.key === 'Enter') modal.querySelector('#we-modal-save').click();
            if (e.key === 'Escape') overlay.remove();
        }));

        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
    }

    function showDeleteConfirm(idx) {
        const btn = activeList().buttons[idx];
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>Delete Button</h3>
      <p class="we-confirm-text">Delete "<strong>${escHtml(btn.name)}</strong>"? This cannot be undone.</p>
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-danger" id="we-modal-delete">Delete</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => overlay.remove());
        modal.querySelector('#we-modal-delete').addEventListener('click', () => {
            activeList().buttons.splice(idx, 1);
            saveState();
            renderUpper();
            overlay.remove();
        });
        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
    }

    function generateId() {
        return 'list-' + Date.now().toString(36);
    }

    function showNewListModal() {
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>New Checklist</h3>
      <label>Name</label>
      <input type="text" autocomplete="off" id="we-input-name" placeholder="e.g. Faction War" maxlength="40" />
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-primary" id="we-modal-save">Create</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const nameInput = modal.querySelector('#we-input-name');
        nameInput.focus();

        const doSave = () => {
            const name = nameInput.value.trim();
            if (!name) { nameInput.style.borderColor = '#a04040'; return; }
            const newList = { id: generateId(), name, buttons: [] };
            state.lists.push(newList);
            state.activeListId = newList.id;
            saveState();
            renderUpper();
            overlay.remove();
        };

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => {
            // Revert dropdown to previous selection
            renderSelector();
            overlay.remove();
        });
        modal.querySelector('#we-modal-save').addEventListener('click', doSave);
        nameInput.addEventListener('keydown', e => {
            if (e.key === 'Enter') doSave();
            if (e.key === 'Escape') { renderSelector(); overlay.remove(); }
        });
        overlay.addEventListener('click', e => { if (e.target === overlay) { renderSelector(); overlay.remove(); } });
    }

    function showRenameListModal() {
        const list = activeList();
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>Rename Checklist</h3>
      <label>Name</label>
      <input type="text" autocomplete="off" id="we-input-name" value="${escHtml(list.name)}" maxlength="40" />
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-primary" id="we-modal-save">Save</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const nameInput = modal.querySelector('#we-input-name');
        nameInput.focus();
        nameInput.select();

        const doSave = () => {
            const name = nameInput.value.trim();
            if (!name) { nameInput.style.borderColor = '#a04040'; return; }
            list.name = name;
            saveState();
            renderUpper();
            overlay.remove();
        };

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => overlay.remove());
        modal.querySelector('#we-modal-save').addEventListener('click', doSave);
        nameInput.addEventListener('keydown', e => {
            if (e.key === 'Enter') doSave();
            if (e.key === 'Escape') overlay.remove();
        });
        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
    }

    function showDeleteListConfirm() {
        const list = activeList();
        if (state.lists.length === 1) {
            // Can't delete the last list — flash the border instead
            selDelete.style.borderColor = '#c96b6b';
            setTimeout(() => selDelete.style.borderColor = '', 800);
            return;
        }
        const overlay = makeOverlay();
        const modal = document.createElement('div');
        modal.className = 'we-modal';
        modal.innerHTML = `
      <h3>Delete Checklist</h3>
      <p class="we-confirm-text">Delete "<strong>${escHtml(list.name)}</strong>" and all its buttons? This cannot be undone.</p>
      <div class="we-modal-actions">
        <button class="we-btn-cancel" id="we-modal-cancel">Cancel</button>
        <button class="we-btn-danger" id="we-modal-delete">Delete</button>
      </div>
    `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        modal.querySelector('#we-modal-cancel').addEventListener('click', () => overlay.remove());
        modal.querySelector('#we-modal-delete').addEventListener('click', () => {
            state.lists = state.lists.filter(l => l.id !== list.id);
            state.activeListId = state.lists[0].id;
            saveState();
            renderUpper();
            overlay.remove();
        });
        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
    }

    // ─── Control Buttons ──────────────────────────────────────────────────────
    btnEdit.addEventListener('click', () => {
        if (!editMode) {
            editMode = true;
            btnEdit.textContent = 'Add';
            btnReset.textContent = 'Save';
            renderUpper();
        } else {
            showAddModal();
        }
    });

    btnReset.addEventListener('click', () => {
        if (editMode) {
            editMode = false;
            btnEdit.textContent = 'Edit';
            btnReset.textContent = 'Reset';
            renderUpper();
        } else {
            activeList().buttons.forEach(b => b.done = false);
            saveState();
            renderUpper();
        }
    });

    selDropdown.addEventListener('change', () => {
        const val = selDropdown.value;
        if (val === '__new__') {
            showNewListModal();
        } else {
            state.activeListId = val;
            saveState();
            renderUpper();
        }
    });

    selDelete.addEventListener('click', () => showDeleteListConfirm());
    selRename.addEventListener('click', () => showRenameListModal());

    btnMinimize.addEventListener('click', () => {
        state.minimized = !state.minimized;
        saveState();

        if (state.minimized) {
            upper.style.display = 'none';
            document.querySelector('#we-lower').style.display = 'none';
            document.querySelector('#we-selector').style.display = 'none';
            panel.style.height = 'auto';
            btnMinimize.textContent = '▢';
        } else {
            upper.style.display = '';
            document.querySelector('#we-lower').style.display = '';
            document.querySelector('#we-selector').style.display = '';
            panel.style.height = state.size.h + 'px';
            btnMinimize.textContent = '—';
        }

        panel.classList.toggle('minimized', state.minimized);
        resizeHandle.style.visibility = (state.minimized || state.locked) ? 'hidden' : 'visible';
    });

    btnLock.addEventListener('click', () => {
        state.locked = !state.locked;
        saveState();
        btnLock.textContent = state.locked ? '🔒' : '🔓';
        titlebar.style.cursor = state.locked ? 'default' : 'grab';
        resizeHandle.style.visibility = (state.locked || state.minimized) ? 'hidden' : 'visible';
    });

    // Initial Setup
    btnLock.textContent = state.locked ? '🔒' : '🔓';
    titlebar.style.cursor = state.locked ? 'default' : 'grab';
    resizeHandle.style.visibility = (state.locked || state.minimized) ? 'hidden' : 'visible';

    if (state.minimized) {
        upper.style.display = 'none';
        document.querySelector('#we-lower').style.display = 'none';
        document.querySelector('#we-selector').style.display = 'none';
        panel.style.height = 'auto';
        btnMinimize.textContent = '▢';
    }

    // ─── Drag ─────────────────────────────────────────────────────────────────
    let dragging = false, dragStartX, dragStartY, panelStartX, panelStartY;

    titlebar.addEventListener('mousedown', e => {
        if (e.button !== 0 || state.locked || editMode) return;
        dragging = true;
        dragStartX = e.clientX;
        dragStartY = e.clientY;
        panelStartX = panel.offsetLeft;
        panelStartY = panel.offsetTop;
        e.preventDefault();
    });

    document.addEventListener('mousemove', e => {
        if (!dragging) return;
        const nx = panelStartX + (e.clientX - dragStartX);
        const ny = panelStartY + (e.clientY - dragStartY);
        panel.style.left = Math.max(0, Math.min(nx, window.innerWidth - panel.offsetWidth)) + 'px';
        panel.style.top = Math.max(0, Math.min(ny, window.innerHeight - panel.offsetHeight)) + 'px';
    });

    document.addEventListener('mouseup', () => {
        if (dragging) {
            dragging = false;
            state.pos = { x: panel.offsetLeft, y: panel.offsetTop };
            saveState();
        }
    });

    // ─── Resize ───────────────────────────────────────────────────────────────
    let resizing = false, resStartX, resStartY, resStartW, resStartH;

    resizeHandle.addEventListener('mousedown', e => {
    if (e.button !== 0 || state.locked || state.minimized) return;
    resizing = true;
    resStartX = e.clientX;
    resStartY = e.clientY;
    resStartW = panel.offsetWidth;
    resStartH = panel.offsetHeight;
    e.preventDefault();
    e.stopPropagation();
});
resizeHandle.addEventListener('touchstart', e => {
    if (state.locked || state.minimized) return;
    const t = e.touches[0];
    resizing = true;
    resStartX = t.clientX;
    resStartY = t.clientY;
    resStartW = panel.offsetWidth;
    resStartH = panel.offsetHeight;
    e.preventDefault();
    e.stopPropagation();
}, { passive: false });

    document.addEventListener('mousemove', e => {
    if (!resizing) return;
    const nw = Math.max(MIN_WIDTH, resStartW + (e.clientX - resStartX));
    const nh = Math.max(120, resStartH + (e.clientY - resStartY));
    panel.style.width = nw + 'px';
    panel.style.height = nh + 'px';
});
document.addEventListener('touchmove', e => {
    if (!resizing) return;
    const t = e.touches[0];
    const nw = Math.max(MIN_WIDTH, resStartW + (t.clientX - resStartX));
    const nh = Math.max(120, resStartH + (t.clientY - resStartY));
    panel.style.width = nw + 'px';
    panel.style.height = nh + 'px';
    e.preventDefault();
}, { passive: false });

    document.addEventListener('mouseup', () => {
    if (resizing) {
        resizing = false;
        state.size = { w: panel.offsetWidth, h: panel.offsetHeight };
        saveState();
    }
});
document.addEventListener('touchend', () => {
    if (resizing) {
        resizing = false;
        state.size = { w: panel.offsetWidth, h: panel.offsetHeight };
        saveState();
    }
});

    // ─── Init ─────────────────────────────────────────────────────────────────
    function initDoneState() {
        renderUpper();
    }

    window.addEventListener('resize', () => {
        const rect = panel.getBoundingClientRect();
        const newLeft = Math.min(panel.offsetLeft, window.innerWidth - rect.width);
        const newTop = Math.min(panel.offsetTop, window.innerHeight - rect.height);
        panel.style.left = Math.max(0, newLeft) + 'px';
        panel.style.top = Math.max(0, newTop) + 'px';
        state.pos = { x: panel.offsetLeft, y: panel.offsetTop };
        saveState();
    });

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initDoneState);
    } else if (document.body) {
        initDoneState();
    } else {
        window.addEventListener('load', initDoneState);
    }

})();