Customized LingQ Reader Theme & Add-ons

Before & After

Reader

Video Player (default size / location)

Features

Theme settings

  • Change the default size/location of video player.
  • Make prev/next page buttons compact
  • Custom font settings (clolor, paddings, space between lines, …)
  • Custom highlightings (color, border, paddings) on words (lingqed, leared, new)

Addons

  • Modified embedded player settings (caption on, auto play, disable controls)
  • Add a video full screen toggle shortcut (x key)
  • Change course sorting: Automatically changes the sorting option in a course page from ‘All lessons’ to other Option. You can change what would be defaulted by changing the number in .dropdown-item:nth-child(3).

How to use?

It is a userscript can be used in Tampermonkey or other apps.
Install the extension and copy & paste my code.

Homepage:

Random tutorial I found on google:

Code

// ==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
// @version      1.3
// ==/UserScript==

(function () {
  "use strict";
  
  // Style settings
  var style = document.createElement("style");
  style.textContent = `
  :root {
    --width_small: 540px;
    --height_small: 370px;
    --height_big: 650px;
    --width_big: 1100px;
    --right_pos: 0.5%;
    --bottom_pos: 5.5%;
    
    --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);
  }

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

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

  .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
  main {}

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

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


  // font settings
  article {}

  .reader-container {
    margin: 0 !important;
    float: left !important;
    line-height: var(--line_height) !important;
    padding: 0 !important;
    font-size: var(--font_size) !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;
  }
  
  // LingQ highlightings
  .phrase-cluster {}
  
  .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;
  }
  `;
  document.querySelector("head").appendChild(style);
  
  
  
  // 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 (x key)
    if (event.key === 'x' || event.key === 'X') {
      const full_screen_btn = document.querySelector('.modal-section > div > button:nth-child(2)');
      if (full_screen_btn) {
        full_screen_btn.click();
      }
    }
  });
  

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

        if (src && src.includes('autoplay=0')) {
            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'] });
  }


  // 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();
          }, 500);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

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

You can customize settings by adjusting these values in the code:

   :root {
    --width_small: 540px;
    --height_small: 370px;
    --height_big: 650px;
    --width_big: 1100px;
    --right_pos: 0.5%;
    --bottom_pos: 5.5%;
    
    --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);
  }
3 Likes