Floating Website Tools

Adds floating draggable website tools: notes, calculator, and timer/stopwatch. Saves notes, minimized states, positions, timer data, and calculator display.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Advertisement:

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

Advertisement:

// ==UserScript==
// @name         Floating Website Tools
// @namespace    Violentmonkey Scripts
// @version      1.0.2
// @description  Adds floating draggable website tools: notes, calculator, and timer/stopwatch. Saves notes, minimized states, positions, timer data, and calculator display.
// @author       Mateo Benavides-Kastner
// @license      MIT
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  if (window.top !== window.self) return;
  if (document.getElementById("mateo-floating-tools-style")) return;

  const TOOL_Z = 2147483647;

  function loadJSON(key) {
    try {
      return JSON.parse(localStorage.getItem(key)) || {};
    } catch {
      return {};
    }
  }

  function saveJSON(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  const style = document.createElement("style");
  style.id = "mateo-floating-tools-style";
  style.textContent = `
    .mft-widget {
      position: fixed;
      background: #1f1f1f;
      color: white;
      border-radius: 14px;
      box-shadow: 0 8px 30px rgba(0,0,0,0.4);
      z-index: ${TOOL_Z};
      font-family: Arial, sans-serif;
      overflow: hidden;
      user-select: none;
    }

    .mft-header {
      background: #111;
      padding: 10px 12px;
      cursor: move;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-weight: bold;
      font-size: 14px;
    }

    .mft-header button,
    .mft-body button {
      background: #333;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
    }

    .mft-header button:hover,
    .mft-body button:hover {
      background: #555;
    }

    .mft-toggle {
      width: 28px;
      height: 24px;
      font-size: 18px;
      line-height: 18px;
    }

    .mft-body {
      padding: 12px;
    }

    #mateo-floating-notes.mft-minimized,
    #mateo-floating-calculator.mft-minimized,
    #mateo-universal-timer.mft-minimized {
      width: 130px !important;
    }

    .mft-minimized .mft-body {
      display: none;
    }

    #mateo-floating-notes {
      width: 280px;
    }

    #mfn-textarea {
      width: 100%;
      height: 160px;
      box-sizing: border-box;
      resize: vertical;
      border: none;
      border-radius: 8px;
      background: #000;
      color: #00ff88;
      font-size: 14px;
      padding: 10px;
      outline: none;
      font-family: Arial, sans-serif;
      user-select: text;
    }

    #mfn-footer {
      margin-top: 8px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 12px;
      color: #aaa;
    }

    #mfn-clear {
      padding: 5px 10px;
      font-size: 12px;
    }

    #mateo-floating-calculator {
      width: 260px;
      min-width: 260px;
    }

    #mateo-floating-calculator.mft-minimized {
      min-width: 130px;
    }

    #mfc-display {
      width: 100%;
      box-sizing: border-box;
      height: 42px;
      margin-bottom: 10px;
      border: none;
      border-radius: 8px;
      background: #000;
      color: #00ff88;
      font-size: 22px;
      text-align: right;
      padding: 8px;
      outline: none;
      user-select: none;
      cursor: default;
    }

    #mfc-buttons {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 7px;
    }

    #mfc-buttons button {
      height: 38px;
      border-radius: 8px;
      font-size: 16px;
    }

    #mfc-buttons button[data-action="equals"] {
      background: #0078ff;
    }

    #mfc-buttons button[data-action="clear"] {
      background: #c0392b;
    }

    #mateo-universal-timer {
      width: 280px;
    }

    #mut-mode-row {
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 8px;
      font-size: 12px;
      margin-bottom: 10px;
      color: #ccc;
    }

    #mut-switch {
      position: relative;
      display: inline-block;
      width: 46px;
      height: 24px;
    }

    #mut-switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }

    #mut-slider {
      position: absolute;
      cursor: pointer;
      inset: 0;
      background: #444;
      border-radius: 999px;
      transition: 0.2s;
    }

    #mut-slider::before {
      content: "";
      position: absolute;
      height: 18px;
      width: 18px;
      left: 3px;
      bottom: 3px;
      background: white;
      border-radius: 50%;
      transition: 0.2s;
    }

    #mut-mode-switch:checked + #mut-slider {
      background: #0078ff;
    }

    #mut-mode-switch:checked + #mut-slider::before {
      transform: translateX(22px);
    }

    #mut-display {
      background: #000;
      color: #00ff88;
      border-radius: 8px;
      padding: 12px;
      text-align: center;
      font-size: 30px;
      font-weight: bold;
      letter-spacing: 1px;
      margin-bottom: 10px;
    }

    #mut-timer-inputs {
      display: none;
      justify-content: center;
      align-items: center;
      gap: 5px;
      margin-bottom: 10px;
    }

    #mut-timer-inputs input {
      width: 52px;
      height: 32px;
      border: none;
      border-radius: 7px;
      background: #000;
      color: #00ff88;
      text-align: center;
      font-size: 15px;
      outline: none;
    }

    #mut-buttons {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 7px;
    }

    #mut-buttons button {
      height: 34px;
      border-radius: 8px;
      font-size: 14px;
    }

    #mut-start {
      background: #137333 !important;
    }

    #mut-stop {
      background: #9a3412 !important;
    }

    #mut-reset {
      background: #c0392b !important;
    }

    .mut-timer-mode #mut-timer-inputs {
      display: flex;
    }
  `;
  document.head.appendChild(style);

  function makeDraggable(widget, header, toggleButton, saveState) {
    let dragging = false;
    let offsetX = 0;
    let offsetY = 0;

    header.addEventListener("mousedown", (event) => {
      if (event.target === toggleButton) return;

      dragging = true;

      const rect = widget.getBoundingClientRect();
      widget.style.left = `${rect.left}px`;
      widget.style.top = `${rect.top}px`;
      widget.style.right = "auto";
      widget.style.transform = "none";

      offsetX = event.clientX - rect.left;
      offsetY = event.clientY - rect.top;
    });

    document.addEventListener("mousemove", (event) => {
      if (!dragging) return;

      widget.style.left = `${event.clientX - offsetX}px`;
      widget.style.top = `${event.clientY - offsetY}px`;
    });

    document.addEventListener("mouseup", () => {
      if (dragging) {
        dragging = false;
        saveState();
      }
    });
  }

  function setupNotes() {
    if (document.getElementById("mateo-floating-notes")) return;

    const notesStorageKey = "mateo-floating-notes-" + location.hostname;
    const stateStorageKey = "mateo-floating-notes-state-v1";
    const saved = loadJSON(stateStorageKey);
    const minimized = saved.minimized !== undefined ? saved.minimized : true;

    const notes = document.createElement("div");
    notes.id = "mateo-floating-notes";
    notes.className = "mft-widget";
    if (minimized) notes.classList.add("mft-minimized");

    notes.style.top = typeof saved.top === "number" ? `${saved.top}px` : "120px";
    notes.style.left = typeof saved.left === "number" ? `${saved.left}px` : "30px";

    notes.innerHTML = `
      <div id="mfn-header" class="mft-header">
        <span>📝 Notes</span>
        <button id="mfn-toggle" class="mft-toggle" type="button">${minimized ? "+" : "−"}</button>
      </div>

      <div id="mfn-body" class="mft-body">
        <textarea id="mfn-textarea" placeholder="Type notes for this website..."></textarea>
        <div id="mfn-footer">
          <span id="mfn-status">Saved</span>
          <button id="mfn-clear" type="button">Clear</button>
        </div>
      </div>
    `;

    document.body.appendChild(notes);

    const header = document.getElementById("mfn-header");
    const toggle = document.getElementById("mfn-toggle");
    const textarea = document.getElementById("mfn-textarea");
    const status = document.getElementById("mfn-status");
    const clear = document.getElementById("mfn-clear");

    textarea.value = localStorage.getItem(notesStorageKey) || "";
    let lastSavedValue = textarea.value;

    function saveState() {
      const rect = notes.getBoundingClientRect();
      saveJSON(stateStorageKey, {
        minimized: notes.classList.contains("mft-minimized"),
        left: rect.left,
        top: rect.top
      });
    }

    function saveNotes() {
      if (textarea.value !== lastSavedValue) {
        localStorage.setItem(notesStorageKey, textarea.value);
        lastSavedValue = textarea.value;
        status.textContent = "Saved";
      }

      saveState();
    }

    textarea.addEventListener("input", () => {
      status.textContent = "Typing...";
    });

    clear.addEventListener("click", () => {
      textarea.value = "";
      localStorage.setItem(notesStorageKey, "");
      lastSavedValue = "";
      status.textContent = "Cleared";
    });

    toggle.addEventListener("click", () => {
      notes.classList.toggle("mft-minimized");
      toggle.textContent = notes.classList.contains("mft-minimized") ? "+" : "−";
      saveState();
    });

    setInterval(saveNotes, 1000);
    window.addEventListener("beforeunload", saveNotes);

    makeDraggable(notes, header, toggle, saveState);
  }

  function setupCalculator() {
    if (document.getElementById("mateo-floating-calculator")) return;

    const stateKey = "mateo-floating-calculator-state-v1";
    const saved = loadJSON(stateKey);
    const minimized = saved.minimized !== undefined ? saved.minimized : true;

    const calc = document.createElement("div");
    calc.id = "mateo-floating-calculator";
    calc.className = "mft-widget";
    if (minimized) calc.classList.add("mft-minimized");
    calc.style.width = minimized ? "130px" : "260px";

    calc.style.top = typeof saved.top === "number" ? `${saved.top}px` : "100px";

    if (typeof saved.left === "number") {
      const safeLeft = Math.max(0, Math.min(saved.left, window.innerWidth - 260));
      calc.style.left = `${safeLeft}px`;
    } else {
      calc.style.right = "30px";
    }

    calc.innerHTML = `
      <div id="mfc-header" class="mft-header">
        <span>Calculator</span>
        <button id="mfc-minimize" class="mft-toggle" type="button">${minimized ? "+" : "−"}</button>
      </div>

      <div id="mfc-body" class="mft-body">
        <input id="mfc-display" type="text" value="" placeholder="0" readonly>

        <div id="mfc-buttons">
          <button type="button" data-action="clear">C</button>
          <button type="button" data-action="back">⌫</button>
          <button type="button" data-insert="(">(</button>
          <button type="button" data-insert=")">)</button>

          <button type="button" data-insert="√(">√</button>
          <button type="button" data-insert="^">xʸ</button>
          <button type="button" data-insert="e">e</button>
          <button type="button" data-insert="/">÷</button>

          <button type="button" data-insert="7">7</button>
          <button type="button" data-insert="8">8</button>
          <button type="button" data-insert="9">9</button>
          <button type="button" data-insert="*">×</button>

          <button type="button" data-insert="4">4</button>
          <button type="button" data-insert="5">5</button>
          <button type="button" data-insert="6">6</button>
          <button type="button" data-insert="-">−</button>

          <button type="button" data-insert="1">1</button>
          <button type="button" data-insert="2">2</button>
          <button type="button" data-insert="3">3</button>
          <button type="button" data-insert="+">+</button>

          <button type="button" data-insert="0">0</button>
          <button type="button" data-insert=".">.</button>
          <button type="button" data-action="negate">±</button>
          <button type="button" data-action="equals">=</button>
        </div>
      </div>
    `;

    document.body.appendChild(calc);

    const display = document.getElementById("mfc-display");
    const header = document.getElementById("mfc-header");
    const minimize = document.getElementById("mfc-minimize");
    const buttons = document.querySelectorAll("#mfc-buttons button");

    display.value = saved.display || "";

    function saveState() {
      const rect = calc.getBoundingClientRect();
      saveJSON(stateKey, {
        minimized: calc.classList.contains("mft-minimized"),
        display: display.value,
        left: rect.left,
        top: rect.top
      });
    }

    function insertText(text) {
      if (["Error", "NaN", "∞", "-∞"].includes(display.value)) {
        display.value = "";
      }

      display.value += text;
      saveState();
    }

    function formatResult(value) {
      if (value === Infinity) return "∞";
      if (value === -Infinity) return "-∞";
      if (Number.isNaN(value)) return "NaN";
      return String(value);
    }

    function tokenize(expression) {
      const tokens = [];
      let i = 0;

      while (i < expression.length) {
        const char = expression[i];

        if (char === " ") {
          i++;
          continue;
        }

        if (/[0-9.]/.test(char)) {
          let number = char;
          i++;

          while (i < expression.length && /[0-9.]/.test(expression[i])) {
            number += expression[i];
            i++;
          }

          tokens.push({ type: "number", value: parseFloat(number) });
          continue;
        }

        if (char === "e") {
          tokens.push({ type: "number", value: Math.E });
          i++;
          continue;
        }

        if (char === "√") {
          tokens.push({ type: "function", value: "sqrt" });
          i++;
          continue;
        }

        if ("+-*/^()".includes(char)) {
          tokens.push({ type: "operator", value: char });
          i++;
          continue;
        }

        throw new Error("Bad character");
      }

      return tokens;
    }

    function toRPN(tokens) {
      const output = [];
      const operators = [];

      const precedence = {
        "u-": 5,
        "sqrt": 5,
        "^": 4,
        "*": 3,
        "/": 3,
        "+": 2,
        "-": 2
      };

      const rightAssociative = {
        "^": true,
        "u-": true,
        "sqrt": true
      };

      let previous = null;

      for (const token of tokens) {
        if (token.type === "number") {
          output.push(token);
          previous = token;
          continue;
        }

        if (token.type === "function") {
          operators.push(token);
          previous = token;
          continue;
        }

        if (token.value === "(") {
          operators.push(token);
          previous = token;
          continue;
        }

        if (token.value === ")") {
          while (operators.length && operators[operators.length - 1].value !== "(") {
            output.push(operators.pop());
          }

          if (!operators.length) throw new Error("Mismatched parentheses");

          operators.pop();

          if (operators.length && operators[operators.length - 1].type === "function") {
            output.push(operators.pop());
          }

          previous = token;
          continue;
        }

        let op = token.value;

        if (
          op === "-" &&
          (!previous ||
            previous.value === "(" ||
            ["+", "-", "*", "/", "^"].includes(previous.value))
        ) {
          op = "u-";
        }

        const current = { type: "operator", value: op };

        while (operators.length) {
          const top = operators[operators.length - 1];
          if (top.value === "(") break;

          const topPrec = precedence[top.value];
          const currentPrec = precedence[current.value];

          if (
            topPrec > currentPrec ||
            (topPrec === currentPrec && !rightAssociative[current.value])
          ) {
            output.push(operators.pop());
          } else {
            break;
          }
        }

        operators.push(current);
        previous = current;
      }

      while (operators.length) {
        const op = operators.pop();
        if (op.value === "(" || op.value === ")") throw new Error("Mismatched parentheses");
        output.push(op);
      }

      return output;
    }

    function evalRPN(rpn) {
      const stack = [];

      for (const token of rpn) {
        if (token.type === "number") {
          stack.push(token.value);
          continue;
        }

        if (token.value === "u-") {
          if (stack.length < 1) throw new Error("Bad unary");
          stack.push(-stack.pop());
          continue;
        }

        if (token.value === "sqrt") {
          if (stack.length < 1) throw new Error("Bad sqrt");
          stack.push(Math.sqrt(stack.pop()));
          continue;
        }

        if (stack.length < 2) throw new Error("Bad expression");

        const b = stack.pop();
        const a = stack.pop();

        if (token.value === "+") stack.push(a + b);
        else if (token.value === "-") stack.push(a - b);
        else if (token.value === "*") stack.push(a * b);
        else if (token.value === "/") stack.push(a / b);
        else if (token.value === "^") stack.push(Math.pow(a, b));
        else throw new Error("Unknown operator");
      }

      if (stack.length !== 1) throw new Error("Bad result");

      return stack[0];
    }

    function calculate() {
      let expression = display.value.trim();
      if (!expression) return;

      expression = expression.replace(/÷/g, "/").replace(/×/g, "*");

      const tokens = tokenize(expression);
      const rpn = toRPN(tokens);
      const result = evalRPN(rpn);

      display.value = formatResult(result);
      saveState();
    }

    buttons.forEach((button) => {
      button.addEventListener("mousedown", (event) => {
        event.preventDefault();
      });

      button.addEventListener("click", () => {
        const insert = button.dataset.insert;
        const action = button.dataset.action;

        try {
          if (insert) {
            insertText(insert);
            return;
          }

          if (action === "clear") {
            display.value = "";
            saveState();
            return;
          }

          if (action === "back") {
            display.value = display.value.slice(0, -1);
            saveState();
            return;
          }

          if (action === "negate") {
            display.value = display.value ? `-(${display.value})` : "-";
            saveState();
            return;
          }

          if (action === "equals") {
            calculate();
          }
        } catch {
          display.value = "Error";
          saveState();
        }
      });
    });

    minimize.addEventListener("click", () => {
      const wasMinimized = calc.classList.contains("mft-minimized");
      const rectBefore = calc.getBoundingClientRect();
      const rightEdge = rectBefore.right;
      const isMinimized = calc.classList.toggle("mft-minimized");
      const newWidth = isMinimized ? 130 : 260;

      calc.style.width = `${newWidth}px`;
      calc.style.minWidth = `${newWidth}px`;

      const safeLeft = Math.max(0, Math.min(rightEdge - newWidth, window.innerWidth - newWidth));
      calc.style.left = `${safeLeft}px`;
      calc.style.right = "auto";

      minimize.textContent = isMinimized ? "+" : "−";
      saveState();
    });

    window.addEventListener("beforeunload", saveState);
    makeDraggable(calc, header, minimize, saveState);
  }

  function setupTimer() {
    if (document.getElementById("mateo-universal-timer")) return;

    const stateKey = "mateo-universal-floating-timer-state-v2";
    const saved = loadJSON(stateKey);
    const minimized = saved.minimized !== undefined ? saved.minimized : true;

    const timer = document.createElement("div");
    timer.id = "mateo-universal-timer";
    timer.className = "mft-widget";
    if (minimized) timer.classList.add("mft-minimized");

    timer.style.top = typeof saved.top === "number" ? `${saved.top}px` : "20px";
    timer.style.left = typeof saved.left === "number" ? `${saved.left}px` : "50%";
    timer.style.transform = typeof saved.left === "number" ? "none" : "translateX(-50%)";

    timer.innerHTML = `
      <div id="mut-header" class="mft-header">
        <span>⏱ Timer</span>
        <button id="mut-toggle" class="mft-toggle" type="button">${minimized ? "+" : "−"}</button>
      </div>

      <div id="mut-body" class="mft-body">
        <div id="mut-mode-row">
          <span>Stopwatch</span>
          <label id="mut-switch">
            <input id="mut-mode-switch" type="checkbox">
            <span id="mut-slider"></span>
          </label>
          <span>Timer</span>
        </div>

        <div id="mut-display">00:00:00</div>

        <div id="mut-timer-inputs">
          <input id="mut-hours" type="number" min="0" max="99" value="0" title="Hours">
          <span>:</span>
          <input id="mut-minutes" type="number" min="0" max="59" value="5" title="Minutes">
          <span>:</span>
          <input id="mut-seconds" type="number" min="0" max="59" value="0" title="Seconds">
        </div>

        <div id="mut-buttons">
          <button id="mut-start" type="button">Start</button>
          <button id="mut-stop" type="button">Stop</button>
          <button id="mut-reset" type="button">Reset</button>
        </div>
      </div>
    `;

    document.body.appendChild(timer);

    const header = document.getElementById("mut-header");
    const toggle = document.getElementById("mut-toggle");
    const display = document.getElementById("mut-display");
    const modeSwitch = document.getElementById("mut-mode-switch");
    const hoursInput = document.getElementById("mut-hours");
    const minutesInput = document.getElementById("mut-minutes");
    const secondsInput = document.getElementById("mut-seconds");
    const startBtn = document.getElementById("mut-start");
    const stopBtn = document.getElementById("mut-stop");
    const resetBtn = document.getElementById("mut-reset");

    let mode = saved.mode === "timer" ? "timer" : "stopwatch";
    let running = saved.running === true;
    let intervalId = null;

    let stopwatchSeconds = Number(saved.stopwatchSeconds) || 0;
    let timerSeconds = Number(saved.timerSeconds) || 5 * 60;
    let timerOriginalSeconds = Number(saved.timerOriginalSeconds) || 5 * 60;

    hoursInput.value = Number(saved.hours ?? 0);
    minutesInput.value = Number(saved.minutes ?? 5);
    secondsInput.value = Number(saved.seconds ?? 0);

    if (mode === "timer") {
      timer.classList.add("mut-timer-mode");
      modeSwitch.checked = true;
    }

    function pad(number) {
      return String(number).padStart(2, "0");
    }

    function formatTime(totalSeconds) {
      totalSeconds = Math.max(0, Math.floor(totalSeconds));

      const hours = Math.floor(totalSeconds / 3600);
      const minutes = Math.floor((totalSeconds % 3600) / 60);
      const seconds = totalSeconds % 60;

      return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
    }

    function getInputSeconds() {
      const hours = Math.max(0, Number(hoursInput.value) || 0);
      const minutes = Math.min(59, Math.max(0, Number(minutesInput.value) || 0));
      const seconds = Math.min(59, Math.max(0, Number(secondsInput.value) || 0));

      hoursInput.value = hours;
      minutesInput.value = minutes;
      secondsInput.value = seconds;

      return hours * 3600 + minutes * 60 + seconds;
    }

    function saveState() {
      const rect = timer.getBoundingClientRect();

      saveJSON(stateKey, {
        minimized: timer.classList.contains("mft-minimized"),
        mode,
        running,
        stopwatchSeconds,
        timerSeconds,
        timerOriginalSeconds,
        hours: Number(hoursInput.value) || 0,
        minutes: Number(minutesInput.value) || 0,
        seconds: Number(secondsInput.value) || 0,
        left: rect.left,
        top: rect.top
      });
    }

    function updateDisplay() {
      display.textContent = mode === "stopwatch"
        ? formatTime(stopwatchSeconds)
        : formatTime(timerSeconds);

      saveState();
    }

    function stop() {
      running = false;

      if (intervalId) {
        clearInterval(intervalId);
        intervalId = null;
      }

      saveState();
    }

    function start() {
      if (running) return;

      if (mode === "timer" && timerSeconds <= 0) {
        timerSeconds = getInputSeconds();
        timerOriginalSeconds = timerSeconds;
      }

      if (mode === "timer" && timerSeconds <= 0) return;

      running = true;
      saveState();

      intervalId = setInterval(() => {
        if (mode === "stopwatch") {
          stopwatchSeconds++;
        } else {
          timerSeconds--;

          if (timerSeconds <= 0) {
            timerSeconds = 0;
            updateDisplay();
            stop();
            alert("Timer done!");
            return;
          }
        }

        updateDisplay();
      }, 1000);
    }

    function reset() {
      stop();

      if (mode === "stopwatch") {
        stopwatchSeconds = 0;
      } else {
        timerSeconds = getInputSeconds();
        timerOriginalSeconds = timerSeconds;
      }

      updateDisplay();
    }

    function setMode(newMode) {
      stop();
      mode = newMode;

      if (mode === "timer") {
        timer.classList.add("mut-timer-mode");
        timerSeconds = getInputSeconds();
        timerOriginalSeconds = timerSeconds;
      } else {
        timer.classList.remove("mut-timer-mode");
      }

      updateDisplay();
    }

    function resumeIfNeeded() {
      if (!running) return;

      running = false;
      start();
    }

    toggle.addEventListener("click", () => {
      timer.classList.toggle("mft-minimized");
      toggle.textContent = timer.classList.contains("mft-minimized") ? "+" : "−";
      saveState();
    });

    modeSwitch.addEventListener("change", () => {
      setMode(modeSwitch.checked ? "timer" : "stopwatch");
    });

    startBtn.addEventListener("click", start);
    stopBtn.addEventListener("click", stop);
    resetBtn.addEventListener("click", reset);

    [hoursInput, minutesInput, secondsInput].forEach((input) => {
      input.addEventListener("input", () => {
        if (!running && mode === "timer") {
          timerSeconds = getInputSeconds();
          timerOriginalSeconds = timerSeconds;
          updateDisplay();
        }
      });
    });

    window.addEventListener("beforeunload", saveState);

    updateDisplay();
    resumeIfNeeded();
    makeDraggable(timer, header, toggle, saveState);
  }

  setupNotes();
  setupCalculator();
  setupTimer();
})();