Extension for Better LingQ Reader Layout (For Video/Audio Lessons)

// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @version      2.4
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  // Function to load a value from GM storage
  function getStoredValue(key, defaultValue) {
    const value = GM_getValue(key);
    return value === undefined ? defaultValue : value;
  }

  // Function to save a value to GM storage
  function setStoredValue(key, value) {
    GM_setValue(key, value);
  }

  // Create the style type selector (Combobox)
  const styleTypeSelector = document.createElement("select"); // Moved here
  styleTypeSelector.id = "styleTypeSelector";

  // Add options to the selector
  const options = ["video", "video(wide)", "audio", "off"];
  options.forEach((option) => {
    const opt = document.createElement("option");
    opt.value = option;
    opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
    styleTypeSelector.appendChild(opt);
  });

  // Load the previous selected setting from GM storage
  const storedStyleType = getStoredValue("styleType", "video");
  styleTypeSelector.value = storedStyleType;

  // Find the #main-nav element
  // More robust selector, checks nav first
  let mainNav = document.querySelector(
    "#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
  );
  if (!mainNav) {
    // Fallback to simpler selector if the first one fails
    mainNav = document.querySelector("#main-nav");
  }

  if (mainNav) {
    // Insert the combobox inside #main-nav
    mainNav.appendChild(styleTypeSelector);

    // Style the combobox's text color using CSS
    styleTypeSelector.style.color = "#ffffff"; // Example: Set text color to white
    styleTypeSelector.style.padding = "5px";
    styleTypeSelector.style.position = "relative"; //Remove fixed positioning
  } else {
    console.error("#main-nav element not found.  ComboBox not inserted.");
  }

  // No longer appending to body

  let styleElement = null;

  // Base CSS (always applied)
  const baseCSS = `
    :root {
      --width_small: 440px;
      --height_small: 260px;
      --right_pos: 0.5%;
      --bottom_pos: 5.5%;

      --width_big: calc(100vw - 424px - 5px);

      --font_size: 1.1rem;
      --font_color: #e0e0e0;
      --line_height: 1.7;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;

      --grid-layout: 1fr var(--height_big) 80px;
      --video_margin: 0 0 90px 10px !important;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }

    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big);
      margin: var(--video_margin);
    }

    .video-player.is-minimized .video-wrapper,
    .sent-video-player.is-minimized .video-wrapper {
      height: var(--height_small);
      width: var(--width_small);
    }

    .video-player.is-minimized .modal-content,
    .sent-video-player.is-minimized .modal-content {
      max-width: var(--width_small);
      margin-bottom: 0;
    }

    .video-player.is-minimized,
    .sent-video-player.is-minimized {
      left: auto;
      top: auto;
      right: var(--right_pos);
      bottom: var(--bottom_pos);
      z-index: 99999999;
      overflow: visible
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }

    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }

    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
  `;

  function applyStyles(styleType) {
    let css = baseCSS; // Start with the base CSS
    let specificCSS = "";

    switch (styleType) {
      case "audio":
          specificCSS = `
            :root {
                --height_big: 10px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 2 / 1 !important;
            }
        `;
        break;

      case "video":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            :root {
                --height_big: 400px;
            }

            .widget-area {
                grid-area: 2 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 1 / -1 !important;
            }
        `;
        break;

      case "video(wide)":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                grid-template-columns: 1fr 424px 1fr;
            }

            .main-content {
                grid-area: 1 / 1 / -1 / 1 !important;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-footer {
                grid-area: 2 / 1 / 2 / 1 !important;
            }

            :root {
                --width_big: calc(50vw - 217px);
                --height_big: calc(100vh - 80px);
                --grid-layout: 1fr 80px;
                --video_margin: 0 10px 20px 10px !important;
                --article_height: calc(var(--app-height) - 265px);
            }

            .modal-container .modls {
                align-items: end;
            }
        `;
        break;

      case "off":
        css = ""; // No styles
        break;
    }

    // Append the style-specific CSS
    css += specificCSS;

    // Remove the old style tag.
    if (styleElement) {
      styleElement.remove();
      styleElement = null;
    }

    // Create & append the new style tag
    if (css) {
      styleElement = document.createElement("style");
      styleElement.textContent = css;
      document.querySelector("head").appendChild(styleElement);
    }
  }

  // Apply styles on initial load
  applyStyles(storedStyleType);

  // Apply styles when the combobox changes
  styleTypeSelector.addEventListener("change", function () {
    const selectedStyleType = styleTypeSelector.value;
    applyStyles(selectedStyleType);
    setStoredValue("styleType", selectedStyleType); // Save to GM storage
  });

  // Add new shortcuts
  document.addEventListener("keydown", function (event) {
    const targetElement = event.target;
    const isTextInput =
      targetElement.type === "text" || targetElement.type === "textarea";
    if (isTextInput) {
      return;
    }

    // video full screen toggle
    if (event.key === "q" || event.key === "Q") {
      const full_screen_btn = document.querySelector(
        ".modal-section > div > button:nth-child(2)"
      );
      if (full_screen_btn) {
        full_screen_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Backward (Same as the 'Ctrl + ,')
    if (event.key === "w") {
      const backward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(1) > a"
      );
      if (backward_btn) {
        backward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Forward (Same as the 'Ctrl + .')
    if (event.key === "e") {
      const forkward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(2) > a"
      );
      if (forkward_btn) {
        forkward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Make the selected word Known (Same as the 'k')
    if (event.key === "5") {
      document.dispatchEvent(
        new KeyboardEvent("keydown", {
          key: "k",
        })
      );
      event.preventDefault();
      event.stopPropagation();
    }

    // Move cursor to the reference input
    if (event.key === "`") {
      const reference_input = document.querySelector(".reference-input-text");

      if (reference_input) {
        reference_input.focus();
        reference_input.setSelectionRange(
          reference_input.value.length,
          reference_input.value.length
        );
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Dictionary
    if (event.key === "d" || event.key === "f") {
      const dict_btn = document.querySelector(
        ".dictionary-resources > a:nth-child(1)"
      );
      if (dict_btn) {
        dict_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Translator
    if (event.key === "t") {
      const translator_btn = document.querySelector(
        ".dictionary-resources > a:nth-last-child(1)"
      );
      if (translator_btn) {
        translator_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Copy selected text
    if (event.key === "c") {
      const selected_text = document.querySelector(".reference-word");
      if (selected_text) {
        navigator.clipboard.writeText(selected_text.textContent);
      }
      event.preventDefault();
      event.stopPropagation();
    }
  });

  // Custom embedded player
  function replaceNoCookie() {
    document.querySelectorAll("iframe").forEach(function (iframe) {
      let src = iframe.getAttribute("src");

      if (src && src.includes("disablekb=1")) {
        // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
        src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
        src = src + "&cc_load_policy=1"; // caption is shown by default.
        src = src + "&controls=0"; // player controls do not display in the player.
        iframe.setAttribute("src", src);
        console.log(src);
      }
    });
  }

  const iframeObserver = new MutationObserver(function (mutationsList, observer) {
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === "IFRAME") {
            replaceNoCookie();
          } else if (node.querySelectorAll) {
            node.querySelectorAll("iframe").forEach(replaceNoCookie);
          }
        });
      } else if (
        mutation.type === "attributes" &&
        mutation.attributeName === "src" &&
        mutation.target.nodeName === "IFRAME"
      ) {
        replaceNoCookie();
      }
    }
  });

  if (document.URL.includes("/reader/")) {
    iframeObserver.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["src"],
    });
  }

  // Change the amount of a scroll
  setTimeout(() => {
    console.log("scroll event!");
    const myDiv = document.querySelector(".reader-container");
    myDiv.addEventListener("wheel", (event) => {
      event.preventDefault();
      const delta = event.deltaY;
      const scrollAmount = 0.3;
      myDiv.scrollTop += delta * scrollAmount;
    });
  }, 3000);

  // Focus on playing sentence
  function focusPlayingSentence() {
    const playingSentence = document.querySelector(".sentence.is-playing");

    if (playingSentence) {
      playingSentence.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }
  }

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (
        mutation.type === "attributes" &&
        mutation.attributeName === "class" &&
        mutation.target.classList.contains("sentence")
      ) {
        focusPlayingSentence();
      }
    });
  });

  const container = document.querySelector(".sentence-text");
  if (container) {
    console.log("observer setted!");
    observer.observe(container, {
      attributes: true,
      subtree: true,
    });
  }

  // Sort courses
  function addClickListener() {
    document.querySelectorAll("div.library-item-wrap").forEach((item) => {
      if (!item.dataset.listenerAdded) {
        item.addEventListener("click", function () {
          setTimeout(() => {
            /* Change the number in .dropdown-item:nth-child(3) by your preference
                         1: All lessons
                         2: Oldest to Newest
                         3: Newest to Oldest
                         4: Last Opened
                         5: New Words %
                         6: A-Z */
            document
              .querySelector(
                ".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
              )
              .click();
          }, 1000);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

  const libraryObserver = new MutationObserver(addClickListener);
  if (document.URL.includes("/library")) {
    libraryObserver.observe(document.body, {
      childList: true,
      subtree: true,
    });
    addClickListener();
  }

  if (document.URL.includes("/library/course/")) {
    setTimeout(() => {
      document
        .querySelector(
          ".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
        )
        .click();
    }, 2000);
  }
})();

Amazing work, congratulations!

Would you adapt your last script to screens 4k and white themes? I had tried with chatGPT but the result is so bad :sweat_smile:

Thank you!!

Change the style-related variables here.
--font_color: #e0e0e0; is the font-color.

    :root {
      --width_small: 440px;
      --height_small: 260px;
      --right_pos: 0.5%;
      --bottom_pos: 5.5%;

      --width_big: calc(100vw - 424px - 5px);

      --font_size: 1.1rem;
      --font_color: #e0e0e0;
      --line_height: 1.7;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;

      --grid-layout: 1fr var(--height_big) 80px;
      --video_margin: 0 0 90px 10px !important;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

The video size is dependent on this code.

:root {
                --height_big: 400px;
            }

Perfect!

I would like to move it to the right sidebar so that it takes up the entire side. Is it possible? now I can’t set the score of know word because it is not viewable.

TIP: You can also change the status of the word with hot-keys ‘1, 2, 3, 4, k’

Patch note:

  • Fixed some hotkeys-related glitches.
// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @version      2.6
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  // Function to load a value from GM storage
  function getStoredValue(key, defaultValue) {
    const value = GM_getValue(key);
    return value === undefined ? defaultValue : value;
  }

  // Function to save a value to GM storage
  function setStoredValue(key, value) {
    GM_setValue(key, value);
  }

  // Create the style type selector (Combobox)
  const styleTypeSelector = document.createElement("select"); // Moved here
  styleTypeSelector.id = "styleTypeSelector";

  // Add options to the selector
  const options = ["video", "video(wide)", "audio", "off"];
  options.forEach((option) => {
    const opt = document.createElement("option");
    opt.value = option;
    opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
    styleTypeSelector.appendChild(opt);
  });

  // Load the previous selected setting from GM storage
  const storedStyleType = getStoredValue("styleType", "video");
  styleTypeSelector.value = storedStyleType;

  // Find the #main-nav element
  // More robust selector, checks nav first
  let mainNav = document.querySelector(
    "#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
  );
  if (!mainNav) {
    // Fallback to simpler selector if the first one fails
    mainNav = document.querySelector("#main-nav");
  }

  if (mainNav) {
    // Insert the combobox inside #main-nav
    mainNav.appendChild(styleTypeSelector);

    // Style the combobox's text color using CSS
    styleTypeSelector.style.color = "#ffffff"; // Example: Set text color to white
    styleTypeSelector.style.padding = "5px";
    styleTypeSelector.style.position = "relative"; //Remove fixed positioning
  } else {
    console.error("#main-nav element not found.  ComboBox not inserted.");
  }

  // No longer appending to body

  let styleElement = null;

  // Base CSS (always applied)
  const baseCSS = `
    :root {
      --width_small: 440px;
      --height_small: 260px;
      --right_pos: 0.5%;
      --bottom_pos: 5.5%;

      --width_big: calc(100vw - 424px - 5px);

      --font_size: 1.1rem;
      --font_color: #e0e0e0;
      --line_height: 1.7;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;

      --grid-layout: 1fr var(--height_big) 80px;
      --video_margin: 0 0 90px 10px !important;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }

    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big);
      margin: var(--video_margin);
    }

    .video-player.is-minimized .video-wrapper,
    .sent-video-player.is-minimized .video-wrapper {
      height: var(--height_small);
      width: var(--width_small);
    }

    .video-player.is-minimized .modal-content,
    .sent-video-player.is-minimized .modal-content {
      max-width: var(--width_small);
      margin-bottom: 0;
    }

    .video-player.is-minimized,
    .sent-video-player.is-minimized {
      left: auto;
      top: auto;
      right: var(--right_pos);
      bottom: var(--bottom_pos);
      z-index: 99999999;
      overflow: visible
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }

    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }

    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
      background-color: var(--lingq_background) !important;
    }

    .phrase-item.phrase-item-status--4,
    .phrase-item.phrase-item-status--4x2 {
      background-color: rgba(0, 0, 0, 0) !important;
    }

    .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
      border: 1px solid var(--lingq_border) !important;
      border-radius: .25rem;
    }

    .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
      border: 1px solid var(--lingq_border_learned) !important;
      border-radius: .25rem;
    }

    .reader-container .sentence .lingq-word:not(.is-learned) {
      border: 1px solid var(--lingq_border) !important;
      background-color: var(--lingq_background) !important;
    }

    .reader-container .sentence .lingq-word.is-learned {
      border: 1px solid var(--lingq_border_learned) !important;
    }

    .reader-container .sentence .blue-word {
      border: 1px solid var(--blue_border) !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
  `;

  function applyStyles(styleType) {
    let css = baseCSS; // Start with the base CSS
    let specificCSS = "";

    switch (styleType) {
      case "audio":
          specificCSS = `
            :root {
                --height_big: 10px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 2 / 1 !important;
            }
        `;
        break;

      case "video":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            :root {
                --height_big: 400px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 1 / 1 !important;
            }
        `;
        break;

      case "video(wide)":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                grid-template-columns: 1fr 424px 1fr;
            }

            .main-content {
                grid-area: 1 / 1 / -1 / 1 !important;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-footer {
                grid-area: 2 / 1 / 2 / 1 !important;
            }

            :root {
                --width_big: calc(50vw - 217px);
                --height_big: calc(100vh - 80px);
                --grid-layout: 1fr 80px;
                --video_margin: 0 10px 20px 10px !important;
                --article_height: calc(var(--app-height) - 265px);
            }

            .modal-container .modls {
                align-items: end;
            }
        `;
        break;

      case "off":
        css = ""; // No styles
        break;
    }

    // Append the style-specific CSS
    css += specificCSS;

    // Remove the old style tag.
    if (styleElement) {
      styleElement.remove();
      styleElement = null;
    }

    // Create & append the new style tag
    if (css) {
      styleElement = document.createElement("style");
      styleElement.textContent = css;
      document.querySelector("head").appendChild(styleElement);
    }
  }

  // Apply styles on initial load
  applyStyles(storedStyleType);

  // Apply styles when the combobox changes
  styleTypeSelector.addEventListener("change", function () {
    const selectedStyleType = styleTypeSelector.value;
    applyStyles(selectedStyleType);
    setStoredValue("styleType", selectedStyleType); // Save to GM storage
  });

  // Add new shortcuts
  document.addEventListener("keydown", function (event) {
    const targetElement = event.target;
    const isTextInput =
      targetElement.type === "text" || targetElement.type === "textarea";
    if (isTextInput) {
      return;
    }

    // video full screen toggle
    if (event.key === "q" || event.key === "Q") {
      const full_screen_btn = document.querySelector(
        ".modal-section > div > button:nth-child(2)"
      );
      if (full_screen_btn) {
        full_screen_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Backward (Same as the 'Ctrl + ,')
    if (event.key === "w") {
      const backward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(1) > a"
      );
      if (backward_btn) {
        backward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Forward (Same as the 'Ctrl + .')
    if (event.key === "e") {
      const forkward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(2) > a"
      );
      if (forkward_btn) {
        forkward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Make the selected word Known (Same as the 'k')
    if (event.key === "r") {
      document.dispatchEvent(
        new KeyboardEvent("keydown", {
          key: "k",
        })
      );
      event.preventDefault();
      event.stopPropagation();
    }

    // Move cursor to the reference input
    if (event.key === "`") {
      const reference_input = document.querySelector(".reference-input-text");

      if (reference_input) {
        reference_input.focus();
        reference_input.setSelectionRange(
          reference_input.value.length,
          reference_input.value.length
        );
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Dictionary
    if ((event.key === "d" || event.key === "f") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
      const dict_btn = document.querySelector(
        ".dictionary-resources > a:nth-child(1)"
      );
      if (dict_btn) {
        dict_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Translator
    if (event.key === "t") {
      const translator_btn = document.querySelector(
        ".dictionary-resources > a:nth-last-child(1)"
      );
      if (translator_btn) {
        translator_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Copy selected text
    if (event.key === "c") {
      const selected_text = document.querySelector(".reference-word");
      if (selected_text) {
        navigator.clipboard.writeText(selected_text.textContent);
      }
      event.preventDefault();
      event.stopPropagation();
    }
  }, true);

  // Custom embedded player
  function replaceNoCookie() {
    document.querySelectorAll("iframe").forEach(function (iframe) {
      let src = iframe.getAttribute("src");

      if (src && src.includes("disablekb=1")) {
        // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
        src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
        src = src + "&cc_load_policy=1"; // caption is shown by default.
        src = src + "&controls=0"; // player controls do not display in the player.
        iframe.setAttribute("src", src);
        console.log(src);
      }
    });
  }

  const iframeObserver = new MutationObserver(function (mutationsList, observer) {
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === "IFRAME") {
            replaceNoCookie();
          } else if (node.querySelectorAll) {
            node.querySelectorAll("iframe").forEach(replaceNoCookie);
          }
        });
      } else if (
        mutation.type === "attributes" &&
        mutation.attributeName === "src" &&
        mutation.target.nodeName === "IFRAME"
      ) {
        replaceNoCookie();
      }
    }
  });

  if (document.URL.includes("/reader/")) {
    iframeObserver.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["src"],
    });
  }

  // Change the amount of a scroll
  setTimeout(() => {
    console.log("scroll event!");
    const myDiv = document.querySelector(".reader-container");
    myDiv.addEventListener("wheel", (event) => {
      event.preventDefault();
      const delta = event.deltaY;
      const scrollAmount = 0.3;
      myDiv.scrollTop += delta * scrollAmount;
    });
  }, 3000);

  // Focus on playing sentence
  function focusPlayingSentence() {
    const playingSentence = document.querySelector(".sentence.is-playing");

    if (playingSentence) {
      playingSentence.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }
  }

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (
        mutation.type === "attributes" &&
        mutation.attributeName === "class" &&
        mutation.target.classList.contains("sentence")
      ) {
        focusPlayingSentence();
      }
    });
  });

  const container = document.querySelector(".sentence-text");
  if (container) {
    console.log("observer setted!");
    observer.observe(container, {
      attributes: true,
      subtree: true,
    });
  }

  // Sort courses
  function addClickListener() {
    document.querySelectorAll("div.library-item-wrap").forEach((item) => {
      if (!item.dataset.listenerAdded) {
        item.addEventListener("click", function () {
          setTimeout(() => {
            /* Change the number in .dropdown-item:nth-child(3) by your preference
                         1: All lessons
                         2: Oldest to Newest
                         3: Newest to Oldest
                         4: Last Opened
                         5: New Words %
                         6: A-Z */
            document
              .querySelector(
                ".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
              )
              .click();
          }, 1000);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

  const libraryObserver = new MutationObserver(addClickListener);
  if (document.URL.includes("/library")) {
    libraryObserver.observe(document.body, {
      childList: true,
      subtree: true,
    });
    addClickListener();
  }

  if (document.URL.includes("/library/course/")) {
    setTimeout(() => {
      document
        .querySelector(
          ".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
        )
        .click();
    }, 2000);
  }
})();

it is almost perfect. I just need to know how change colors new brown LingQ to yellow original range. And the other side, now when I play audio or video I have lost the sync audio display style underline

is it possible repair those?

PLZ read this post

I named the variables to make them easy to understand. Change them.

Sorry, I saw the post but just wanted to know the original RGB colors, HSV in this case.

The main trouble is that It lost sync between audio and text and now the underlined no appear

For white background

// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @version      2.6
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  // Function to load a value from GM storage
  function getStoredValue(key, defaultValue) {
    const value = GM_getValue(key);
    return value === undefined ? defaultValue : value;
  }

  // Function to save a value to GM storage
  function setStoredValue(key, value) {
    GM_setValue(key, value);
  }

  // Create the style type selector (Combobox)
  const styleTypeSelector = document.createElement("select"); // Moved here
  styleTypeSelector.id = "styleTypeSelector";

  // Add options to the selector
  const options = ["video", "video(wide)", "audio", "off"];
  options.forEach((option) => {
    const opt = document.createElement("option");
    opt.value = option;
    opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
    styleTypeSelector.appendChild(opt);
  });

  // Load the previous selected setting from GM storage
  const storedStyleType = getStoredValue("styleType", "video");
  styleTypeSelector.value = storedStyleType;

  // Find the #main-nav element
  // More robust selector, checks nav first
  let mainNav = document.querySelector(
    "#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
  );
  if (!mainNav) {
    // Fallback to simpler selector if the first one fails
    mainNav = document.querySelector("#main-nav");
  }

  if (mainNav) {
    // Insert the combobox inside #main-nav
    mainNav.appendChild(styleTypeSelector);

    // Style the combobox's text color using CSS
    styleTypeSelector.style.color = "#000000"; // Example: Set text color to white
    styleTypeSelector.style.padding = "5px";
    styleTypeSelector.style.position = "relative"; //Remove fixed positioning
  } else {
    console.error("#main-nav element not found.  ComboBox not inserted.");
  }

  // No longer appending to body

  let styleElement = null;

  // Base CSS (always applied)
  const baseCSS = `
    :root {
      --width_small: 440px;
      --height_small: 260px;
      --right_pos: 0.5%;
      --bottom_pos: 5.5%;

      --width_big: calc(100vw - 424px - 5px);

      --font_size: 1.1rem;
      --line_height: 1.7;

      --grid-layout: 1fr var(--height_big) 80px;
      --video_margin: 0 0 90px 10px !important;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }

    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big);
      margin: var(--video_margin);
    }

    .video-player.is-minimized .video-wrapper,
    .sent-video-player.is-minimized .video-wrapper {
      height: var(--height_small);
      width: var(--width_small);
    }

    .video-player.is-minimized .modal-content,
    .sent-video-player.is-minimized .modal-content {
      max-width: var(--width_small);
      margin-bottom: 0;
    }

    .video-player.is-minimized,
    .sent-video-player.is-minimized {
      left: auto;
      top: auto;
      right: var(--right_pos);
      bottom: var(--bottom_pos);
      z-index: 99999999;
      overflow: visible
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }

    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
    }

    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
  `;

  function applyStyles(styleType) {
    let css = baseCSS; // Start with the base CSS
    let specificCSS = "";

    switch (styleType) {
      case "audio":
          specificCSS = `
            :root {
                --height_big: 10px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 2 / 1 !important;
            }
        `;
        break;

      case "video":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            :root {
                --height_big: 400px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 1 / 1 !important;
            }
        `;
        break;

      case "video(wide)":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                grid-template-columns: 1fr 424px 1fr;
            }

            .main-content {
                grid-area: 1 / 1 / -1 / 1 !important;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-footer {
                grid-area: 2 / 1 / 2 / 1 !important;
            }

            :root {
                --width_big: calc(50vw - 217px);
                --height_big: calc(100vh - 80px);
                --grid-layout: 1fr 80px;
                --video_margin: 0 10px 20px 10px !important;
                --article_height: calc(var(--app-height) - 265px);
            }

            .modal-container .modls {
                align-items: end;
            }
        `;
        break;

      case "off":
        css = ""; // No styles
        break;
    }

    // Append the style-specific CSS
    css += specificCSS;

    // Remove the old style tag.
    if (styleElement) {
      styleElement.remove();
      styleElement = null;
    }

    // Create & append the new style tag
    if (css) {
      styleElement = document.createElement("style");
      styleElement.textContent = css;
      document.querySelector("head").appendChild(styleElement);
    }
  }

  // Apply styles on initial load
  applyStyles(storedStyleType);

  // Apply styles when the combobox changes
  styleTypeSelector.addEventListener("change", function () {
    const selectedStyleType = styleTypeSelector.value;
    applyStyles(selectedStyleType);
    setStoredValue("styleType", selectedStyleType); // Save to GM storage
  });

  // Add new shortcuts
  document.addEventListener("keydown", function (event) {
    const targetElement = event.target;
    const isTextInput =
      targetElement.type === "text" || targetElement.type === "textarea";
    if (isTextInput) {
      return;
    }

    // video full screen toggle
    if (event.key === "q" || event.key === "Q") {
      const full_screen_btn = document.querySelector(
        ".modal-section > div > button:nth-child(2)"
      );
      if (full_screen_btn) {
        full_screen_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Backward (Same as the 'Ctrl + ,')
    if (event.key === "w") {
      const backward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(1) > a"
      );
      if (backward_btn) {
        backward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Forward (Same as the 'Ctrl + .')
    if (event.key === "e") {
      const forkward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(2) > a"
      );
      if (forkward_btn) {
        forkward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Make the selected word Known (Same as the 'k')
    if (event.key === "r") {
      document.dispatchEvent(
        new KeyboardEvent("keydown", {
          key: "k",
        })
      );
      event.preventDefault();
      event.stopPropagation();
    }

    // Move cursor to the reference input
    if (event.key === "`") {
      const reference_input = document.querySelector(".reference-input-text");

      if (reference_input) {
        reference_input.focus();
        reference_input.setSelectionRange(
          reference_input.value.length,
          reference_input.value.length
        );
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Dictionary
    if ((event.key === "d" || event.key === "f") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
      const dict_btn = document.querySelector(
        ".dictionary-resources > a:nth-child(1)"
      );
      if (dict_btn) {
        dict_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Translator
    if (event.key === "t") {
      const translator_btn = document.querySelector(
        ".dictionary-resources > a:nth-last-child(1)"
      );
      if (translator_btn) {
        translator_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Copy selected text
    if (event.key === "c") {
      const selected_text = document.querySelector(".reference-word");
      if (selected_text) {
        navigator.clipboard.writeText(selected_text.textContent);
      }
      event.preventDefault();
      event.stopPropagation();
    }
  }, true);

  // Custom embedded player
  function replaceNoCookie() {
    document.querySelectorAll("iframe").forEach(function (iframe) {
      let src = iframe.getAttribute("src");

      if (src && src.includes("disablekb=1")) {
        // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
        src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
        src = src + "&cc_load_policy=1"; // caption is shown by default.
        src = src + "&controls=0"; // player controls do not display in the player.
        iframe.setAttribute("src", src);
        console.log(src);
      }
    });
  }

  const iframeObserver = new MutationObserver(function (mutationsList, observer) {
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === "IFRAME") {
            replaceNoCookie();
          } else if (node.querySelectorAll) {
            node.querySelectorAll("iframe").forEach(replaceNoCookie);
          }
        });
      } else if (
        mutation.type === "attributes" &&
        mutation.attributeName === "src" &&
        mutation.target.nodeName === "IFRAME"
      ) {
        replaceNoCookie();
      }
    }
  });

  if (document.URL.includes("/reader/")) {
    iframeObserver.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["src"],
    });
  }

  // Change the amount of a scroll
  setTimeout(() => {
    console.log("scroll event!");
    const myDiv = document.querySelector(".reader-container");
    myDiv.addEventListener("wheel", (event) => {
      event.preventDefault();
      const delta = event.deltaY;
      const scrollAmount = 0.3;
      myDiv.scrollTop += delta * scrollAmount;
    });
  }, 3000);

  // Focus on playing sentence
  function focusPlayingSentence() {
    const playingSentence = document.querySelector(".sentence.is-playing");

    if (playingSentence) {
      playingSentence.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }
  }

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (
        mutation.type === "attributes" &&
        mutation.attributeName === "class" &&
        mutation.target.classList.contains("sentence")
      ) {
        focusPlayingSentence();
      }
    });
  });

  const container = document.querySelector(".sentence-text");
  if (container) {
    console.log("observer setted!");
    observer.observe(container, {
      attributes: true,
      subtree: true,
    });
  }

  // Sort courses
  function addClickListener() {
    document.querySelectorAll("div.library-item-wrap").forEach((item) => {
      if (!item.dataset.listenerAdded) {
        item.addEventListener("click", function () {
          setTimeout(() => {
            /* Change the number in .dropdown-item:nth-child(3) by your preference
                         1: All lessons
                         2: Oldest to Newest
                         3: Newest to Oldest
                         4: Last Opened
                         5: New Words %
                         6: A-Z */
            document
              .querySelector(
                ".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
              )
              .click();
          }, 1000);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

  const libraryObserver = new MutationObserver(addClickListener);
  if (document.URL.includes("/library")) {
    libraryObserver.observe(document.body, {
      childList: true,
      subtree: true,
    });
    addClickListener();
  }

  if (document.URL.includes("/library/course/")) {
    setTimeout(() => {
      document
        .querySelector(
          ".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
        )
        .click();
    }, 2000);
  }
})();
``
2 Likes

Patchnote

  • A toggle for dark/white backgound theme was added.
    (But you still need to change the LingQ theme setting manually.

image

// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @version      2.7
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  // Function to load a value from GM storage
  function getStoredValue(key, defaultValue) {
    const value = GM_getValue(key);
    return value === undefined ? defaultValue : value;
  }

  // Function to save a value to GM storage
  function setStoredValue(key, value) {
    GM_setValue(key, value);
  }

  // Create the style type selector (Combobox)
  const styleTypeSelector = document.createElement("select"); // Moved here
  styleTypeSelector.id = "styleTypeSelector";

  // Add options to the selector
  const options = ["video", "video2", "audio", "off"];
  options.forEach((option) => {
    const opt = document.createElement("option");
    opt.value = option;
    opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
    styleTypeSelector.appendChild(opt);
  });

  // Load the previous selected setting from GM storage
  const storedStyleType = getStoredValue("styleType", "video");
  styleTypeSelector.value = storedStyleType;

  // Create the color mode selector (Toggle)
  const colorModeSelector = document.createElement("select");
  colorModeSelector.id = "colorModeSelector";

  // Add options to the color mode selector
  const colorOptions = ["dark", "white"];
  colorOptions.forEach((option) => {
    const opt = document.createElement("option");
    opt.value = option;
    opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
    colorModeSelector.appendChild(opt);
  });

  // Load the previous selected color mode from GM storage
  const storedColorMode = getStoredValue("colorMode", "dark");
  colorModeSelector.value = storedColorMode;

  // Find the #main-nav element
  // More robust selector, checks nav first
  let mainNav = document.querySelector(
    "#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
  );
  if (!mainNav) {
    // Fallback to simpler selector if the first one fails
    mainNav = document.querySelector("#main-nav");
  }

  if (mainNav) {
    // Insert the combobox inside #main-nav
    mainNav.appendChild(styleTypeSelector);
    // Insert the color mode selector inside #main-nav
    mainNav.appendChild(colorModeSelector);
  } else {
    console.error("#main-nav element not found. ComboBox not inserted.");
  }

  // No longer appending to body

  let styleElement = null;

  // Base CSS (always applied)
  const baseCSS = `
    :root {
      --width_small: 440px;
      --height_small: 260px;
      --right_pos: 0.5%;
      --bottom_pos: 5.5%;

      --width_big: calc(100vw - 424px - 5px);

      --font_size: 1.1rem;
      --line_height: 1.7;

      --grid-layout: 1fr var(--height_big) 80px;
      --video_margin: 0 0 90px 10px !important;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    #styleTypeSelector, #colorModeSelector {
      color: var(--font_color);
      padding: 5px;
      position: relative;
      font-size: 0.9rem;
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }

    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big);
      margin: var(--video_margin);
    }

    .video-player.is-minimized .video-wrapper,
    .sent-video-player.is-minimized .video-wrapper {
      height: var(--height_small);
      width: var(--width_small);
    }

    .video-player.is-minimized .modal-content,
    .sent-video-player.is-minimized .modal-content {
      max-width: var(--width_small);
      margin-bottom: 0;
    }

    .video-player.is-minimized,
    .sent-video-player.is-minimized {
      left: auto;
      top: auto;
      right: var(--right_pos);
      bottom: var(--bottom_pos);
      z-index: 99999999;
      overflow: visible
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }

    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }

    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
      background-color: var(--lingq_background) !important;
    }

    .phrase-item.phrase-item-status--4,
    .phrase-item.phrase-item-status--4x2 {
      background-color: rgba(0, 0, 0, 0) !important;
    }

    .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
      border: 1px solid var(--lingq_border) !important;
      border-radius: .25rem;
    }

    .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
      border: 1px solid var(--lingq_border_learned) !important;
      border-radius: .25rem;
    }

    .reader-container .sentence .lingq-word:not(.is-learned) {
      border: 1px solid var(--lingq_border) !important;
      background-color: var(--lingq_background) !important;
    }

    .reader-container .sentence .lingq-word.is-learned {
      border: 1px solid var(--lingq_border_learned) !important;
    }

    .reader-container .sentence .blue-word {
      border: 1px solid var(--blue_border) !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
  `;

  // Dark mode CSS
  const darkModeCSS = `
    :root {
      --font_color: #e0e0e0;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;
    }
  `;

  // White mode CSS
  const whiteModeCSS = `
    :root {
      --font_color: #000000;

      --lingq_background: hsl(47, 100%, 50%, 0.4);
      --lingq_border: hsl(47, 100%, 50%, 0.3);
      --lingq_border_learned: hsl(47, 100%, 50%, 1);
      --blue_border: hsl(214, 100%, 50%, 0.3);

      --is_playing_underline: #000000;
    }
  `;

  function applyStyles(styleType, colorMode) {
    let css = baseCSS; // Start with the base CSS
    let specificCSS = "";
    let colorCSS = "";

    // Apply color mode CSS
    switch (colorMode) {
      case "dark":
        colorCSS = darkModeCSS;
        break;
      case "white":
        colorCSS = whiteModeCSS;
        break;
    }

    // Apply style type CSS
    switch (styleType) {
      case "audio":
          specificCSS = `
            :root {
                --height_big: 10px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 2 / 1 !important;
            }
        `;
        break;

      case "video":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            :root {
                --height_big: 400px;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 1 / 1 !important;
            }
        `;
        break;

      case "video(wide)":
        // Add video-specific styles here if you have any, otherwise leave empty.
        specificCSS = `
            .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                grid-template-columns: 1fr 424px 1fr;
            }

            .main-content {
                grid-area: 1 / 1 / -1 / 1 !important;
            }

            .widget-area {
                grid-area: 1 / 2 / -1 / 2 !important;
            }

            .main-footer {
                grid-area: 2 / 1 / 2 / 1 !important;
            }

            :root {
                --width_big: calc(50vw - 217px);
                --height_big: calc(100vh - 80px);
                --grid-layout: 1fr 80px;
                --video_margin: 0 10px 20px 10px !important;
                --article_height: calc(var(--app-height) - 265px);
            }

            .modal-container .modls {
                align-items: end;
            }
        `;
        break;

      case "off":
        css = ""; // No styles
        break;
    }

    // Append the style-specific CSS and color mode CSS
    css += colorCSS + specificCSS;

    // Remove the old style tag.
    if (styleElement) {
      styleElement.remove();
      styleElement = null;
    }

    // Create & append the new style tag
    if (css) {
      styleElement = document.createElement("style");
      styleElement.textContent = css;
      document.querySelector("head").appendChild(styleElement);
    }
  }

  // Apply styles on initial load
  applyStyles(storedStyleType, storedColorMode);

  // Apply styles when the style type combobox changes
  styleTypeSelector.addEventListener("change", function () {
    const selectedStyleType = styleTypeSelector.value;
    const selectedColorMode = colorModeSelector.value;
    applyStyles(selectedStyleType, selectedColorMode);
    setStoredValue("styleType", selectedStyleType); // Save to GM storage
  });

  // Apply styles when the color mode combobox changes
  colorModeSelector.addEventListener("change", function () {
    const selectedStyleType = styleTypeSelector.value;
    const selectedColorMode = colorModeSelector.value;
    applyStyles(selectedStyleType, selectedColorMode);
    setStoredValue("colorMode", selectedColorMode); // Save to GM storage
  });

  // Add new shortcuts
  document.addEventListener("keydown", function (event) {
    const targetElement = event.target;
    const isTextInput =
      targetElement.type === "text" || targetElement.type === "textarea";
    if (isTextInput) {
      return;
    }

    // video full screen toggle
    if (event.key === "q" || event.key === "Q") {
      const full_screen_btn = document.querySelector(
        ".modal-section > div > button:nth-child(2)"
      );
      if (full_screen_btn) {
        full_screen_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Backward (Same as the 'Ctrl + ,')
    if (event.key === "w") {
      const backward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(1) > a"
      );
      if (backward_btn) {
        backward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // 5 sec Forward (Same as the 'Ctrl + .')
    if (event.key === "e") {
      const forkward_btn = document.querySelector(
        ".audio-player--controllers > div:nth-child(2) > a"
      );
      if (forkward_btn) {
        forkward_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Make the selected word Known (Same as the 'k')
    if (event.key === "r") {
      document.dispatchEvent(
        new KeyboardEvent("keydown", {
          key: "k",
        })
      );
      event.preventDefault();
      event.stopPropagation();
    }

    // Move cursor to the reference input
    if (event.key === "`") {
      const reference_input = document.querySelector(".reference-input-text");

      if (reference_input) {
        reference_input.focus();
        reference_input.setSelectionRange(
          reference_input.value.length,
          reference_input.value.length
        );
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Dictionary
    if ((event.key === "d" || event.key === "f") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
      const dict_btn = document.querySelector(
        ".dictionary-resources > a:nth-child(1)"
      );
      if (dict_btn) {
        dict_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Open Translator
    if (event.key === "t") {
      const translator_btn = document.querySelector(
        ".dictionary-resources > a:nth-last-child(1)"
      );
      if (translator_btn) {
        translator_btn.click();
      }
      event.preventDefault();
      event.stopPropagation();
    }

    // Copy selected text
    if (event.key === "c") {
      const selected_text = document.querySelector(".reference-word");
      if (selected_text) {
        navigator.clipboard.writeText(selected_text.textContent);
      }
      event.preventDefault();
      event.stopPropagation();
    }
  }, true);

  // Custom embedded player
  function replaceNoCookie() {
    document.querySelectorAll("iframe").forEach(function (iframe) {
      let src = iframe.getAttribute("src");

      if (src && src.includes("disablekb=1")) {
        // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
        src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
        src = src + "&cc_load_policy=1"; // caption is shown by default.
        src = src + "&controls=0"; // player controls do not display in the player.
        iframe.setAttribute("src", src);
        console.log(src);
      }
    });
  }

  const iframeObserver = new MutationObserver(function (mutationsList, observer) {
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === "IFRAME") {
            replaceNoCookie();
          } else if (node.querySelectorAll) {
            node.querySelectorAll("iframe").forEach(replaceNoCookie);
          }
        });
      } else if (
        mutation.type === "attributes" &&
        mutation.attributeName === "src" &&
        mutation.target.nodeName === "IFRAME"
      ) {
        replaceNoCookie();
      }
    }
  });

  if (document.URL.includes("/reader/")) {
    iframeObserver.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["src"],
    });
  }

  // Change the amount of a scroll
  setTimeout(() => {
    console.log("scroll event!");
    const myDiv = document.querySelector(".reader-container");
    myDiv.addEventListener("wheel", (event) => {
      event.preventDefault();
      const delta = event.deltaY;
      const scrollAmount = 0.3;
      myDiv.scrollTop += delta * scrollAmount;
    });
  }, 3000);

  // Focus on playing sentence
  function focusPlayingSentence() {
    const playingSentence = document.querySelector(".sentence.is-playing");

    if (playingSentence) {
      playingSentence.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }
  }

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (
        mutation.type === "attributes" &&
        mutation.attributeName === "class" &&
        mutation.target.classList.contains("sentence")
      ) {
        focusPlayingSentence();
      }
    });
  });

  const container = document.querySelector(".sentence-text");
  if (container) {
    console.log("observer setted!");
    observer.observe(container, {
      attributes: true,
      subtree: true,
    });
  }

  // Sort courses
  function addClickListener() {
    document.querySelectorAll("div.library-item-wrap").forEach((item) => {
      if (!item.dataset.listenerAdded) {
        item.addEventListener("click", function () {
          setTimeout(() => {
            /* Change the number in .dropdown-item:nth-child(3) by your preference
                         1: All lessons
                         2: Oldest to Newest
                         3: Newest to Oldest
                         4: Last Opened
                         5: New Words %
                         6: A-Z */
            document
              .querySelector(
                ".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
              )
              .click();
          }, 1000);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

  const libraryObserver = new MutationObserver(addClickListener);
  if (document.URL.includes("/library")) {
    libraryObserver.observe(document.body, {
      childList: true,
      subtree: true,
    });
    addClickListener();
  }

  if (document.URL.includes("/library/course/")) {
    setTimeout(() => {
      document
        .querySelector(
          ".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
        )
        .click();
    }, 2000);
  }
})();
  • Refactored some of the code
  • You can resize and move the video player in the “off” setting.
  • Removed the “course sorting”
// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @version      2.8
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
    "use strict";

    // Function to load a value from GM storage
    function getStoredValue(key, defaultValue) {
        const value = GM_getValue(key);
        return value === undefined ? defaultValue : value;
    }

    // Function to save a value to GM storage
    function setStoredValue(key, value) {
        GM_setValue(key, value);
    }

    // Create the style type selector (Combobox)
    const styleTypeSelector = document.createElement("select"); // Moved here
    styleTypeSelector.id = "styleTypeSelector";

    // Add options to the selector
    const options = ["video", "video2", "audio", "off"];
    options.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        styleTypeSelector.appendChild(opt);
    });

    // Load the previous selected setting from GM storage
    const storedStyleType = getStoredValue("styleType", "video");
    styleTypeSelector.value = storedStyleType;

    // Create the color mode selector (Toggle)
    const colorModeSelector = document.createElement("select");
    colorModeSelector.id = "colorModeSelector";

    // Add options to the color mode selector
    const colorOptions = ["dark", "white"];
    colorOptions.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        colorModeSelector.appendChild(opt);
    });

    // Load the previous selected color mode from GM storage
    const storedColorMode = getStoredValue("colorMode", "dark");
    colorModeSelector.value = storedColorMode;

    // Find the #main-nav element
    // More robust selector, checks nav first
    let mainNav = document.querySelector(
        "#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
    );
    if (!mainNav) {
        // Fallback to simpler selector if the first one fails
        mainNav = document.querySelector("#main-nav");
    }

    if (mainNav) {
        // Insert the combobox inside #main-nav
        mainNav.appendChild(styleTypeSelector);
        // Insert the color mode selector inside #main-nav
        mainNav.appendChild(colorModeSelector);
    } else {
        console.error("#main-nav element not found. ComboBox not inserted.");
    }

    // No longer appending to body

    let styleElement = null;

    // Base CSS (always applied)
    const baseCSS = `
    :root {
      --font_size: 1.1rem;
      --line_height: 1.7;

      --grid-layout: 1fr var(--height_big) 80px;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    #styleTypeSelector, #colorModeSelector {
      color: var(--font_color);
      padding: 5px;
      position: relative;
      font-size: 0.9rem;
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }


    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big) !important;
      margin: var(--video_margin);
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }


    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }


    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
      background-color: var(--lingq_background) !important;
    }

    .phrase-item.phrase-item-status--4,
    .phrase-item.phrase-item-status--4x2 {
      background-color: rgba(0, 0, 0, 0) !important;
    }

    .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
      border: 1px solid var(--lingq_border) !important;
      border-radius: .25rem;
    }

    .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
      border: 1px solid var(--lingq_border_learned) !important;
      border-radius: .25rem;
    }

    .reader-container .sentence .lingq-word:not(.is-learned) {
      border: 1px solid var(--lingq_border) !important;
      background-color: var(--lingq_background) !important;
    }

    .reader-container .sentence .lingq-word.is-learned {
      border: 1px solid var(--lingq_border_learned) !important;
    }

    .reader-container .sentence .blue-word {
      border: 1px solid var(--blue_border) !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
    `;

    // Dark mode CSS
    const darkModeCSS = `
    :root {
      --font_color: #e0e0e0;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;
    }
  `;

    // White mode CSS
    const whiteModeCSS = `
    :root {
      --font_color: #000000;

      --lingq_background: hsl(47, 100%, 50%, 0.4);
      --lingq_border: hsl(47, 100%, 50%, 0.3);
      --lingq_border_learned: hsl(47, 100%, 50%, 1);
      --blue_border: hsl(214, 100%, 50%, 0.3);

      --is_playing_underline: #000000;
    }
  `;

    function applyStyles(styleType, colorMode) {
        let css = baseCSS; // Start with the base CSS
        let specificCSS = "";
        let colorCSS = "";

        // Apply color mode CSS
        switch (colorMode) {
            case "dark":
                colorCSS = darkModeCSS;
                break;
            case "white":
                colorCSS = whiteModeCSS;
                break;
        }

        // Apply style type CSS
        switch (styleType) {
            case "video":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(100vw - 424px - 5px);
                    --height_big: 400px;

                    --video_margin: 0 0 90px 10px !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 1 / 1 !important;
                }
                `;
                break;

            case "video2":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(50vw - 217px);
                    --height_big: calc(100vh - 80px);

                    --grid-layout: 1fr 80px;
                    --video_margin: 0 10px 20px 10px !important;
                    --article_height: calc(var(--app-height) - 265px);
                }

                .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                    grid-template-columns: 1fr 424px 1fr;
                }

                .main-content {
                    grid-area: 1 / 1 / -1 / 1 !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-footer {
                    grid-area: 2 / 1 / 2 / 1 !important;
                }

                .modal-container .modls {
                    align-items: end;
                }
                `;
                break;

            case "audio":
                specificCSS = `
                :root {
                    --height_big: 10px;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 2 / 1 !important;
                }
                `;
                break;

            case "off":
                css = `
                :root {
                    --width_small: 440px;
                    --height_small: 260px;
                    --right_pos: 0.5%;
                    --bottom_pos: 5.5%;
                }

                .video-player.is-minimized .video-wrapper,
                .sent-video-player.is-minimized .video-wrapper {
                    height: var(--height_small);
                    width: var(--width_small);
                    overflow: auto;
                    resize: both;
                }

                .video-player.is-minimized .modal-content,
                .sent-video-player.is-minimized .modal-content {
                    max-width: calc(var(--width_small)* 3);
                    margin-bottom: 0;
                }

                .video-player.is-minimized,
                .sent-video-player.is-minimized {
                    left: auto;
                    top: auto;
                    right: var(--right_pos);
                    bottom: var(--bottom_pos);
                    z-index: 99999999;
                    overflow: visible
                }
                `;
                break;
        }

        // Append the style-specific CSS and color mode CSS
        css += colorCSS + specificCSS;

        // Remove the old style tag.
        if (styleElement) {
            styleElement.remove();
            styleElement = null;
        }

        // Create & append the new style tag
        if (css) {
            styleElement = document.createElement("style");
            styleElement.textContent = css;
            document.querySelector("head").appendChild(styleElement);
        }
    }

    // Apply styles on initial load
    applyStyles(storedStyleType, storedColorMode);

    // Apply styles when the style type combobox changes
    styleTypeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("styleType", selectedStyleType); // Save to GM storage
    });

    // Apply styles when the color mode combobox changes
    colorModeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("colorMode", selectedColorMode); // Save to GM storage
    });

    // Add new shortcuts
    document.addEventListener(
        "keydown",
        function (event) {
            const targetElement = event.target;
            const isTextInput =
                targetElement.type === "text" ||
                targetElement.type === "textarea";
            if (isTextInput) {
                return;
            }

            // video full screen toggle
            if (event.key === "q" || event.key === "Q") {
                const full_screen_btn = document.querySelector(
                    ".modal-section > div > button:nth-child(2)",
                );
                if (full_screen_btn) {
                    full_screen_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Backward (Same as the 'Ctrl + ,')
            if (event.key === "w") {
                const backward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(1) > a",
                );
                if (backward_btn) {
                    backward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Forward (Same as the 'Ctrl + .')
            if (event.key === "e") {
                const forkward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(2) > a",
                );
                if (forkward_btn) {
                    forkward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Make the selected word Known (Same as the 'k')
            if (event.key === "r") {
                document.dispatchEvent(
                    new KeyboardEvent("keydown", {
                        key: "k",
                    }),
                );
                event.preventDefault();
                event.stopPropagation();
            }

            // Move cursor to the reference input
            if (event.key === "`") {
                const reference_input = document.querySelector(
                    ".reference-input-text",
                );

                if (reference_input) {
                    reference_input.focus();
                    reference_input.setSelectionRange(
                        reference_input.value.length,
                        reference_input.value.length,
                    );
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Dictionary
            if (
                (event.key === "d" || event.key === "f") &&
                !event.ctrlKey &&
                !event.shiftKey &&
                !event.altKey
            ) {
                const dict_btn = document.querySelector(
                    ".dictionary-resources > a:nth-child(1)",
                );
                if (dict_btn) {
                    dict_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Translator
            if (event.key === "t") {
                const translator_btn = document.querySelector(
                    ".dictionary-resources > a:nth-last-child(1)",
                );
                if (translator_btn) {
                    translator_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Copy selected text
            if (event.key === "c") {
                const selected_text = document.querySelector(".reference-word");
                if (selected_text) {
                    navigator.clipboard.writeText(selected_text.textContent);
                }
                event.preventDefault();
                event.stopPropagation();
            }
        },
        true,
    );

    // Custom embedded player
    function replaceNoCookie() {
        document.querySelectorAll("iframe").forEach(function (iframe) {
            let src = iframe.getAttribute("src");

            if (src && src.includes("disablekb=1")) {
                // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
                src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
                src = src + "&cc_load_policy=1"; // caption is shown by default.
                src = src + "&controls=0"; // player controls do not display in the player.
                iframe.setAttribute("src", src);
                console.log(src);
            }
        });
    }

    const iframeObserver = new MutationObserver(function (
        mutationsList,
        observer,
    ) {
        for (const mutation of mutationsList) {
            if (
                mutation.type === "childList" &&
                mutation.addedNodes.length > 0
            ) {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeName === "IFRAME") {
                        replaceNoCookie();
                    } else if (node.querySelectorAll) {
                        node.querySelectorAll("iframe").forEach(
                            replaceNoCookie,
                        );
                    }
                });
            } else if (
                mutation.type === "attributes" &&
                mutation.attributeName === "src" &&
                mutation.target.nodeName === "IFRAME"
            ) {
                replaceNoCookie();
            }
        }
    });

    iframeObserver.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["src"],
    });

    // Change the amount of a scroll
    setTimeout(() => {
        console.log("scroll event!");
        const myDiv = document.querySelector(".reader-container");
        myDiv.addEventListener("wheel", (event) => {
            event.preventDefault();
            const delta = event.deltaY;
            const scrollAmount = 0.3;
            myDiv.scrollTop += delta * scrollAmount;
        });
    }, 3000);

    // Focus on playing sentence
    function focusPlayingSentence() {
        const playingSentence = document.querySelector(".sentence.is-playing");

        if (playingSentence) {
            playingSentence.scrollIntoView({
                behavior: "smooth",
                block: "center",
            });
        }
    }

    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (
                mutation.type === "attributes" &&
                mutation.attributeName === "class" &&
                mutation.target.classList.contains("sentence")
            ) {
                focusPlayingSentence();
            }
        });
    });

    const container = document.querySelector(".sentence-text");
    if (container) {
        console.log("observer setted!");
        observer.observe(container, {
            attributes: true,
            subtree: true,
        });
    }
})();
  • Now, the video player is automatically expanded.
    (You can still expand/shrink with the q key.)
