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