OpsCheck

Multi-checklist action panel for Torn.com

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         OpsCheck
// @version      2.5.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,
                    fabPos: { x: 16, y: window.innerHeight - 60 },
                    panelVisible: true
                };
                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,
                fabPos: { x: 16, y: window.innerHeight - 60 },
                panelVisible: true
            };
            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();
    if (!state.fabPos) state.fabPos = { x: 16, y: window.innerHeight - 60 };
    if (state.panelVisible === undefined) state.panelVisible = true;
    if (state.hideOnAttack === undefined) state.hideOnAttack = true;
    state.lists.forEach(l => { if (l.enabled === undefined) l.enabled = true; });

    function activeList() {
        const found = state.lists.find(l => l.id === state.activeListId);
        if (found && found.enabled) return found;
        const firstEnabled = state.lists.find(l => l.enabled);
        if (firstEnabled) { state.activeListId = firstEnabled.id; return firstEnabled; }
        return null;
    }

    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;
  min-width: 0;
  width: 0;
  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: 44px; height: 44px;
  cursor: se-resize;
  background: transparent;
  z-index: 10;
  touch-action: none;
}

#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: 100002;
      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; }
    #we-fab {
  position: fixed;
  z-index: 99998;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #0f2a1f;
  border: 2px solid #006633;
  box-shadow: 0 0 12px rgba(0, 170, 68, 0.4), 0 2px 8px rgba(0,0,0,0.6);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  transition: none;
  user-select: none;
  touch-action: manipulation;   /* Helps click response on mobile */
    -webkit-tap-highlight-color: rgba(0,0,0,0.1);

}
#we-fab.glow {
  border-color: #00aa66;
  box-shadow: 0 0 18px rgba(0, 204, 102, 0.6), 0 2px 8px rgba(0,0,0,0.6);
  transform: scale(1.08);
}
#we-fab svg {
  width: 100%;
  height: 100%;
  display: block;
}
#we-btn-settings {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  padding: 0;
  line-height: 1;
  opacity: 0.6;
  transition: opacity 0.15s, transform 0.3s;
  flex-shrink: 0;
  color: #9da6b0;
}
#we-btn-settings:hover {
  opacity: 1;
  transform: rotate(45deg);
}
#we-settings-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  z-index: 100001;
  display: flex;
  align-items: center;
  justify-content: center;
}
#we-settings-panel {
  background: #14181e;
  border: 1px solid #2a3040;
  border-radius: 8px;
  box-shadow: 0 8px 40px rgba(0,0,0,0.8);
  width: 350px;
  height: 500px;
  display: flex;
  flex-direction: column;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  color: #c8cdd4;
  overflow: hidden;
}
#we-settings-header {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid #2a3040;
  background: #1a1e23;
  flex-shrink: 0;
}
#we-settings-header h2 {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #9da6b0;
  flex: 1;
}
#we-settings-close {
  background: none;
  border: none;
  color: #7a8490;
  font-size: 16px;
  cursor: pointer;
  padding: 0;
  line-height: 1;
}
#we-settings-close:hover { color: #c8cdd4; }
#we-settings-body {
  display: flex;
  flex: 1;
  overflow: hidden;
}
#we-settings-lists {
  width: 180px;
  flex-shrink: 0;
  border-right: 1px solid #2a3040;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#we-settings-lists-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 6px 0;
}
.we-settings-list-row {
  display: flex;
  align-items: center;
  padding: 10px 10px;
  cursor: pointer;
  gap: 6px;
  border-left: 3px solid transparent;
  transition: background 0.1s;
}
.we-settings-list-row:hover { background: #1c2128; }
.we-settings-list-row.active {
  background: #1c2128;
  border-left-color: #006633;
}
.we-settings-list-name {
  flex: 1;
  font-size: 11px;
  line-height: 1.5;
  color: #c8cdd4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.we-settings-list-row.active .we-settings-list-name { color: #00ff99; }
.we-settings-list-edit {
  background: none;
  border: none;
  color: #8a9098;
  font-size: 12px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  flex-shrink: 0;
}
.we-settings-list-edit:hover { color: #c8cdd4; }
.we-settings-list-delete {
  background: none;
  border: none;
  color: #8a9098;
  font-size: 13px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  flex-shrink: 0;
}
.we-settings-list-delete:hover { color: #c96b6b; }
#we-settings-lists-footer {
  padding: 6px 8px;
  border-top: 1px solid #2a3040;
  flex-shrink: 0;
}
#we-settings-items {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#we-settings-items-header {
  display: none;
}
#we-settings-items-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 6px 0;
}
.we-settings-item-row {
  display: flex;
  align-items: center;
  padding: 10px 10px;
  gap: 6px;
}
.we-settings-item-row:hover { background: #1a1e23; }

.we-settings-item-name {
  flex: 1;
  font-size: 11px;
  line-height: 1.5;
  color: #c8cdd4;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.we-settings-item-name:hover { color: #00ff99; }
.we-settings-item-name.disabled { color: #6a7888; }
.we-settings-item-delete {
  background: none;
  border: none;
  color: #8a9098;
  font-size: 13px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  flex-shrink: 0;
}
.we-settings-item-delete:hover { color: #c96b6b; }
#we-settings-items-footer {
  padding: 6px 8px;
  border-top: 1px solid #2a3040;
  flex-shrink: 0;
}
#we-settings-footer {
  border-top: 1px solid #2a3040;
  padding: 10px 16px;
  background: #0e1114;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}
.we-settings-toggle-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: #7a8490;
}
.we-settings-toggle {
  width: 32px; height: 18px;
  background: #2a2f35;
  border-radius: 9px;
  position: relative;
  cursor: pointer;
  border: none;
  transition: background 0.2s;
  flex-shrink: 0;
}
.we-settings-toggle.on { background: #006633; }
.we-settings-toggle::after {
  content: '';
  position: absolute;
  width: 12px; height: 12px;
  background: #c8cdd4;
  border-radius: 50%;
  top: 3px; left: 3px;
  transition: left 0.2s;
}
.we-settings-toggle.on::after { left: 17px; }
.we-settings-add-btn {
  width: 100%;
  padding: 5px;
  background: #1a1e23;
  border: 1px dashed #2a3040;
  border-radius: 4px;
  color: #4a5568;
  font-size: 11px;
  cursor: pointer;
  text-align: center;
  transition: color 0.1s, border-color 0.1s;
}
.we-settings-add-btn:hover { color: #c8cdd4; border-color: #4a5568; }
#we-btn-lock {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 12px;
  padding: 0;
  line-height: 1;
  opacity: 0.6;
  transition: opacity 0.15s;
  flex-shrink: 0;
}
.we-settings-item-drag {
  color: #3a4555;
  font-size: 13px;
  cursor: grab;
  padding: 0 2px;
  flex-shrink: 0;
  user-select: none;
}
.we-settings-item-drag:active { cursor: grabbing; }
.we-settings-item-row.drag-over-top { border-top: 2px solid #5080b0; }
.we-settings-item-row.drag-over-bot { border-bottom: 2px solid #5080b0; }
#we-btn-lock:hover { opacity: 1; }
    `;
    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.size.h + 'px';

    panel.innerHTML = `
        <div id="we-titlebar">
      <div id="we-title-icon"></div>
      <span id="we-title-text">OpsCheck</span>
      <button id="we-btn-settings" title="Settings">⚙</button>
      <button id="we-btn-lock">🔓</button>
    </div>
    <div id="we-selector">
      <select id="we-sel-dropdown"></select>
    </div>
    <div id="we-upper"></div>
        <div id="we-lower">
      <button class="we-ctrl-btn" id="we-btn-reset">Reset</button>
    </div>
    <div id="we-resize-handle"></div>
    `;

    document.body.appendChild(panel);
    const resizeHandle = panel.querySelector('#we-resize-handle');
    function updateResizeHandle() {}


    const fab = document.createElement('button');
    fab.id = 'we-fab';
    fab.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 13L9 18L20 7" stroke="#00ff99" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
    document.body.appendChild(fab);



    function applyFabPos() {
        const size = 44;
        const margin = 15;

        let x = state.fabPos.x;
        let y = state.fabPos.y;

        const maxX = window.innerWidth - size - margin;
        const maxY = window.innerHeight - size - margin;

        x = Math.max(margin, Math.min(x, maxX));
        y = Math.max(margin, Math.min(y, maxY));

        fab.style.left = x + 'px';
        fab.style.top = y + 'px';
        // Do NOT save state here during resize
    }

    function applyPanelVisible() {
        panel.style.display = state.panelVisible ? '' : 'none';
    }

    applyFabPos();
    applyPanelVisible();



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



    const upper = panel.querySelector('#we-upper');
    const btnReset = panel.querySelector('#we-btn-reset');
    const btnLock = panel.querySelector('#we-btn-lock');
    const btnSettings = panel.querySelector('#we-btn-settings');
    const titlebar = panel.querySelector('#we-titlebar');
    const selDropdown = panel.querySelector('#we-sel-dropdown');

    let resizing = false, resStartX, resStartY, resStartW, resStartH;

    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 => {
            if (!list.enabled) return;
            const opt = document.createElement('option');
            opt.value = list.id;
            opt.textContent = list.name;
            if (list.id === state.activeListId) opt.selected = true;
            selDropdown.appendChild(opt);
        });

    }

    // ─── Render Upper ─────────────────────────────────────────────────────────
    function renderUpper() {
        renderSelector();
        upper.innerHTML = '';
        if (!activeList()) {
            const hint = document.createElement('div');
            hint.className = 'we-empty-hint';
            hint.textContent = 'No checklists enabled. Open Settings to enable one.';
            upper.appendChild(hint);
            return;
        }
        const visibleButtons = activeList().buttons.filter(b => b.enabled);
        if (!activeList().buttons.length) {
            const hint = document.createElement('div');
            hint.className = 'we-empty-hint';
            hint.textContent = 'No items yet. Open Settings to add one.';
            upper.appendChild(hint);
            return;
        }
        if (!visibleButtons.length) {
            const hint = document.createElement('div');
            hint.className = 'we-empty-hint';
            hint.textContent = 'No visible items. Open Settings to enable some.';
            upper.appendChild(hint);
            return;
        }

        activeList().buttons.forEach((btn, idx) => {
            if (!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 (btn.done) {
                actionBtn.disabled = true;
                actionBtn.style.pointerEvents = 'none';
                row.style.cursor = 'pointer';
                row.addEventListener('click', () => toggleDone(idx));
            } else {
                actionBtn.disabled = false;
                actionBtn.addEventListener('click', () => handleActionClick(idx));
            }
            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(listId, callback) {
        const targetList = listId ? state.lists.find(l => l.id === listId) : activeList();
        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;
            targetList.buttons.push({ name, url, done: false, enabled: true });
            saveState();
            if (callback) callback(); else 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();
        }));


    }

    function showEditModal(idx, listId, callback) {
        const targetList = listId ? state.lists.find(l => l.id === listId) : activeList();
        const btn = targetList.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;
            targetList.buttons[idx].name = name;
            targetList.buttons[idx].url = url;
            saveState();
            if (callback) callback(); else 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();
        }));


    }

    function showDeleteConfirm(idx, listId, callback) {
        const targetList = listId ? state.lists.find(l => l.id === listId) : activeList();
        const btn = targetList.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', () => {
            targetList.buttons.splice(idx, 1);
            saveState();
            if (callback) callback(); else renderUpper();
            overlay.remove();
        });

    }

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

    function showNewListModal(callback, noActivate) {

        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: [], enabled: true };
            state.lists.push(newList);
            if (!noActivate) state.activeListId = newList.id;
            saveState();
            if (callback) callback(newList.id); else 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(); }
        });
    }

    function showRenameListModal(listId, callback) {
        const list = listId ? state.lists.find(l => l.id === listId) : 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();
            if (callback) callback(); else 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();
        });

    }

    function showDeleteListConfirm(listId, callback) {
        const list = listId ? state.lists.find(l => l.id === listId) : activeList();

        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);
        if (state.activeListId === list.id) state.activeListId = state.lists[0].id;
        saveState();
        if (callback) callback(); else renderUpper();
        overlay.remove();

    });

}

    let settingsSelectedListId = null;

    function showSettingsPanel() {
        settingsSelectedListId = state.activeListId;

        const overlay = document.createElement('div');
        overlay.id = 'we-settings-overlay';
        document.body.appendChild(overlay);

        const sp = document.createElement('div');
        sp.id = 'we-settings-panel';
        overlay.appendChild(sp);

        sp.innerHTML = `
        <div id="we-settings-header">
            <h2>⚙ Settings</h2>
            <button id="we-settings-close">✕</button>
        </div>
        <div id="we-settings-body">
            <div id="we-settings-lists">
                <div id="we-settings-lists-scroll"></div>
                <div id="we-settings-lists-footer">
                    <button class="we-settings-add-btn" id="we-settings-new-list">+ New Checklist</button>
                </div>
            </div>
            <div id="we-settings-items">
                <div id="we-settings-items-header" id="we-settings-items-title"></div>
                <div id="we-settings-items-scroll"></div>
                <div id="we-settings-items-footer">
                    <button class="we-settings-add-btn" id="we-settings-new-item">+ Add Item</button>
                </div>
            </div>
        </div>
        <div id="we-settings-footer">
            <div class="we-settings-toggle-row">
                <button class="we-settings-toggle ${state.hideOnAttack ? 'on' : ''}" id="we-settings-attack-toggle"></button>
                <span>Hide checklist on attack pages</span>
            </div>
        </div>
    `;

    function renderSettingsLists() {
        const scroll = sp.querySelector('#we-settings-lists-scroll');
        scroll.innerHTML = '';
        const sorted = [...state.lists].sort((a, b) => a.name.localeCompare(b.name));
        sorted.forEach(list => {
            const row = document.createElement('div');
            row.className = 'we-settings-list-row' + (list.id === settingsSelectedListId ? ' active' : '');

            const name = document.createElement('span');
            name.className = 'we-settings-list-name';
            name.textContent = list.name;
            name.addEventListener('click', () => {
                settingsSelectedListId = list.id;
                renderSettingsLists();
                renderSettingsItems();
            });

            const eyeBtn = document.createElement('button');
            eyeBtn.className = 'we-settings-list-edit';
            eyeBtn.textContent = list.enabled ? '👁' : '🚫';
            eyeBtn.title = list.enabled ? 'Hide from dropdown' : 'Show in dropdown';
            eyeBtn.addEventListener('click', e => {
                e.stopPropagation();
                list.enabled = !list.enabled;
                saveState();
                renderSettingsLists();
                renderUpper();
            });

            const editBtn = document.createElement('button');
            editBtn.className = 'we-settings-list-edit';
            editBtn.textContent = '✎';
            editBtn.title = 'Rename';
            editBtn.addEventListener('click', e => {
                e.stopPropagation();
                showRenameListModal(list.id, () => {
                    renderSettingsLists();
                    renderSettingsItems();
                    renderUpper();
                });
            });

            const delBtn = document.createElement('button');
            delBtn.className = 'we-settings-list-delete';
            delBtn.textContent = '🗑';
            delBtn.title = 'Delete';
            delBtn.addEventListener('click', e => {
                e.stopPropagation();
                showDeleteListConfirm(list.id, () => {
                    settingsSelectedListId = state.activeListId;
                    renderSettingsLists();
                    renderSettingsItems();
                    renderUpper();
                });
            });

            row.appendChild(name);
            row.appendChild(eyeBtn);
            row.appendChild(editBtn);
            row.appendChild(delBtn);
            scroll.appendChild(row);
        });
    }

    function renderSettingsItems() {
        const scroll = sp.querySelector('#we-settings-items-scroll');
        scroll.innerHTML = '';

        if (!settingsSelectedListId) return;
        const list = state.lists.find(l => l.id === settingsSelectedListId);
        if (!list) return;

        list.buttons.forEach((btn, idx) => {
            const row = document.createElement('div');
            row.className = 'we-settings-item-row';

            const dragHandle = document.createElement('span');
            dragHandle.className = 'we-settings-item-drag';
            dragHandle.textContent = '⠿';

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

            // Desktop drag
            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 = '';
                sp.querySelectorAll('.we-settings-item-row').forEach(r => r.classList.remove('drag-over-top', 'drag-over-bot'));
            });
            row.addEventListener('dragover', e => {
                e.preventDefault();
                const rect = row.getBoundingClientRect();
                const mid = rect.top + rect.height / 2;
                row.classList.toggle('drag-over-top', e.clientY < mid);
                row.classList.toggle('drag-over-bot', e.clientY >= mid);
            });
            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 = list.buttons.splice(fromIdx, 1)[0];
                if (fromIdx < toIdx) toIdx--;
                list.buttons.splice(toIdx, 0, item);
                saveState();
                renderSettingsItems();
                renderUpper();
            });

            // Touch drag
            dragHandle.addEventListener('touchstart', e => {
                const touch = e.touches[0];
                let touchDragging = true;
                const startY = touch.clientY;
                const rowHeight = row.getBoundingClientRect().height;
                row.style.opacity = '0.4';

                const onMove = e => {
                    if (!touchDragging) return;
                    const t = e.touches[0];
                    const rows = [...sp.querySelectorAll('.we-settings-item-row')];
                    rows.forEach(r => r.classList.remove('drag-over-top', 'drag-over-bot'));
                    const target = rows.find(r => {
                        const rect = r.getBoundingClientRect();
                        return t.clientY >= rect.top && t.clientY <= rect.bottom;
                    });
                    if (target && target !== row) {
                        const rect = target.getBoundingClientRect();
                        const mid = rect.top + rect.height / 2;
                        target.classList.toggle('drag-over-top', t.clientY < mid);
                        target.classList.toggle('drag-over-bot', t.clientY >= mid);
                    }
                    e.preventDefault();
                };

                const onEnd = e => {
                    touchDragging = false;
                    row.style.opacity = '';
                    const rows = [...sp.querySelectorAll('.we-settings-item-row')];
                    const overTop = sp.querySelector('.we-settings-item-row.drag-over-top');
                    const overBot = sp.querySelector('.we-settings-item-row.drag-over-bot');
                    rows.forEach(r => r.classList.remove('drag-over-top', 'drag-over-bot'));

                    const target = overTop || overBot;
                    if (!target) return;
                    const toIdx2 = parseInt(target.dataset.idx);
                    const insertBefore = !!overTop;
                    let toIdx = insertBefore ? toIdx2 : toIdx2 + 1;
                    if (idx === toIdx || idx === toIdx - 1) return;
                    const item = list.buttons.splice(idx, 1)[0];
                    if (idx < toIdx) toIdx--;
                    list.buttons.splice(toIdx, 0, item);
                    saveState();
                    renderSettingsItems();
                    renderUpper();

                    document.removeEventListener('touchmove', onMove);
                    document.removeEventListener('touchend', onEnd);
                };

                document.addEventListener('touchmove', onMove, { passive: false });
                document.addEventListener('touchend', onEnd);
                e.stopPropagation();
            }, { passive: true });

            const toggleBtn = document.createElement('button');
            toggleBtn.className = 'we-settings-list-edit';
            toggleBtn.textContent = btn.enabled ? '👁' : '🚫';
            toggleBtn.title = btn.enabled ? 'Hide' : 'Show';
            toggleBtn.addEventListener('click', () => {
                btn.enabled = !btn.enabled;
                saveState();
                renderSettingsItems();
                renderUpper();
            });

            const nameEl = document.createElement('span');
            nameEl.className = 'we-settings-item-name' + (btn.enabled ? '' : ' disabled');
            nameEl.textContent = btn.name + (btn.url ? ' 🔗' : '');
            nameEl.title = 'Click to edit';
            nameEl.addEventListener('click', () => {
                showEditModal(idx, list.id, () => {
                    renderSettingsItems();
                    renderUpper();
                });
            });

            const delBtn = document.createElement('button');
            delBtn.className = 'we-settings-item-delete';
            delBtn.textContent = '🗑';
            delBtn.title = 'Delete';
            delBtn.addEventListener('click', () => {
                showDeleteConfirm(idx, list.id, () => {
                    renderSettingsItems();
                    renderUpper();
                });
            });

            row.appendChild(dragHandle);
            row.appendChild(toggleBtn);
            row.appendChild(nameEl);
            row.appendChild(delBtn);
            scroll.appendChild(row);
        });
    }

    sp.querySelector('#we-settings-close').addEventListener('click', () => overlay.remove());


    sp.querySelector('#we-settings-new-list').addEventListener('click', () => {
        showNewListModal(() => {
            renderSettingsLists();
            renderUpper();
        }, true);
    });

    sp.querySelector('#we-settings-new-item').addEventListener('click', () => {
        if (!settingsSelectedListId) return;
        showAddModal(settingsSelectedListId, () => {
            renderSettingsItems();
            renderUpper();
        });
    });

    sp.querySelector('#we-settings-attack-toggle').addEventListener('click', e => {
        state.hideOnAttack = !state.hideOnAttack;
        e.target.classList.toggle('on', state.hideOnAttack);
        saveState();
    });

    renderSettingsLists();
    renderSettingsItems();
}

    // ─── Control Buttons ──────────────────────────────────────────────────────


    btnReset.addEventListener('click', () => {
        activeList().buttons.forEach(b => b.done = false);
        saveState();
        renderUpper();
    });

    btnSettings.addEventListener('click', () => showSettingsPanel());

    selDropdown.addEventListener('change', () => {
        state.activeListId = selDropdown.value;
        saveState();
        renderUpper();
    });



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

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

    // ─── FAB Drag & Tap (Torn PDA + Desktop Fix v8) ───────────────────────────
    let fabDragged = false;
    let fabDragging = false, fabStartX, fabStartY, fabOrigX, fabOrigY;
    let lastTapTime = 0;
    let isDragging = false;   // Global flag to suppress click after drag

    function onFabDragStart(clientX, clientY) {
        fabDragging = true;
        fabDragged = false;
        isDragging = false;
        fabStartX = clientX;
        fabStartY = clientY;
        fabOrigX = fab.offsetLeft;
        fabOrigY = fab.offsetTop;
    }

    function onFabDragMove(clientX, clientY) {
        if (!fabDragging) return;

        const dx = clientX - fabStartX;
        const dy = clientY - fabStartY;

        if (Math.abs(dx) > 6 || Math.abs(dy) > 6) {
            fabDragged = true;
            isDragging = true;
        }

        const size = 44, margin = 10;
        const nx = Math.max(margin, Math.min(fabOrigX + dx, window.innerWidth - size - margin));
        const ny = Math.max(margin, Math.min(fabOrigY + dy, window.innerHeight - size - margin));

        fab.style.left = nx + 'px';
        fab.style.top = ny + 'px';
        state.fabPos = { x: nx, y: ny };
    }

    function onFabDragEnd() {
        fabDragging = false;
        if (fabDragged) {
            saveState();
        }
        // Do not reset isDragging here - let click handler handle it
    }

    function handleFabTap() {
        const now = Date.now();
        if (now - lastTapTime < 250) return;
        lastTapTime = now;

        if (fabDragged || isDragging) {
            fabDragged = false;
            isDragging = false;
            return;
        }

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

    // === LISTENERS ===
    fab.addEventListener('click', (e) => {
        // This is the critical part for desktop
        if (isDragging) {
            isDragging = false;
            fabDragged = false;
            return;
        }
        handleFabTap();
    });

    fab.addEventListener('mousedown', e => {
        if (e.button !== 0) return;
        onFabDragStart(e.clientX, e.clientY);
        e.preventDefault();
    });

    fab.addEventListener('touchstart', e => {
        const t = e.touches[0];
        onFabDragStart(t.clientX, t.clientY);
        fab.classList.add('glow');
        e.preventDefault();
    }, { passive: false });

    fab.addEventListener('touchmove', e => {
        if (!fabDragging) return;
        const t = e.touches[0];
        onFabDragMove(t.clientX, t.clientY);
        e.preventDefault();
        updateResizeHandle();
    }, { passive: false });

    fab.addEventListener('touchend', e => {
        fab.classList.remove('glow');
        setTimeout(() => {
            handleFabTap();
            onFabDragEnd();
        }, 30);
    }, { passive: true });

    document.addEventListener('mousemove', e => {
        onFabDragMove(e.clientX, e.clientY);
        updateResizeHandle();
    });

    document.addEventListener('mouseup', () => {
        onFabDragEnd();
        // Small delay to let click event see the flag
        setTimeout(() => {
            isDragging = false;
        }, 50);
    });

    document.addEventListener('touchend', onFabDragEnd);


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

    titlebar.addEventListener('mousedown', e => {
        if (e.button !== 0 || state.locked) return;
        dragging = true;
        dragStartX = e.clientX;
        dragStartY = e.clientY;
        panelStartX = panel.offsetLeft;
        panelStartY = panel.offsetTop;
        e.preventDefault();
    });
    titlebar.addEventListener('touchstart', e => {
        if (state.locked) return;
        if (e.target.closest('button')) return;
        const t = e.touches[0];
        dragging = true;
        dragStartX = t.clientX;
        dragStartY = t.clientY;
        panelStartX = panel.offsetLeft;
        panelStartY = panel.offsetTop;
        e.preventDefault();
    }, { passive: false });


    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';
        updateResizeHandle();
    });
    document.addEventListener('touchmove', e => {
        if (!dragging && !resizing) return;
        if (dragging) {
            const t = e.touches[0];
            const nx = panelStartX + (t.clientX - dragStartX);
            const ny = panelStartY + (t.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';
            updateResizeHandle();
        }
        if (resizing) {
            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 (dragging) {
            dragging = false;
            state.pos = { x: panel.offsetLeft, y: panel.offsetTop };
            saveState();
        }

        if (resizing) {
            resizing = false;
            state.size = { w: panel.offsetWidth, h: panel.offsetHeight };
            saveState();
        }
    });

    document.addEventListener('touchend', () => {
        if (dragging) {
            dragging = false;
            state.pos = { x: panel.offsetLeft, y: panel.offsetTop };
            saveState();
        }
        if (resizing) {
            resizing = false;
            state.size = { w: panel.offsetWidth, h: panel.offsetHeight };
            saveState();
        }
    });

    // ─── Resize ───────────────────────────────────────────────────────────────


    resizeHandle.addEventListener('mousedown', e => {
        if (e.button !== 0 || state.locked) 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) return;
        const t = e.touches[0];
        resizing = true;
        resStartX = t.clientX;
        resStartY = t.clientY;
        resStartW = panel.offsetWidth;
        resStartH = panel.offsetHeight;
        e.preventDefault();
    }, { 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';
        updateResizeHandle();
    });






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

    // ─── Window Resize Handler (Preserve Original Position) ───────────────────
    window.addEventListener('resize', () => {
        // --- Main Panel ---
        const savedLeft = state.pos.x;
        const savedTop = state.pos.y;

        const panelW = panel.offsetWidth;
        const panelH = panel.offsetHeight;

        const maxLeft = Math.max(0, window.innerWidth - panelW - 20);
        const maxTop = Math.max(0, window.innerHeight - panelH - 20);

        // Only clamp for current display, do NOT overwrite saved position
        let displayLeft = Math.max(0, Math.min(savedLeft, maxLeft));
        let displayTop = Math.max(0, Math.min(savedTop, maxTop));

        panel.style.left = displayLeft + 'px';
        panel.style.top = displayTop + 'px';
        updateResizeHandle();

        // --- FAB ---
        applyFabPos();
    });

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

})();