// ==UserScript==
// @name         LingQ Addon
// @description  Provides custom LingQ layouts
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @version      2.9
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
    "use strict";

    // Function to load a value from GM storage
    function getStoredValue(key, defaultValue) {
        const value = GM_getValue(key);
        return value === undefined ? defaultValue : value;
    }

    // Function to save a value to GM storage
    function setStoredValue(key, value) {
        GM_setValue(key, value);
    }

    // Create the style type selector (Combobox)
    const styleTypeSelector = document.createElement("select"); // Moved here
    styleTypeSelector.id = "styleTypeSelector";

    // Add options to the selector
    const options = ["video", "video2", "audio", "off"];
    options.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        styleTypeSelector.appendChild(opt);
    });

    // Load the previous selected setting from GM storage
    const storedStyleType = getStoredValue("styleType", "video");
    styleTypeSelector.value = storedStyleType;

    // Create the color mode selector (Toggle)
    const colorModeSelector = document.createElement("select");
    colorModeSelector.id = "colorModeSelector";

    // Add options to the color mode selector
    const colorOptions = ["dark", "white"];
    colorOptions.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        colorModeSelector.appendChild(opt);
    });

    // Load the previous selected color mode from GM storage
    const storedColorMode = getStoredValue("colorMode", "dark");
    colorModeSelector.value = storedColorMode;

    // Find the #main-nav element
    // More robust selector, checks nav first
    let mainNav = document.querySelector(
        "#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
    );
    if (!mainNav) {
        // Fallback to simpler selector if the first one fails
        mainNav = document.querySelector("#main-nav");
    }

    if (mainNav) {
        // Insert the combobox inside #main-nav
        mainNav.appendChild(styleTypeSelector);
        // Insert the color mode selector inside #main-nav
        mainNav.appendChild(colorModeSelector);
    } else {
        console.error("#main-nav element not found. ComboBox not inserted.");
    }

    // No longer appending to body

    let styleElement = null;

    // Base CSS (always applied)
    const baseCSS = `
    :root {
      --font_size: 1.1rem;
      --line_height: 1.7;

      --grid-layout: 1fr var(--height_big) 80px;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    #styleTypeSelector, #colorModeSelector {
      color: var(--font_color);
      padding: 5px;
      position: relative;
      font-size: 0.9rem;
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }


    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big) !important;
      margin: var(--video_margin);
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }


    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }


    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
      background-color: var(--lingq_background) !important;
    }

    .phrase-item.phrase-item-status--4,
    .phrase-item.phrase-item-status--4x2 {
      background-color: rgba(0, 0, 0, 0) !important;
    }

    .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
      border: 1px solid var(--lingq_border) !important;
      border-radius: .25rem;
    }

    .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
      border: 1px solid var(--lingq_border_learned) !important;
      border-radius: .25rem;
    }

    .reader-container .sentence .lingq-word:not(.is-learned) {
      border: 1px solid var(--lingq_border) !important;
      background-color: var(--lingq_background) !important;
    }

    .reader-container .sentence .lingq-word.is-learned {
      border: 1px solid var(--lingq_border_learned) !important;
    }

    .reader-container .sentence .blue-word {
      border: 1px solid var(--blue_border) !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
    `;

    // Dark mode CSS
    const darkModeCSS = `
    :root {
      --font_color: #e0e0e0;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;
    }
  `;

    // White mode CSS
    const whiteModeCSS = `
    :root {
      --font_color: #000000;

      --lingq_background: hsl(47, 100%, 50%, 0.4);
      --lingq_border: hsl(47, 100%, 50%, 0.3);
      --lingq_border_learned: hsl(47, 100%, 50%, 1);
      --blue_border: hsl(214, 100%, 50%, 0.3);

      --is_playing_underline: #000000;
    }
  `;

    function applyStyles(styleType, colorMode) {
        let css = baseCSS; // Start with the base CSS
        let specificCSS = "";
        let colorCSS = "";

        // Apply color mode CSS
        switch (colorMode) {
            case "dark":
                colorCSS = darkModeCSS;
                break;
            case "white":
                colorCSS = whiteModeCSS;
                break;
        }

        // Apply style type CSS
        switch (styleType) {
            case "video":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(100vw - 424px - 5px);
                    --height_big: 400px;

                    --video_margin: 0 0 90px 10px !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 1 / 1 !important;
                }
                `;
                break;

            case "video2":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(50vw - 217px);
                    --height_big: calc(100vh - 80px);

                    --grid-layout: 1fr 80px;
                    --video_margin: 0 10px 20px 10px !important;
                    --article_height: calc(var(--app-height) - 265px);
                }

                .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                    grid-template-columns: 1fr 424px 1fr;
                }

                .main-content {
                    grid-area: 1 / 1 / -1 / 1 !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-footer {
                    grid-area: 2 / 1 / 2 / 1 !important;
                }

                .modal-container .modls {
                    align-items: end;
                }
                `;
                break;

            case "audio":
                specificCSS = `
                :root {
                    --height_big: 10px;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 2 / 1 !important;
                }
                `;
                break;

            case "off":
                css = `
                :root {
                    --width_small: 440px;
                    --height_small: 260px;
                    --right_pos: 0.5%;
                    --bottom_pos: 5.5%;
                }

                .video-player.is-minimized .video-wrapper,
                .sent-video-player.is-minimized .video-wrapper {
                    height: var(--height_small);
                    width: var(--width_small);
                    overflow: auto;
                    resize: both;
                }

                .video-player.is-minimized .modal-content,
                .sent-video-player.is-minimized .modal-content {
                    max-width: calc(var(--width_small)* 3);
                    margin-bottom: 0;
                }

                .video-player.is-minimized,
                .sent-video-player.is-minimized {
                    left: auto;
                    top: auto;
                    right: var(--right_pos);
                    bottom: var(--bottom_pos);
                    z-index: 99999999;
                    overflow: visible
                }
                `;
                break;
        }

        // Append the style-specific CSS and color mode CSS
        css += colorCSS + specificCSS;

        // Remove the old style tag.
        if (styleElement) {
            styleElement.remove();
            styleElement = null;
        }

        // Create & append the new style tag
        if (css) {
            styleElement = document.createElement("style");
            styleElement.textContent = css;
            document.querySelector("head").appendChild(styleElement);
        }
    }

    // Apply styles on initial load
    applyStyles(storedStyleType, storedColorMode);

    // Apply styles when the style type combobox changes
    styleTypeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("styleType", selectedStyleType); // Save to GM storage
    });

    // Apply styles when the color mode combobox changes
    colorModeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("colorMode", selectedColorMode); // Save to GM storage
    });

    // Add new shortcuts
    document.addEventListener(
        "keydown",
        function (event) {
            const targetElement = event.target;
            const isTextInput =
                targetElement.type === "text" ||
                targetElement.type === "textarea";
            if (isTextInput) {
                return;
            }

            // video full screen toggle
            if (event.key === "q" || event.key === "Q") {
                const full_screen_btn = document.querySelector(
                    ".modal-section > div > button:nth-child(2)",
                );
                if (full_screen_btn) {
                    full_screen_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Backward (Same as the 'Ctrl + ,')
            if (event.key === "w") {
                const backward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(1) > a",
                );
                if (backward_btn) {
                    backward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Forward (Same as the 'Ctrl + .')
            if (event.key === "e") {
                const forkward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(2) > a",
                );
                if (forkward_btn) {
                    forkward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Make the selected word Known (Same as the 'k')
            if (event.key === "r") {
                document.dispatchEvent(
                    new KeyboardEvent("keydown", {
                        key: "k",
                    }),
                );
                event.preventDefault();
                event.stopPropagation();
            }

            // Move cursor to the reference input
            if (event.key === "`") {
                const reference_input = document.querySelector(
                    ".reference-input-text",
                );

                if (reference_input) {
                    reference_input.focus();
                    reference_input.setSelectionRange(
                        reference_input.value.length,
                        reference_input.value.length,
                    );
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Dictionary
            if (
                (event.key === "d" || event.key === "f") &&
                !event.ctrlKey &&
                !event.shiftKey &&
                !event.altKey
            ) {
                const dict_btn = document.querySelector(
                    ".dictionary-resources > a:nth-child(1)",
                );
                if (dict_btn) {
                    dict_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Translator
            if (event.key === "t") {
                const translator_btn = document.querySelector(
                    ".dictionary-resources > a:nth-last-child(1)",
                );
                if (translator_btn) {
                    translator_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Copy selected text
            if (event.key === "c") {
                const selected_text = document.querySelector(".reference-word");
                if (selected_text) {
                    navigator.clipboard.writeText(selected_text.textContent);
                }
                event.preventDefault();
                event.stopPropagation();
            }
        },
        true,
    );

    // Custom embedded player
    function replaceNoCookie() {
        document.querySelectorAll("iframe").forEach(function (iframe) {
            let src = iframe.getAttribute("src");

            if (src && src.includes("disablekb=1")) {
                // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
                src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
                src = src + "&cc_load_policy=1"; // caption is shown by default.
                src = src + "&controls=0"; // player controls do not display in the player.
                iframe.setAttribute("src", src);
                console.log(src);
            }
        });
    }

    const iframeObserver = new MutationObserver(function (
        mutationsList,
        observer,
    ) {
        for (const mutation of mutationsList) {
            if (
                mutation.type === "childList" &&
                mutation.addedNodes.length > 0
            ) {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeName === "IFRAME") {
                        replaceNoCookie();
                        document.querySelector('.modal-section.modal-section--head button[title="Expand"]').click();
                    }
                });
            }
        }
    });

    iframeObserver.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["src"],
    });

    // Change the amount of a scroll
    setTimeout(() => {
        console.log("scroll event!");
        const myDiv = document.querySelector(".reader-container");
        myDiv.addEventListener("wheel", (event) => {
            event.preventDefault();
            const delta = event.deltaY;
            const scrollAmount = 0.3;
            myDiv.scrollTop += delta * scrollAmount;
        });
    }, 3000);

    // Focus on playing sentence
    function focusPlayingSentence() {
        const playingSentence = document.querySelector(".sentence.is-playing");

        if (playingSentence) {
            playingSentence.scrollIntoView({
                behavior: "smooth",
                block: "center",
            });
        }
    }

    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (
                mutation.type === "attributes" &&
                mutation.attributeName === "class" &&
                mutation.target.classList.contains("sentence")
            ) {
                focusPlayingSentence();
            }
        });
    });

    const container = document.querySelector(".sentence-text");
    if (container) {
        console.log("observer setted!");
        observer.observe(container, {
            attributes: true,
            subtree: true,
        });
    }
})();

