Floating Website Tools

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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