YouTube Enhancer (Loop & Screenshot Buttons)

Add Loop, Save and Copy Screenshot Buttons.

// ==UserScript==
// @name         YouTube Enhancer (Loop & Screenshot Buttons)
// @description  Add Loop, Save and Copy Screenshot Buttons.
// @icon         https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
// @version      1.7
// @author       exyezed
// @namespace    https://github.com/exyezed/youtube-enhancer/
// @supportURL   https://github.com/exyezed/youtube-enhancer/issues
// @license      MIT
// @match        https://www.youtube.com/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const buttonConfig = {
    screenshotFormat: "png",
    extension: "png",
    clickDuration: 500,
  };

  const buttonCSS = `
    a.buttonLoopAndScreenshot-loop-button, 
    a.buttonLoopAndScreenshot-save-screenshot-button,
    a.buttonLoopAndScreenshot-copy-screenshot-button {
        text-align: center;
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 48px;
        height: 48px;
    }

    a.buttonLoopAndScreenshot-loop-button svg, 
    a.buttonLoopAndScreenshot-save-screenshot-button svg,
    a.buttonLoopAndScreenshot-copy-screenshot-button svg {
        width: 24px;
        height: 24px;
        vertical-align: middle;
        transition: fill 0.2s ease;
    }

    a.buttonLoopAndScreenshot-loop-button:hover svg,
    a.buttonLoopAndScreenshot-save-screenshot-button:hover svg,
    a.buttonLoopAndScreenshot-copy-screenshot-button:hover svg {
        fill: url(#buttonGradient);
    }

    a.buttonLoopAndScreenshot-loop-button.active svg,
    a.buttonLoopAndScreenshot-save-screenshot-button.clicked svg,
    a.buttonLoopAndScreenshot-copy-screenshot-button.clicked svg {
        fill: url(#successGradient);
    }    
        
    .buttonLoopAndScreenshot-shorts-save-button,
    .buttonLoopAndScreenshot-shorts-copy-button {
        display: flex;
        align-items: center;
        justify-content: center;
        margin-top: 16px;
        margin-bottom: 16px;
        width: 48px;
        height: 48px;
        border-radius: 50%;
        cursor: pointer;
        transition: background-color 0.3s;
    }

    .buttonLoopAndScreenshot-shorts-save-button svg,
    .buttonLoopAndScreenshot-shorts-copy-button svg {
        width: 24px;
        height: 24px;
        transition: fill 0.1s ease;
    }

    .buttonLoopAndScreenshot-shorts-save-button svg path,
    .buttonLoopAndScreenshot-shorts-copy-button svg path {
        transition: fill 0.1s ease;
    }

    .buttonLoopAndScreenshot-shorts-save-button:hover svg path,
    .buttonLoopAndScreenshot-shorts-copy-button:hover svg path {
        fill: url(#shortsButtonGradient) !important;
    }

    .buttonLoopAndScreenshot-shorts-save-button.clicked svg path,
    .buttonLoopAndScreenshot-shorts-copy-button.clicked svg path {
        fill: url(#shortsSuccessGradient) !important;
    }

    html[dark] .buttonLoopAndScreenshot-shorts-save-button,
    html[dark] .buttonLoopAndScreenshot-shorts-copy-button {
        background-color: rgba(255, 255, 255, 0.1);
    }

    html[dark] .buttonLoopAndScreenshot-shorts-save-button:hover,
    html[dark] .buttonLoopAndScreenshot-shorts-copy-button:hover {
        background-color: rgba(255, 255, 255, 0.2);
    }

    html[dark] .buttonLoopAndScreenshot-shorts-save-button svg path,
    html[dark] .buttonLoopAndScreenshot-shorts-copy-button svg path {
        fill: white;
    }

    html:not([dark]) .buttonLoopAndScreenshot-shorts-save-button,
    html:not([dark]) .buttonLoopAndScreenshot-shorts-copy-button {
        background-color: rgba(0, 0, 0, 0.05);
    }

    html:not([dark]) .buttonLoopAndScreenshot-shorts-save-button:hover,
    html:not([dark]) .buttonLoopAndScreenshot-shorts-copy-button:hover {
        background-color: rgba(0, 0, 0, 0.1);
    }

    html:not([dark]) .buttonLoopAndScreenshot-shorts-save-button svg path,
    html:not([dark]) .buttonLoopAndScreenshot-shorts-copy-button svg path {
        fill: #030303;
    }
    `;

  const iconUtils = {
    createGradientDefs(isShortsButton = false) {
      const defs = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "defs"
      );

      const hoverGradient = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "linearGradient"
      );
      hoverGradient.setAttribute(
        "id",
        isShortsButton ? "shortsButtonGradient" : "buttonGradient"
      );
      hoverGradient.setAttribute("x1", "0%");
      hoverGradient.setAttribute("y1", "0%");
      hoverGradient.setAttribute("x2", "100%");
      hoverGradient.setAttribute("y2", "100%");

      const hoverStop1 = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "stop"
      );
      hoverStop1.setAttribute("offset", "0%");
      hoverStop1.setAttribute("style", "stop-color:#f03");

      const hoverStop2 = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "stop"
      );
      hoverStop2.setAttribute("offset", "100%");
      hoverStop2.setAttribute("style", "stop-color:#ff2791");

      hoverGradient.appendChild(hoverStop1);
      hoverGradient.appendChild(hoverStop2);
      defs.appendChild(hoverGradient);

      const successGradient = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "linearGradient"
      );
      successGradient.setAttribute(
        "id",
        isShortsButton ? "shortsSuccessGradient" : "successGradient"
      );
      successGradient.setAttribute("x1", "0%");
      successGradient.setAttribute("y1", "0%");
      successGradient.setAttribute("x2", "100%");
      successGradient.setAttribute("y2", "100%");

      const successStop1 = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "stop"
      );
      successStop1.setAttribute("offset", "0%");
      successStop1.setAttribute("style", "stop-color:#0f9d58");

      const successStop2 = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "stop"
      );
      successStop2.setAttribute("offset", "100%");
      successStop2.setAttribute("style", "stop-color:#00c853");

      successGradient.appendChild(successStop1);
      successGradient.appendChild(successStop2);
      defs.appendChild(successGradient);

      return defs;
    },

    createBaseSVG(viewBox, fill = "#e8eaed", isShortsButton = false) {
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
      svg.setAttribute("height", "24px");
      svg.setAttribute("viewBox", viewBox);
      svg.setAttribute("width", "24px");
      svg.setAttribute("fill", fill);
      svg.appendChild(this.createGradientDefs(isShortsButton));
      return svg;
    },

    paths: {
      loopPath:
        "M220-260q-92 0-156-64T0-480q0-92 64-156t156-64q37 0 71 13t61 37l68 62-60 54-62-56q-16-14-36-22t-42-8q-58 0-99 41t-41 99q0 58 41 99t99 41q22 0 42-8t36-22l310-280q27-24 61-37t71-13q92 0 156 64t64 156q0 92-64 156t-156 64q-37 0-71-13t-61-37l-68-62 60-54 62 56q16 14 36 22t42 8q58 0 99-41t41-99q0-58-41-99t-99-41q-22 0-42 8t-36 22L352-310q-27 24-61 37t-71 13Z",
      screenshotPath:
        "M20 5h-3.17l-1.24-1.35A2 2 0 0 0 14.12 3H9.88c-.56 0-1.1.24-1.47.65L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-3 12H7a.5.5 0 0 1-.4-.8l2-2.67c.2-.27.6-.27.8 0L11.25 16l2.6-3.47c.2-.27.6-.27.8 0l2.75 3.67a.5.5 0 0 1-.4.8",
      copyScreenshotPath:
        "M9 14h10l-3.45-4.5l-2.3 3l-1.55-2zm-1 4q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm0-2h12V4H8zm-4 6q-.825 0-1.412-.587T2 20V6h2v14h14v2zM8 4h12v12H8z",
    },

    createLoopIcon() {
      const svg = this.createBaseSVG("0 -960 960 960");

      const path = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
      );
      path.setAttribute("d", this.paths.loopPath);

      svg.appendChild(path);
      return svg;
    },

    createSaveScreenshotIcon(isShortsButton = false) {
      const svg = this.createBaseSVG("0 0 24 24", "#e8eaed", isShortsButton);

      const path = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
      );
      path.setAttribute("d", this.paths.screenshotPath);

      svg.appendChild(path);
      return svg;
    },

    createCopyScreenshotIcon(isShortsButton = false) {
      const svg = this.createBaseSVG("0 0 24 24", "#e8eaed", isShortsButton);

      const path = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
      );
      path.setAttribute("d", this.paths.copyScreenshotPath);

      svg.appendChild(path);
      return svg;
    },
  };

  const buttonUtils = {
    addStyle(styleString) {
      const style = document.createElement("style");
      style.textContent = styleString;
      document.head.append(style);
    },

    getVideoId() {
      const urlParams = new URLSearchParams(window.location.search);
      return urlParams.get("v") || window.location.pathname.split("/").pop();
    },

    getApiKey() {
      const scripts = document.getElementsByTagName("script");
      for (const script of scripts) {
        const match = script.textContent.match(
          /"INNERTUBE_API_KEY":\s*"([^"]+)"/
        );
        if (match && match[1]) return match[1];
      }
      return null;
    },

    getClientInfo() {
      const scripts = document.getElementsByTagName("script");
      let clientName = null;
      let clientVersion = null;

      for (const script of scripts) {
        const nameMatch = script.textContent.match(
          /"INNERTUBE_CLIENT_NAME":\s*"([^"]+)"/
        );
        const versionMatch = script.textContent.match(
          /"INNERTUBE_CLIENT_VERSION":\s*"([^"]+)"/
        );

        if (nameMatch && nameMatch[1]) clientName = nameMatch[1];
        if (versionMatch && versionMatch[1]) clientVersion = versionMatch[1];
      }

      return { clientName, clientVersion };
    },

    async fetchVideoDetails(videoId) {
      try {
        const apiKey = this.getApiKey();
        if (!apiKey) return null;

        const { clientName, clientVersion } = this.getClientInfo();
        if (!clientName || !clientVersion) return null;

        const response = await fetch(
          `https://www.youtube.com/youtubei/v1/player?key=${apiKey}`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              videoId: videoId,
              context: {
                client: {
                  clientName: clientName,
                  clientVersion: clientVersion,
                },
              },
            }),
          }
        );

        if (!response.ok) return null;
        const data = await response.json();
        if (data && data.videoDetails && data.videoDetails.title) {
          return data.videoDetails.title;
        }
        return "YouTube Video";
      } catch (error) {
        return "YouTube Video";
      }
    },

    async getVideoTitle(callback) {
      const videoId = this.getVideoId();
      const title = await this.fetchVideoDetails(videoId);
      callback(title || "YouTube Video");
    },

    formatTime(time) {
      const date = new Date();
      const dateString = `${date.getFullYear()}-${String(
        date.getMonth() + 1
      ).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
      const timeString = [
        Math.floor(time / 3600),
        Math.floor((time % 3600) / 60),
        Math.floor(time % 60),
      ]
        .map((v) => v.toString().padStart(2, "0"))
        .join("-");
      return `${dateString} ${timeString}`;
    },

    async copyToClipboard(blob) {
      const clipboardItem = new ClipboardItem({ "image/png": blob });
      await navigator.clipboard.write([clipboardItem]);
    },

    downloadScreenshot(blob, filename) {
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.style.display = "none";
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    },

    captureScreenshot(player, action = "download") {
      if (!player) return;

      const canvas = document.createElement("canvas");
      canvas.width = player.videoWidth;
      canvas.height = player.videoHeight;
      canvas
        .getContext("2d")
        .drawImage(player, 0, 0, canvas.width, canvas.height);

      this.getVideoTitle((title) => {
        const time = player.currentTime;
        const filename = `${title} ${this.formatTime(time)}.${
          buttonConfig.extension
        }`;

        canvas.toBlob(async (blob) => {
          if (action === "copy") {
            await this.copyToClipboard(blob);
          } else {
            this.downloadScreenshot(blob, filename);
          }
        }, `image/${buttonConfig.screenshotFormat}`);
      });
    },
  };

  const regularVideo = {
    init() {
      this.waitForControls().then(() => {
        this.insertLoopElement();
        this.insertSaveScreenshotElement();
        this.insertCopyScreenshotElement();
        this.addObserver();
        this.addContextMenuListener();
      });
    },

    waitForControls() {
      return new Promise((resolve, reject) => {
        let attempts = 0;
        const maxAttempts = 50;

        const checkControls = () => {
          const controls = document.querySelector("div.ytp-left-controls");
          if (controls) {
            resolve(controls);
          } else if (attempts >= maxAttempts) {
            reject(new Error("Controls not found after maximum attempts"));
          } else {
            attempts++;
            setTimeout(checkControls, 100);
          }
        };

        checkControls();
      });
    },

    insertLoopElement() {
      const controls = document.querySelector("div.ytp-left-controls");
      if (!controls) return;

      if (document.querySelector(".buttonLoopAndScreenshot-loop-button"))
        return;

      const newButton = document.createElement("a");
      newButton.classList.add(
        "ytp-button",
        "buttonLoopAndScreenshot-loop-button"
      );
      newButton.title = "Loop Video";
      newButton.appendChild(iconUtils.createLoopIcon());
      newButton.addEventListener("click", this.toggleLoopState);

      controls.appendChild(newButton);
    },

    insertSaveScreenshotElement() {
      const controls = document.querySelector("div.ytp-left-controls");
      if (!controls) return;

      if (
        document.querySelector(
          ".buttonLoopAndScreenshot-save-screenshot-button"
        )
      )
        return;

      const newButton = document.createElement("a");
      newButton.classList.add(
        "ytp-button",
        "buttonLoopAndScreenshot-save-screenshot-button"
      );
      newButton.title = "Save Screenshot";
      newButton.appendChild(iconUtils.createSaveScreenshotIcon());
      newButton.addEventListener("click", this.handleSaveScreenshotClick);

      const loopButton = document.querySelector(
        ".buttonLoopAndScreenshot-loop-button"
      );
      if (loopButton) {
        loopButton.parentNode.insertBefore(newButton, loopButton.nextSibling);
      } else {
        controls.appendChild(newButton);
      }
    },

    insertCopyScreenshotElement() {
      const controls = document.querySelector("div.ytp-left-controls");
      if (!controls) return;

      if (
        document.querySelector(
          ".buttonLoopAndScreenshot-copy-screenshot-button"
        )
      )
        return;

      const newButton = document.createElement("a");
      newButton.classList.add(
        "ytp-button",
        "buttonLoopAndScreenshot-copy-screenshot-button"
      );
      newButton.title = "Copy Screenshot to Clipboard";
      newButton.appendChild(iconUtils.createCopyScreenshotIcon());
      newButton.addEventListener("click", this.handleCopyScreenshotClick);

      const saveButton = document.querySelector(
        ".buttonLoopAndScreenshot-save-screenshot-button"
      );
      if (saveButton) {
        saveButton.parentNode.insertBefore(newButton, saveButton.nextSibling);
      } else {
        controls.appendChild(newButton);
      }
    },

    toggleLoopState() {
      const video = document.querySelector("video");
      video.loop = !video.loop;
      if (video.loop) video.play();

      regularVideo.updateToggleControls();
    },

    updateToggleControls() {
      const youtubeVideoLoop = document.querySelector(
        ".buttonLoopAndScreenshot-loop-button"
      );
      youtubeVideoLoop.classList.toggle("active");
      youtubeVideoLoop.setAttribute(
        "title",
        this.isActive() ? "Stop Looping" : "Loop Video"
      );
    },

    isActive() {
      const youtubeVideoLoop = document.querySelector(
        ".buttonLoopAndScreenshot-loop-button"
      );
      return youtubeVideoLoop.classList.contains("active");
    },

    addObserver() {
      const video = document.querySelector("video");
      new MutationObserver((mutations) => {
        mutations.forEach(() => {
          if (
            (video.getAttribute("loop") === null && this.isActive()) ||
            (video.getAttribute("loop") !== null && !this.isActive())
          )
            this.updateToggleControls();
        });
      }).observe(video, { attributes: true, attributeFilter: ["loop"] });
    },

    addContextMenuListener() {
      const video = document.querySelector("video");
      video.addEventListener("contextmenu", () => {
        setTimeout(() => {
          const checkbox = document.querySelector("[role=menuitemcheckbox]");
          checkbox.setAttribute("aria-checked", this.isActive());
          checkbox.addEventListener("click", this.toggleLoopState);
        }, 50);
      });
    },

    handleSaveScreenshotClick(event) {
      const button = event.currentTarget;
      button.classList.add("clicked");
      setTimeout(() => {
        button.classList.remove("clicked");
      }, buttonConfig.clickDuration);

      const player = document.querySelector("video");
      buttonUtils.captureScreenshot(player, "download");
    },

    handleCopyScreenshotClick(event) {
      const button = event.currentTarget;
      button.classList.add("clicked");
      setTimeout(() => {
        button.classList.remove("clicked");
      }, buttonConfig.clickDuration);

      const player = document.querySelector("video");
      buttonUtils.captureScreenshot(player, "copy");
    },
  };

  const shortsVideo = {
    init() {
      this.insertSaveScreenshotElement();
      this.insertCopyScreenshotElement();
    },

    insertSaveScreenshotElement() {
      const likeButtonContainer = document.querySelector(
        "ytd-reel-video-renderer[is-active] #like-button"
      );
      if (
        likeButtonContainer &&
        !document.querySelector(".buttonLoopAndScreenshot-shorts-save-button")
      ) {
        const iconDiv = document.createElement("div");
        iconDiv.className = "buttonLoopAndScreenshot-shorts-save-button";
        iconDiv.title = "Save Screenshot";
        iconDiv.appendChild(iconUtils.createSaveScreenshotIcon(true));

        likeButtonContainer.parentNode.insertBefore(
          iconDiv,
          likeButtonContainer
        );

        iconDiv.addEventListener("click", (event) => {
          const button = event.currentTarget;
          button.classList.add("clicked");

          setTimeout(() => {
            button.classList.remove("clicked");
          }, buttonConfig.clickDuration);

          this.captureScreenshot("download");
        });
      }
    },

    insertCopyScreenshotElement() {
      const likeButtonContainer = document.querySelector(
        "ytd-reel-video-renderer[is-active] #like-button"
      );
      const saveButton = document.querySelector(
        ".buttonLoopAndScreenshot-shorts-save-button"
      );

      if (
        likeButtonContainer &&
        !document.querySelector(".buttonLoopAndScreenshot-shorts-copy-button")
      ) {
        const iconDiv = document.createElement("div");
        iconDiv.className = "buttonLoopAndScreenshot-shorts-copy-button";
        iconDiv.title = "Copy Screenshot to Clipboard";
        iconDiv.appendChild(iconUtils.createCopyScreenshotIcon(true));

        if (saveButton) {
          saveButton.parentNode.insertBefore(iconDiv, saveButton.nextSibling);
        } else {
          likeButtonContainer.parentNode.insertBefore(
            iconDiv,
            likeButtonContainer
          );
        }

        iconDiv.addEventListener("click", (event) => {
          const button = event.currentTarget;
          button.classList.add("clicked");

          setTimeout(() => {
            button.classList.remove("clicked");
          }, buttonConfig.clickDuration);

          this.captureScreenshot("copy");
        });
      }
    },

    captureScreenshot(action) {
      const player = document.querySelector(
        "ytd-reel-video-renderer[is-active] video"
      );
      buttonUtils.captureScreenshot(player, action);
    },
  };

  const themeHandler = {
    init() {
      this.updateStyles();
      this.addObserver();
    },

    updateStyles() {
      const isDarkTheme = document.documentElement.hasAttribute("dark");
      document.documentElement.classList.toggle("dark-theme", isDarkTheme);
    },

    addObserver() {
      const observer = new MutationObserver(() => this.updateStyles());
      observer.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ["dark"],
      });
    },
  };

  function initialize() {
    buttonUtils.addStyle(buttonCSS);
    waitForVideo().then(initializeWhenReady);
  }

  function waitForVideo() {
    return new Promise((resolve) => {
      const checkVideo = () => {
        if (document.querySelector("video")) {
          resolve();
        } else {
          setTimeout(checkVideo, 100);
        }
      };
      checkVideo();
    });
  }

  function initializeWhenReady() {
    initializeFeatures();
  }

  function initializeFeatures() {
    regularVideo.init();
    themeHandler.init();
    initializeShortsFeatures();
  }

  function initializeShortsFeatures() {
    if (window.location.pathname.includes("/shorts/")) {
      setTimeout(shortsVideo.init.bind(shortsVideo), 500);
    }
  }

  const keyboardShortcuts = {
    init() {
      document.addEventListener("keydown", this.handleKeyDown.bind(this));
    },

    handleKeyDown(event) {
      if (!event.altKey) return;

      if (event.key === "s" || event.key === "c" || event.key === "l") {
        event.preventDefault();
        event.stopPropagation();
      }

      switch (event.key.toLowerCase()) {
        case "s":
          this.triggerSaveScreenshot();
          break;
        case "c":
          this.triggerCopyScreenshot();
          break;
        case "l":
          this.triggerLoopToggle();
          break;
      }
    },

    triggerSaveScreenshot() {
      if (window.location.pathname.includes("/shorts/")) {
        const player = document.querySelector(
          "ytd-reel-video-renderer[is-active] video"
        );
        if (player) {
          buttonUtils.captureScreenshot(player, "download");
          this.showShortcutFeedback("save");
        }
      } else {
        const player = document.querySelector("video");
        if (player) {
          buttonUtils.captureScreenshot(player, "download");
          this.showShortcutFeedback("save");
        }
      }
    },

    triggerCopyScreenshot() {
      if (window.location.pathname.includes("/shorts/")) {
        const player = document.querySelector(
          "ytd-reel-video-renderer[is-active] video"
        );
        if (player) {
          buttonUtils.captureScreenshot(player, "copy");
          this.showShortcutFeedback("copy");
        }
      } else {
        const player = document.querySelector("video");
        if (player) {
          buttonUtils.captureScreenshot(player, "copy");
          this.showShortcutFeedback("copy");
        }
      }
    },

    triggerLoopToggle() {
      if (!window.location.pathname.includes("/shorts/")) {
        const video = document.querySelector("video");
        if (video) {
          video.loop = !video.loop;
          if (video.loop) video.play();
          regularVideo.updateToggleControls();
          this.showShortcutFeedback("loop");
        }
      }
    },

    showShortcutFeedback(action) {
      let button;
      if (window.location.pathname.includes("/shorts/")) {
        if (action === "save") {
          button = document.querySelector(
            ".buttonLoopAndScreenshot-shorts-save-button"
          );
        } else if (action === "copy") {
          button = document.querySelector(
            ".buttonLoopAndScreenshot-shorts-copy-button"
          );
        }
      } else {
        if (action === "save") {
          button = document.querySelector(
            ".buttonLoopAndScreenshot-save-screenshot-button"
          );
        } else if (action === "copy") {
          button = document.querySelector(
            ".buttonLoopAndScreenshot-copy-screenshot-button"
          );
        } else if (action === "loop") {
          button = document.querySelector(
            ".buttonLoopAndScreenshot-loop-button"
          );
        }
      }

      if (button) {
        button.classList.add("clicked");
        setTimeout(() => {
          button.classList.remove("clicked");
        }, buttonConfig.clickDuration);
      }
    },
  };

  const shortsObserver = new MutationObserver((mutations) => {
    for (let mutation of mutations) {
      if (mutation.type === "childList") {
        initializeShortsFeatures();
      }
    }
  });

  shortsObserver.observe(document.body, { childList: true, subtree: true });

  window.addEventListener("yt-navigate-finish", initializeShortsFeatures);

  document.addEventListener("yt-action", function (event) {
    if (
      event.detail &&
      event.detail.actionName === "yt-reload-continuation-items-command"
    ) {
      initializeShortsFeatures();
    }
  });

  keyboardShortcuts.init();

  initialize();
})();