WeatherBell Overlay (Enhanced)

A highly customizable overlay for WeatherBell model time data.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WeatherBell Overlay (Enhanced)
// @namespace    http://tampermonkey.net/
// @version      2.0.1
// @description  A highly customizable overlay for WeatherBell model time data.
// @match        https://maps.weatherbell.com/*
// @match        https://models.weatherbell.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // --- STATE & CONFIGURATION ---

  let targetNode = null;
  let observer = null;
  let isMinimized = false;
  let isLocked = false;
  let modelInitTime = null;
  let tempSettings = {};

  const defaultSettings = {
    position: { top: 100, left: 200 },
    dimensions: { width: 500, padding: 30, borderRadius: 16 },
    font: { size: 72, family: "Saira Condensed" },
    theme: "dark",
    display: {
      opacity: 0.95,
      showDate: true,
      showAccent: true,
      showDelta: true,
      showTimezones: ["ET"],
      dateTimeSpacing: 4,
    },
    timeFormat: "12",
    locked: false,
  };

  const themes = {
    dark: {
      bg: "rgba(20, 20, 25, 1)",
      text: "#ffffff",
      subtext: "#9ca3af",
      accent: "rgba(96, 165, 250, 0.5)",
      border: "rgba(255,255,255,0.08)",
    },
    light: {
      bg: "rgba(255, 255, 255, 1)",
      text: "#1f2937",
      subtext: "#6b7280",
      accent: "rgba(59, 130, 246, 0.5)",
      border: "rgba(0,0,0,0.08)",
    },
    minimal: {
      bg: "rgba(0, 0, 0, 1)",
      text: "#ffffff",
      subtext: "#ffffff",
      accent: "rgba(255, 255, 255, 0.3)",
      border: "rgba(255,255,255,0.15)",
    },
    contrast: {
      bg: "rgba(0, 0, 0, 1)",
      text: "#00ff00",
      subtext: "#ffff00",
      accent: "rgba(0, 255, 0, 0.5)",
      border: "rgba(0,255,0,0.3)",
    },
  };

  const fonts = {
    "Saira Condensed": "Saira+Condensed:wght@600;700",
    "Roboto Condensed": "Roboto+Condensed:wght@700",
    Oswald: "Oswald:wght@700",
    Montserrat: "Montserrat:wght@700",
  };

  // --- UTILITY FUNCTIONS ---

  function loadSettings() {
    try {
      const saved = localStorage.getItem("weatherbell_overlay_settings_v2");
      if (saved) {
        // Deep merge to ensure new defaults are applied if not in saved settings
        const parsed = JSON.parse(saved);
        return {
          ...defaultSettings,
          ...parsed,
          dimensions: { ...defaultSettings.dimensions, ...parsed.dimensions },
          font: { ...defaultSettings.font, ...parsed.font },
          display: { ...defaultSettings.display, ...parsed.display },
        };
      }
    } catch (e) {
      console.error("Error loading settings, using defaults.", e);
    }
    return defaultSettings;
  }

  function saveSettings(settingsToSave) {
    localStorage.setItem(
      "weatherbell_overlay_settings_v2",
      JSON.stringify(settingsToSave)
    );
  }

  let settings = loadSettings();
  isLocked = settings.locked;

  // --- DYNAMIC FONT LOADING ---

  function loadFont(fontFamily) {
    const fontId = `font-${fontFamily.replace(/\s+/g, "-")}`;
    if (document.getElementById(fontId) || !fonts[fontFamily]) return;

    const fontLink = document.createElement("link");
    fontLink.id = fontId;
    fontLink.rel = "stylesheet";
    fontLink.href = `https://fonts.googleapis.com/css2?family=${fonts[fontFamily]}&display=swap`;
    document.head.appendChild(fontLink);
  }
  loadFont(settings.font.family);

  // --- CREATE UI ELEMENTS ---

  const overlay = document.createElement("div");
  const controlsBar = document.createElement("div");
  const dateText = document.createElement("div");
  const timeContainer = document.createElement("div");
  const accentLine = document.createElement("div");
  const deltaText = document.createElement("div");
  const resizeHandle = document.createElement("div");

  function setupElements() {
    // Overlay
    overlay.style.position = "fixed";
    overlay.style.top = settings.position.top + "px";
    overlay.style.left = settings.position.left + "px";
    overlay.style.zIndex = "99999";
    overlay.style.backdropFilter = "blur(10px)";
    overlay.style.boxShadow =
      "0 8px 32px rgba(0,0,0,0.8), 0 0 1px rgba(255,255,255,0.1) inset";
    overlay.style.display = "flex";
    overlay.style.flexDirection = "column";
    overlay.style.justifyContent = "center";
    overlay.style.alignItems = "center";
    overlay.style.gap = "8px";
    overlay.style.pointerEvents = "auto";
    overlay.style.transition = "all 0.3s ease";
    document.body.appendChild(overlay);

    // Controls Bar
    controlsBar.style.position = "absolute";
    controlsBar.style.top = "8px";
    controlsBar.style.right = "8px";
    controlsBar.style.display = "flex";
    controlsBar.style.gap = "6px";
    controlsBar.style.opacity = "0";
    controlsBar.style.transition = "opacity 0.3s ease";
    overlay.appendChild(controlsBar);

    // Date Text
    dateText.style.fontWeight = "600";
    dateText.style.textTransform = "uppercase";
    dateText.style.letterSpacing = "2px";
    dateText.style.marginBottom = "4px";
    overlay.appendChild(dateText);

    // Time Container
    timeContainer.style.display = "flex";
    timeContainer.style.flexDirection = "column";
    timeContainer.style.alignItems = "center";
    timeContainer.style.gap = "4px";
    overlay.appendChild(timeContainer);

    // Accent Line
    accentLine.style.width = "80%";
    accentLine.style.height = "2px";
    accentLine.style.marginTop = "12px";
    overlay.appendChild(accentLine);

    // Delta Text
    deltaText.style.fontWeight = "600";
    deltaText.style.marginTop = "8px";
    overlay.appendChild(deltaText);

    // Resize Handle
    resizeHandle.style.position = "absolute";
    resizeHandle.style.bottom = "0";
    resizeHandle.style.right = "0";
    resizeHandle.style.width = "20px";
    resizeHandle.style.height = "20px";
    resizeHandle.style.cursor = "se-resize";
    resizeHandle.style.zIndex = "100000";
    overlay.appendChild(resizeHandle);
  }
  setupElements();

  function applyStyles(s) {
    const theme = themes[s.theme];
    const bgColor = theme.bg.replace(/, 1\)$/, `, ${s.display.opacity})`);

    loadFont(s.font.family);

    overlay.style.width = s.dimensions.width + "px";
    overlay.style.padding = `${s.dimensions.padding}px ${
      s.dimensions.padding * 1.33
    }px`;
    overlay.style.borderRadius = s.dimensions.borderRadius + "px";
    overlay.style.backgroundColor = bgColor;
    overlay.style.color = theme.text;
    overlay.style.fontFamily = `'${s.font.family}', sans-serif`;
    overlay.style.fontSize = s.font.size + "px";
    overlay.style.fontWeight = "700";
    overlay.style.border = "1px solid " + theme.border;
    overlay.style.cursor = s.locked ? "default" : "move";
    resizeHandle.style.display = s.locked ? "none" : "block";

    dateText.style.display = s.display.showDate ? "block" : "none";
    dateText.style.fontSize = s.font.size * 0.39 + "px";
    dateText.style.color = theme.subtext;
    dateText.style.marginBottom = s.display.dateTimeSpacing + "px";

    accentLine.style.display = s.display.showAccent ? "block" : "none";
    accentLine.style.background = `linear-gradient(90deg, transparent, ${theme.accent}, transparent)`;

    deltaText.style.display = s.display.showDelta ? "block" : "none";
    deltaText.style.fontSize = s.font.size * 0.25 + "px";
    deltaText.style.color = theme.subtext;

    if (targetNode) updateText(targetNode.innerText);
  }

  // --- CONTROLS & SETTINGS PANEL ---

  const settingsPanel = document.createElement("div");
  settingsPanel.style.position = "fixed";
  settingsPanel.style.top = "50%";
  settingsPanel.style.left = "50%";
  settingsPanel.style.transform = "translate(-50%, -50%)";
  settingsPanel.style.zIndex = 100000;
  settingsPanel.style.display = "none";
  settingsPanel.style.width = "500px";
  settingsPanel.style.maxHeight = "90vh";
  settingsPanel.style.overflowY = "auto";
  settingsPanel.style.boxShadow = "0 20px 60px rgba(0,0,0,0.9)";
  settingsPanel.style.backdropFilter = "blur(10px)";
  document.body.appendChild(settingsPanel);

  function createSettingRow(label, control) {
    return `<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                    <label style="font-size: 18px; color: ${
                      themes[settings.theme].subtext
                    };">${label}</label>
                    <div style="width: 250px;">${control}</div>
                </div>`;
  }
  function createSlider(id, value, min, max, unit = "") {
    return `<div style="display: flex; align-items: center; gap: 10px;">
                    <input type="range" id="${id}" min="${min}" max="${max}" value="${value}" style="width: 100%;">
                    <span id="${id}Value" style="min-width: 50px; text-align: right;">${value}${unit}</span>
                </div>`;
  }
  function createSelect(id, options) {
    return `<select id="${id}" style="width: 100%; padding: 8px; border-radius: 8px; background: rgba(255,255,255,0.1); color: ${
      themes[settings.theme].text
    }; border: 1px solid ${
      themes[settings.theme].border
    }; font-size: 16px;">${options}</select>`;
  }
  function createCheckboxGroup(groupId, options, checkedValues) {
    return `<div id="${groupId}" style="display: flex; justify-content: flex-end; gap: 15px;">${options
      .map(
        (opt) => `
            <label style="font-size: 16px;"><input type="checkbox" value="${opt}" ${
          checkedValues.includes(opt) ? "checked" : ""
        }> ${opt}</label>
        `
      )
      .join("")}</div>`;
  }

  function buildSettingsPanel() {
    const s = tempSettings;
    const theme = themes[s.theme];
    settingsPanel.innerHTML = `
            <div style="background-color: ${theme.bg}; border: 2px solid ${
      theme.border
    }; border-radius: 16px; padding: 30px; color: ${
      theme.text
    }; font-family: '${s.font.family}', sans-serif;">
            <h2 style="margin: 0 0 30px 0; font-size: 32px; font-weight: 700;">Settings</h2>

            <h3 style="font-size: 20px; color: ${
              theme.subtext
            }; margin: 25px 0 15px; border-bottom: 1px solid ${
      theme.border
    }; padding-bottom: 5px;">Appearance</h3>
            ${createSettingRow(
              "Theme",
              createSelect(
                "theme",
                Object.keys(themes)
                  .map(
                    (t) =>
                      `<option value="${t}" ${
                        s.theme === t ? "selected" : ""
                      }>${t.charAt(0).toUpperCase() + t.slice(1)}</option>`
                  )
                  .join("")
              )
            )}
            ${createSettingRow(
              "Font Family",
              createSelect(
                "fontFamily",
                Object.keys(fonts)
                  .map(
                    (f) =>
                      `<option value="${f}" ${
                        s.font.family === f ? "selected" : ""
                      }>${f}</option>`
                  )
                  .join("")
              )
            )}
            ${createSettingRow(
              "Font Size",
              createSlider("fontSize", s.font.size, 24, 120, "px")
            )}
            ${createSettingRow(
              "Opacity",
              createSlider("opacity", s.display.opacity * 100, 50, 100, "%")
            )}

            <h3 style="font-size: 20px; color: ${
              theme.subtext
            }; margin: 25px 0 15px; border-bottom: 1px solid ${
      theme.border
    }; padding-bottom: 5px;">Dimensions</h3>
            ${createSettingRow(
              "Width",
              createSlider("width", s.dimensions.width, 100, 1000, "px")
            )}
            ${createSettingRow(
              "Padding",
              createSlider("padding", s.dimensions.padding, 10, 80, "px")
            )}
            ${createSettingRow(
              "Border Radius",
              createSlider(
                "borderRadius",
                s.dimensions.borderRadius,
                0,
                50,
                "px"
              )
            )}
            ${createSettingRow(
              "Date/Time Spacing",
              createSlider(
                "dateTimeSpacing",
                s.display.dateTimeSpacing,
                0,
                30,
                "px"
              )
            )}

            <h3 style="font-size: 20px; color: ${
              theme.subtext
            }; margin: 25px 0 15px; border-bottom: 1px solid ${
      theme.border
    }; padding-bottom: 5px;">Content</h3>
            ${createSettingRow(
              "Time Format",
              createSelect(
                "timeFormat",
                `<option value="12" ${
                  s.timeFormat === "12" ? "selected" : ""
                }>12 Hour</option><option value="24" ${
                  s.timeFormat === "24" ? "selected" : ""
                }>24 Hour</option>`
              )
            )}
            ${createSettingRow(
              "Timezones",
              createCheckboxGroup(
                "timezones",
                ["ET", "UTC", "Local"],
                s.display.showTimezones
              )
            )}
            ${createSettingRow(
              "Show Elements",
              createCheckboxGroup(
                "showElements",
                ["Date", "Accent", "Delta"],
                [
                  s.display.showDate && "Date",
                  s.display.showAccent && "Accent",
                  s.display.showDelta && "Delta",
                ].filter(Boolean)
              )
            )}

            <div style="display: flex; gap: 12px; margin-top: 30px; border-top: 1px solid ${
              theme.border
            }; padding-top: 20px;">
                <button id="saveSettings" style="flex: 2; padding: 12px; border-radius: 8px; background: ${theme.accent.replace(
                  "0.5",
                  "0.8"
                )}; color: white; border: none; cursor: pointer; font-size: 18px; font-weight: 700;">Save & Reload</button>
                <button id="cancelSettings" style="flex: 1; padding: 12px; border-radius: 8px; background: rgba(255,255,255,0.1); color: ${
                  theme.text
                }; border: 1px solid ${
      theme.border
    }; cursor: pointer;">Cancel</button>
                <button id="resetSettings" style="padding: 12px; border-radius: 8px; background: rgba(220, 38, 38, 0.7); color: white; border: none; cursor: pointer;">Reset</button>
            </div>
            </div>
        `;
    addSettingsListeners();
  }

  function addSettingsListeners() {
    const update = (path, value) => {
      let obj = tempSettings;
      const keys = path.split(".");
      keys.slice(0, -1).forEach((key) => (obj = obj[key]));
      obj[keys[keys.length - 1]] = value;
      applyStyles(tempSettings);
      if (path.includes("theme") || path.includes("font.family"))
        buildSettingsPanel();
    };

    const addSliderListener = (id, path, unit = "") => {
      document.getElementById(id).addEventListener("input", (e) => {
        const value =
          id === "opacity" ? e.target.value / 100 : parseInt(e.target.value);
        document.getElementById(`${id}Value`).innerText = e.target.value + unit;
        update(path, value);
      });
    };
    addSliderListener("fontSize", "font.size", "px");
    addSliderListener("opacity", "display.opacity", "%");
    addSliderListener("width", "dimensions.width", "px");
    addSliderListener("padding", "dimensions.padding", "px");
    addSliderListener("borderRadius", "dimensions.borderRadius", "px");
    addSliderListener("dateTimeSpacing", "display.dateTimeSpacing", "px");

    document
      .getElementById("theme")
      .addEventListener("change", (e) => update("theme", e.target.value));
    document
      .getElementById("fontFamily")
      .addEventListener("change", (e) => update("font.family", e.target.value));
    document
      .getElementById("timeFormat")
      .addEventListener("change", (e) => update("timeFormat", e.target.value));

    document.getElementById("timezones").addEventListener("change", () => {
      const checked = Array.from(
        document.querySelectorAll("#timezones input:checked")
      ).map((cb) => cb.value);
      update("display.showTimezones", checked);
    });

    document.getElementById("showElements").addEventListener("change", () => {
      const checked = Array.from(
        document.querySelectorAll("#showElements input:checked")
      ).map((cb) => cb.value);
      update("display.showDate", checked.includes("Date"));
      update("display.showAccent", checked.includes("Accent"));
      update("display.showDelta", checked.includes("Delta"));
    });

    document.getElementById("saveSettings").addEventListener("click", () => {
      settings = JSON.parse(JSON.stringify(tempSettings));
      saveSettings(settings);
      location.reload();
    });

    document.getElementById("cancelSettings").addEventListener("click", () => {
      applyStyles(settings);
      toggleSettings();
    });

    document.getElementById("resetSettings").addEventListener("click", () => {
      if (
        confirm(
          "Are you sure you want to reset all settings to their defaults?"
        )
      ) {
        localStorage.removeItem("weatherbell_overlay_settings_v2");
        location.reload();
      }
    });
  }

  function toggleSettings() {
    if (settingsPanel.style.display === "none") {
      tempSettings = JSON.parse(JSON.stringify(settings));
      buildSettingsPanel();
      settingsPanel.style.display = "block";
    } else {
      settingsPanel.style.display = "none";
    }
  }

  // --- UI INTERACTIONS ---

  function createButton(icon, title, onClick) {
    const btn = document.createElement("button");
    btn.innerHTML = icon;
    btn.title = title;
    Object.assign(btn.style, {
      background: "rgba(255,255,255,0.1)",
      border: "1px solid rgba(255,255,255,0.2)",
      borderRadius: "6px",
      width: "32px",
      height: "32px",
      cursor: "pointer",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      fontSize: "16px",
      color: "inherit",
      transition: "all 0.2s",
    });
    btn.addEventListener(
      "mouseenter",
      () => (btn.style.background = "rgba(255,255,255,0.2)")
    );
    btn.addEventListener(
      "mouseleave",
      () => (btn.style.background = "rgba(255,255,255,0.1)")
    );
    btn.addEventListener("click", (e) => {
      e.stopPropagation();
      onClick();
    });
    return btn;
  }

  const settingsBtn = createButton(
    "⚙️",
    "Settings (Ctrl+Shift+S)",
    toggleSettings
  );
  const minimizeBtn = createButton("−", "Minimize", toggleMinimize);
  const lockBtn = createButton(
    isLocked ? "🔒" : "🔓",
    "Lock/Unlock Position",
    toggleLock
  );
  controlsBar.append(lockBtn, minimizeBtn, settingsBtn);

  function toggleMinimize() {
    isMinimized = !isMinimized;
    const elementsToHide = [timeContainer, dateText, accentLine, deltaText];
    if (isMinimized) {
      elementsToHide.forEach((el) => (el.style.display = "none"));
      overlay.style.width = "auto";
      overlay.style.minWidth = "200px";
      overlay.style.padding = "15px 20px";
      minimizeBtn.innerHTML = "□";
      minimizeBtn.title = "Expand";
    } else {
      applyStyles(settings); // Restore styles
      overlay.style.minWidth = "";
      minimizeBtn.innerHTML = "−";
      minimizeBtn.title = "Minimize";
    }
  }

  function toggleLock() {
    isLocked = !isLocked;
    settings.locked = isLocked;
    saveSettings(settings);
    overlay.style.cursor = isLocked ? "default" : "move";
    resizeHandle.style.display = isLocked ? "none" : "block";
    lockBtn.innerHTML = isLocked ? "🔒" : "🔓";
    lockBtn.title = isLocked ? "Unlock Position" : "Lock Position";
  }

  // --- TIME PARSING AND DISPLAY LOGIC ---

  function formatTime(timeStr) {
    try {
      const dt = new Date(timeStr);
      if (isNaN(dt)) return null;

      if (!modelInitTime) modelInitTime = dt;

      const dateOptions = { weekday: "long", timeZone: "America/New_York" };
      const dayName = dt.toLocaleString("en-US", dateOptions);

      const results = { date: dayName, times: [] };

      settings.display.showTimezones.forEach((tz) => {
        let label, timezone;
        if (tz === "ET") {
          timezone = "America/New_York";
          label = "ET";
        } else if (tz === "UTC") {
          timezone = "UTC";
          label = "UTC";
        } else if (tz === "Local") {
          timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
          label = "Local";
        }

        const timeOptions = { minute: "2-digit", timeZone: timezone };
        timeOptions.hour = settings.timeFormat === "12" ? "numeric" : "2-digit";
        timeOptions.hour12 = settings.timeFormat === "12";

        const timeFormatted = dt
          .toLocaleString("en-US", timeOptions)
          .replace(" ", "");
        results.times.push({ label, time: timeFormatted });
      });

      if (modelInitTime) {
        const delta = Math.floor(
          (Date.now() - modelInitTime.getTime()) / 1000 / 60
        );
        if (delta >= 0) {
          const hours = Math.floor(delta / 60);
          const minutes = delta % 60;
          results.delta =
            hours > 0
              ? `${hours}h ${minutes}m since model run`
              : `${minutes}m since model run`;
        }
      }
      return results;
    } catch (e) {
      return null;
    }
  }

  function updateText(text) {
    const formatted = formatTime(text);
    if (!formatted || isMinimized) return;

    dateText.innerText = formatted.date;
    timeContainer.innerHTML = "";
    const theme = themes[settings.theme];

    formatted.times.forEach((tz) => {
      const tzDiv = document.createElement("div");
      Object.assign(tzDiv.style, {
        display: "flex",
        alignItems: "center",
        gap: "12px",
        cursor: "pointer",
        title: "Click to copy",
      });

      const timeSpan = document.createElement("span");
      timeSpan.style.color = theme.text;
      timeSpan.style.lineHeight = "1";
      timeSpan.innerText = tz.time;

      const labelSpan = document.createElement("span");
      labelSpan.style.fontSize = settings.font.size * 0.39 + "px";
      labelSpan.style.color = theme.subtext;
      labelSpan.style.fontWeight = "600";
      labelSpan.innerText = tz.label;

      tzDiv.append(timeSpan, labelSpan);
      tzDiv.addEventListener("click", (e) => {
        e.stopPropagation();
        navigator.clipboard
          .writeText(`${formatted.date} ${tz.time} ${tz.label}`)
          .then(() => {
            tzDiv.style.opacity = "0.5";
            setTimeout(() => {
              tzDiv.style.opacity = "1";
            }, 200);
          });
      });
      timeContainer.appendChild(tzDiv);
    });

    if (formatted.delta && modelInitTime) {
      deltaText.innerText = formatted.delta;
    }
  }

  function initObserver() {
    const el = document.querySelector(".wba-meta-time-text");
    if (el && el !== targetNode) {
      targetNode = el;
      updateText(targetNode.innerText);
      if (observer) observer.disconnect();
      observer = new MutationObserver(() => updateText(targetNode.innerText));
      observer.observe(targetNode, {
        characterData: true,
        childList: true,
        subtree: true,
      });
    }
  }

  // --- DRAG & RESIZE HANDLERS ---
  function setupInteractions() {
    let offsetX,
      offsetY,
      isDragging = false,
      isResizing = false,
      originalWidth,
      originalHeight,
      originalMouseX,
      originalMouseY;

    const startDrag = (e) => {
      if (isLocked || e.target.tagName === "BUTTON" || isResizing) return;
      isDragging = true;
      offsetX = e.clientX - overlay.offsetLeft;
      offsetY = e.clientY - overlay.offsetTop;
      overlay.style.transition = "none";
    };

    const startResize = (e) => {
      if (isLocked) return;
      e.preventDefault();
      e.stopPropagation();
      isResizing = true;
      originalWidth = overlay.offsetWidth;
      originalMouseX = e.clientX;
      overlay.style.transition = "none";
    };

    const onMouseMove = (e) => {
      if (isDragging) {
        overlay.style.left = e.clientX - offsetX + "px";
        overlay.style.top = e.clientY - offsetY + "px";
      }
      if (isResizing) {
        const newWidth = originalWidth + (e.clientX - originalMouseX);
        if (newWidth > 300) {
          // min width
          settings.dimensions.width = newWidth;
          applyStyles(settings);
        }
      }
    };

    const onMouseUp = () => {
      if (isDragging || isResizing) {
        overlay.style.transition = "all 0.3s ease";
        if (isDragging)
          settings.position = {
            top: parseInt(overlay.style.top),
            left: parseInt(overlay.style.left),
          };
        saveSettings(settings);
      }
      isDragging = false;
      isResizing = false;
    };

    overlay.addEventListener("mousedown", startDrag);
    resizeHandle.addEventListener("mousedown", startResize);
    window.addEventListener("mousemove", onMouseMove);
    window.addEventListener("mouseup", onMouseUp);

    overlay.addEventListener("mouseenter", () => {
      controlsBar.style.opacity = "1";
      if (!isDragging && !isLocked && !isResizing) {
        overlay.style.transform = "scale(1.02)";
        overlay.style.boxShadow =
          "0 12px 40px rgba(0,0,0,0.9), 0 0 1px rgba(255,255,255,0.15) inset";
      }
    });

    overlay.addEventListener("mouseleave", () => {
      controlsBar.style.opacity = "0";
      overlay.style.transform = "scale(1)";
      overlay.style.boxShadow =
        "0 8px 32px rgba(0,0,0,0.8), 0 0 1px rgba(255,255,255,0.1) inset";
    });

    document.addEventListener("keydown", (e) => {
      if (e.ctrlKey && e.shiftKey && e.key === "T") {
        e.preventDefault();
        overlay.style.display =
          overlay.style.display === "none" ? "flex" : "none";
      }
      if (e.ctrlKey && e.shiftKey && e.key === "S") {
        e.preventDefault();
        toggleSettings();
      }
    });
  }

  // --- INITIALIZATION ---
  applyStyles(settings);
  setupInteractions();
  setInterval(initObserver, 1000);
  setInterval(() => {
    if (targetNode) updateText(targetNode.innerText);
  }, 60000);
})();