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);
    }

})();