Thanks for this.

I use 2 monitors and I wonder if there is a way to let the translation “window” appear on the second monitor? (since I still think the main text and video is all I really want on my main screen)

There’s no way.

  1. I can not extract the translations. (I don’t use translation feature provided by LingQ).
  2. Controlling another window is out of the scope of Userstyle.
  3. You need to manage the translation in the other window manually.
1 Like

Thanks.
Not sure what I am doing wrong, but the text is all at the left and scrolls.

Also, I do not want the yellow box around the characters. And, dark mode turns the text to a hardly legible beige ?

Video1

Video2



Change the value of the border-related variables

      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);
      --lingq_background: hsl(47, 100%, 50%, 0.4);
      --lingq_border: hsl(47, 100%, 50%, 0.3);
      --lingq_border_learned: hsl(47, 100%, 50%, 1);
      --blue_border: hsl(214, 100%, 50%, 0.3);

Last value is the oppacity.

1 Like

Text is still in the left corner…I will look into this on the weekend. I am sure I will figure it out. Thanks again

Change the layout to the Video
Video2 lists the article, the word widget, and the video horizontally. It’s for wide-screen users.

  • Whenever you change the dark/white theme setting, LingQ’s theme also changes automatically.
// ==UserScript==
// @name         LingQ Addon
// @description  Provides custom LingQ layouts
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @version      2.10
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
    "use strict";

    // Function to load a value from GM storage
    function getStoredValue(key, defaultValue) {
        const value = GM_getValue(key);
        return value === undefined ? defaultValue : value;
    }

    // Function to save a value to GM storage
    function setStoredValue(key, value) {
        GM_setValue(key, value);
    }

    // Create the style type selector (Combobox)
    const styleTypeSelector = document.createElement("select"); // Moved here
    styleTypeSelector.id = "styleTypeSelector";

    // Add options to the selector
    const options = ["video", "video2", "audio", "off"];
    options.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        styleTypeSelector.appendChild(opt);
    });

    // Load the previous selected setting from GM storage
    const storedStyleType = getStoredValue("styleType", "video");
    styleTypeSelector.value = storedStyleType;

    // Create the color mode selector (Toggle)
    const colorModeSelector = document.createElement("select");
    colorModeSelector.id = "colorModeSelector";

    // Add options to the color mode selector
    const colorOptions = ["dark", "white"];
    colorOptions.forEach((option) => {
        const opt = document.createElement("option");
        opt.value = option;
        opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
        colorModeSelector.appendChild(opt);
    });

    // Load the previous selected color mode from GM storage
    const storedColorMode = getStoredValue("colorMode", "dark");
    colorModeSelector.value = storedColorMode;

    // Find the #main-nav element
    // More robust selector, checks nav first
    let mainNav = document.querySelector(
        "#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
    );
    if (!mainNav) {
        // Fallback to simpler selector if the first one fails
        mainNav = document.querySelector("#main-nav");
    }

    if (mainNav) {
        // Insert the combobox inside #main-nav
        mainNav.appendChild(styleTypeSelector);
        // Insert the color mode selector inside #main-nav
        mainNav.appendChild(colorModeSelector);
    } else {
        console.error("#main-nav element not found. ComboBox not inserted.");
    }

    // No longer appending to body

    let styleElement = null;

    // Base CSS (always applied)
    const baseCSS = `
    :root {
      --font_size: 1.1rem;
      --line_height: 1.7;

      --grid-layout: 1fr var(--height_big) 80px;
      --article_height: calc(var(--app-height) - 205px - var(--height_big));
    }

    #styleTypeSelector, #colorModeSelector {
      color: var(--font_color);
      padding: 5px;
      position: relative;
      font-size: 0.9rem;
    }

    .main-wrapper {
      padding-top: calc(var(--spacing) * 12) !important;
    }

    #main-nav .navbar,
    #main-nav .navbar-brand {
      min-height: 2.75rem !important;
    }

    .main-header svg {
      width: 20px !important;
      height: 20px !important;
    }

    #lesson-reader {
      grid-template-rows: var(--grid-layout);
      overflow-y: hidden;
    }

    .sentence-text {
      height: var(--article_height) !important;
    }

    .reader-container-wrapper {
      height: 100% !important;
    }


    /*video viewer*/

    .main-footer {
      grid-area: 3 / 1 / 3 / 1 !important;
      align-self: end;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      align-items: anchor-center;
    }

    .main-content > .main-header {
      margin-top: 30px;
    }

    .modal-container .modls {
      pointer-events: none;
      justify-content: end !important;
      align-items: flex-start;
    }

    .modal-background {
      background-color: rgb(26 28 30 / 0%) !important;
    }

    .modal-section.modal-section--head {
      display: none !important;
    }

    .video-player .video-wrapper,
    .sent-video-player .video-wrapper {
      height: var(--height_big);
      overflow: hidden;
      pointer-events: auto;
    }

    .modal.video-player .modal-content {
      max-width: var(--width_big) !important;
      margin: var(--video_margin);
    }

    /*make prev/next page buttons compact*/

    .reader-component {
      grid-template-columns: 0.5rem 1fr 0rem !important;
    }

    .reader-component > div > a.button > span {
      width: 0.5rem !important;
    }

    .reader-component > div > a.button > span > svg {
      width: 15px !important;
      height: 15px !important;
    }


    /*font settings*/

    .reader-container {
      margin: 0 !important;
      float: left !important;
      line-height: var(--line_height) !important;
      padding: 0 0 100px 0 !important;
      font-size: var(--font_size) !important;
      columns: unset !important;
      overflow-y: scroll !important;
      max-width: unset !important;
    }

    .reader-container p {
      margin-top: 0 !important;
    }

    .reader-container p span.sentence-item {
      color: var(--font_color) !important;
    }

    .sentence.is-playing,
    .sentence.is-playing span {
      text-underline-offset: .2em !important;
      text-decoration-color: var(--is_playing_underline) !important;
    }


    /*LingQ highlightings*/

    .phrase-item {
      padding: 0 !important;
    }

    .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
      background-color: var(--lingq_background) !important;
    }

    .phrase-item.phrase-item-status--4,
    .phrase-item.phrase-item-status--4x2 {
      background-color: rgba(0, 0, 0, 0) !important;
    }

    .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
      border: 1px solid var(--lingq_border) !important;
      border-radius: .25rem;
    }

    .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
      border: 1px solid var(--lingq_border_learned) !important;
      border-radius: .25rem;
    }

    .reader-container .sentence .lingq-word:not(.is-learned) {
      border: 1px solid var(--lingq_border) !important;
      background-color: var(--lingq_background) !important;
    }

    .reader-container .sentence .lingq-word.is-learned {
      border: 1px solid var(--lingq_border_learned) !important;
    }

    .reader-container .sentence .blue-word {
      border: 1px solid var(--blue_border) !important;
    }

    .phrase-cluster:hover,
    .phrase-created:hover {
      padding: 0 !important;
    }

    .phrase-cluster:hover .phrase-item,
    .phrase-created .phrase-item {
      padding: 0 !important;
    }

    .reader-container .sentence .selected-text {
      padding: 0 !important;
    }
    `;

    // Dark mode CSS
    const darkModeCSS = `
    :root {
      --font_color: #e0e0e0;

      --lingq_background: hsl(41 43% 30% / 0.7);
      --lingq_border: hsl(43 99% 64% / 0.3);
      --lingq_border_learned: hsl(43 99% 64% / 0.5);
      --blue_border: hsl(213 99% 64% / 0.5);

      --is_playing_underline: #ffffff;
    }
  `;

    // White mode CSS
    const whiteModeCSS = `
    :root {
      --font_color: #000000;

      --lingq_background: hsl(47, 100%, 50%, 0.4);
      --lingq_border: hsl(47, 100%, 50%, 0.3);
      --lingq_border_learned: hsl(47, 100%, 50%, 1);
      --blue_border: hsl(214, 100%, 50%, 0.3);

      --is_playing_underline: #000000;
    }
  `;

    function applyStyles(styleType, colorMode) {
        let css = baseCSS; // Start with the base CSS
        let specificCSS = "";
        let colorCSS = "";
        let theme_btn = "";

        // Apply color mode CSS
        switch (colorMode) {
            case "dark":
                colorCSS = darkModeCSS;
                theme_btn = document.querySelector(".reader-themes-component > button:nth-child(5)");
                break;
            case "white":
                colorCSS = whiteModeCSS;
                theme_btn = document.querySelector(".reader-themes-component > button:nth-child(1)");
                break;
        }

        // Apply style type CSS
        switch (styleType) {
            case "video":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(100vw - 424px - 5px);
                    --height_big: 400px;

                    --video_margin: 0 0 90px 10px !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 1 / 1 !important;
                }
                `;
                break;

            case "video2":
                // Add video-specific styles here if you have any, otherwise leave empty.
                specificCSS = `
                :root {
                    --width_big: calc(50vw - 217px);
                    --height_big: calc(100vh - 80px);

                    --grid-layout: 1fr 80px;
                    --video_margin: 0 10px 20px 10px !important;
                    --article_height: calc(var(--app-height) - 265px);
                }

                .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
                    grid-template-columns: 1fr 424px 1fr;
                }

                .main-content {
                    grid-area: 1 / 1 / -1 / 1 !important;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-footer {
                    grid-area: 2 / 1 / 2 / 1 !important;
                }

                .modal-container .modls {
                    align-items: end;
                }
                `;
                break;

            case "audio":
                specificCSS = `
                :root {
                    --height_big: 10px;
                }

                .widget-area {
                    grid-area: 1 / 2 / -1 / 2 !important;
                }

                .main-content {
                    grid-area: 1 / 1 / 2 / 1 !important;
                }
                `;
                break;

            case "off":
                css = `
                :root {
                    --width_small: 440px;
                    --height_small: 260px;
                    --right_pos: 0.5%;
                    --bottom_pos: 5.5%;
                }

                .video-player.is-minimized .video-wrapper,
                .sent-video-player.is-minimized .video-wrapper {
                    height: var(--height_small);
                    width: var(--width_small);
                    overflow: auto;
                    resize: both;
                }

                .video-player.is-minimized .modal-content,
                .sent-video-player.is-minimized .modal-content {
                    max-width: calc(var(--width_small)* 3);
                    margin-bottom: 0;
                }

                .video-player.is-minimized,
                .sent-video-player.is-minimized {
                    left: auto;
                    top: auto;
                    right: var(--right_pos);
                    bottom: var(--bottom_pos);
                    z-index: 99999999;
                    overflow: visible
                }
                `;
                break;
        }

        // Append the style-specific CSS and color mode CSS
        css += colorCSS + specificCSS;

        // Remove the old style tag.
        if (styleElement) {
            styleElement.remove();
            styleElement = null;
        }

        // Create & append the new style tag
        if (css) {
            styleElement = document.createElement("style");
            styleElement.textContent = css;
            document.querySelector("head").appendChild(styleElement);
        }

        if (theme_btn) {
            theme_btn.click();
        }
    }

    // Apply styles on initial load
    applyStyles(storedStyleType, storedColorMode);

    // Apply styles when the style type combobox changes
    styleTypeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("styleType", selectedStyleType); // Save to GM storage
    });

    // Apply styles when the color mode combobox changes
    colorModeSelector.addEventListener("change", function () {
        const selectedStyleType = styleTypeSelector.value;
        const selectedColorMode = colorModeSelector.value;
        applyStyles(selectedStyleType, selectedColorMode);
        setStoredValue("colorMode", selectedColorMode); // Save to GM storage
    });

    // Add new shortcuts
    document.addEventListener(
        "keydown",
        function (event) {
            const targetElement = event.target;
            const isTextInput =
                targetElement.type === "text" ||
                targetElement.type === "textarea";
            if (isTextInput) {
                return;
            }

            // video full screen toggle
            if (event.key === "q" || event.key === "Q") {
                const full_screen_btn = document.querySelector(
                    ".modal-section > div > button:nth-child(2)",
                );
                if (full_screen_btn) {
                    full_screen_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Backward (Same as the 'Ctrl + ,')
            if (event.key === "w") {
                const backward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(1) > a",
                );
                if (backward_btn) {
                    backward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // 5 sec Forward (Same as the 'Ctrl + .')
            if (event.key === "e") {
                const forkward_btn = document.querySelector(
                    ".audio-player--controllers > div:nth-child(2) > a",
                );
                if (forkward_btn) {
                    forkward_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Make the selected word Known (Same as the 'k')
            if (event.key === "r") {
                document.dispatchEvent(
                    new KeyboardEvent("keydown", {
                        key: "k",
                    }),
                );
                event.preventDefault();
                event.stopPropagation();
            }

            // Move cursor to the reference input
            if (event.key === "`") {
                const reference_input = document.querySelector(
                    ".reference-input-text",
                );

                if (reference_input) {
                    reference_input.focus();
                    reference_input.setSelectionRange(
                        reference_input.value.length,
                        reference_input.value.length,
                    );
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Dictionary
            if (
                (event.key === "d" || event.key === "f") &&
                !event.ctrlKey &&
                !event.shiftKey &&
                !event.altKey
            ) {
                const dict_btn = document.querySelector(
                    ".dictionary-resources > a:nth-child(1)",
                );
                if (dict_btn) {
                    dict_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Open Translator
            if (event.key === "t") {
                const translator_btn = document.querySelector(
                    ".dictionary-resources > a:nth-last-child(1)",
                );
                if (translator_btn) {
                    translator_btn.click();
                }
                event.preventDefault();
                event.stopPropagation();
            }

            // Copy selected text
            if (event.key === "c") {
                const selected_text = document.querySelector(".reference-word");
                if (selected_text) {
                    navigator.clipboard.writeText(selected_text.textContent);
                }
                event.preventDefault();
                event.stopPropagation();
            }
        },
        true,
    );

    // Custom embedded player
    function replaceNoCookie() {
        document.querySelectorAll("iframe").forEach(function (iframe) {
            let src = iframe.getAttribute("src");

            if (src && src.includes("disablekb=1")) {
                // src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
                src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled.
                src = src + "&cc_load_policy=1"; // caption is shown by default.
                src = src + "&controls=0"; // player controls do not display in the player.
                iframe.setAttribute("src", src);
                console.log(src);
            }
        });
    }

    const iframeObserver = new MutationObserver(function (
        mutationsList,
        observer,
    ) {
        for (const mutation of mutationsList) {
            if (
                mutation.type === "childList" &&
                mutation.addedNodes.length > 0
            ) {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeName === "IFRAME") {
                        replaceNoCookie();
                        document.querySelector('.modal-section.modal-section--head button[title="Expand"]').click();
                    }
                });
            }
        }
    });

    iframeObserver.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["src"],
    });

    // Change the amount of a scroll
    setTimeout(() => {
        console.log("scroll event!");
        const myDiv = document.querySelector(".reader-container");
        myDiv.addEventListener("wheel", (event) => {
            event.preventDefault();
            const delta = event.deltaY;
            const scrollAmount = 0.3;
            myDiv.scrollTop += delta * scrollAmount;
        });
    }, 3000);

    // Focus on playing sentence
    function focusPlayingSentence() {
        const playingSentence = document.querySelector(".sentence.is-playing");

        if (playingSentence) {
            playingSentence.scrollIntoView({
                behavior: "smooth",
                block: "center",
            });
        }
    }

    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (
                mutation.type === "attributes" &&
                mutation.attributeName === "class" &&
                mutation.target.classList.contains("sentence")
            ) {
                focusPlayingSentence();
            }
        });
    });

    const container = document.querySelector(".sentence-text");
    if (container) {
        console.log("observer setted!");
        observer.observe(container, {
            attributes: true,
            subtree: true,
        });
    }
})();

Patch Note


  • 1.3: Article is displayed in a single page.
  • 1.4: Now the sentence playing is located at the center of the screen. You don’t need to scrol acording to the playing video.
  • 1.5: Provide new hotkeys: Move the cursor to the reference input (`), Open Dictionary (d or f), Open Translator (t), and Copy selected text (c)
  • 1.6: Now, LingQ uses “youtube.com” player, not “youtube-nocookie.com 1”. Some code is updated for that change.

  • 2.3 Provide three layout optionss: Video, Video(wide), Audio, off.
  • 2.4: Provide video(wide) layout options
  • 2.6: Fixed some hotkeys-related glitches.
  • 2.7: Added a toggle for dark/white backgound theme.
  • 2.8: The video player in the “off” setting is resizable and movable, Removed the “course sorting”
  • 2.9: The video player is automatically expanded.
  • 2.10: Whenever you change the dark/white theme setting, LingQ’s theme also changes automatically.

  • 3.0: You can set the options according to your preference in the options pop-up.
  • 3.1: Changed the location of the reset button. Fix some unit-related problems.
  • 3.2: Changed color pickers and default values.

You can install the extension from here:

If it was helpful to you, you can
Buy Me A Coffee