Watch9 Reconstruct (modified)

Restores the old watch layout from before 2019 (works better with both 2016-2020 UI scripts and Proper Description script to be installed.)

  1. // ==UserScript==
  2. // @name Watch9 Reconstruct (modified)
  3. // @version 2024.07.24
  4. // @description Restores the old watch layout from before 2019 (works better with both 2016-2020 UI scripts and Proper Description script to be installed.)
  5. // @author Magma_Craft (originally by Aubrey P.)
  6. // @icon https://www.youtube.com/favicon.ico
  7. // @namespace Magma_Craft (originally by aubymori)
  8. // @license Unlicense
  9. // @match www.youtube.com/*
  10. // @grant none
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. const w9rOptions = {
  15. oldAutoplay: true, // Classic autoplay renderer with "Up next" text
  16. removeBloatButtons: true // Removes "Clip", "Thanks", "Download", etc.
  17. }
  18.  
  19. /**
  20. * Localization strings.
  21. *
  22. * See LOCALIZATION.md in the GitHub repo.
  23. */
  24. const w9ri18n = {
  25. en: {
  26. subSuffixMatch: /( subscribers)|( subscriber)/,
  27. nonPublishMatch: /(Premier)|(Stream)|(Start)/,
  28. publishedOn: "Published on %s",
  29. uploadedOn: "Uploaded on %s",
  30. upNext: "Up next",
  31. autoplay: "Autoplay",
  32. autoplayTip: "When autoplay is enabled, a suggested video will automatically play next."
  33. },
  34. ja: {
  35. subSuffixMatch: /(チャンネル登録者数 )|(人)/g,
  36. nonPublishMatch: /(公開済)|(開始済)/g,
  37. publishedOn: "%s に公開",
  38. uploadedOn: "%s にアップロード",
  39. upNext: "自動再生",
  40. autoplay: "次の動画",
  41. autoplayTip: "自動再生を有効にすると、関連動画が自動的に再生されます。"
  42. },
  43. pl: {
  44. subSuffixMatch: /( subskrybentów)|( subskrybent)/,
  45. nonPublishMatch: /(Data premiery: )|(adawane na żywo )|(Transmisja zaczęła się )/,
  46. publishedOn: "Opublikowany %s",
  47. uploadedOn: "Przesłany %s",
  48. upNext: "Następny",
  49. autoplay: "Autoodtwarzanie",
  50. autoplayTip: "Jeśli masz włączone autoodtwarzanie, jako następny włączy się automatycznie proponowany film."
  51. },
  52. fil: {
  53. subSuffixMatch: /(na)|( subscribers)|( subscriber)|(\s)/g,
  54. nonPublishMatch: /(simula)/,
  55. publishedOn: "Na-publish noong %s",
  56. uploadedOn: "Na-upload noong %s",
  57. upNext: "Susunod",
  58. autoplay: "I-autoplay",
  59. autoplayTip: "Kapag naka-enable ang autoplay, awtomatikong susunod na magpe-play ang isang iminumungkahing video."
  60. },
  61. fr: {
  62. subSuffixMatch: /( abonnés)|( abonné)|( d’abonnés)|( d’abonné)/g,
  63. nonPublishMatch: /(Diffus)|(Sortie)/g,
  64. publishedOn: "Publiée le %s",
  65. uploadedOn: "Mise en ligne le %s",
  66. upNext: "À suivre",
  67. autoplay: "Lecture automatique",
  68. autoplayTip: "Lorsque cette fonctionnalité est activée, une vidéo issue des suggestions est automatiquement lancée à la suite de la lecture en cours."
  69. },
  70. es: {
  71. subSuffixMatch: /( de suscriptores)|( suscriptor)/g,
  72. nonPublishMatch: /(directo)|(Fecha)/g,
  73. publishedOn: "Publicado el %s",
  74. uploadedOn: "Subido el %s",
  75. upNext: "A continuación",
  76. autoplay: "Reproducción automática",
  77. autoplayTip: "Si la reproducción automática está habilitada, se reproducirá automáticamente un vídeo a continuación."
  78. },
  79. pt: {
  80. subSuffixMatch: /( de subscritores)|( subscritor)/g,
  81. nonPublishMatch: /(Stream)|(Estreou)/g,
  82. publishedOn: "Publicado a %s",
  83. uploadedOn: "Carregado a %s",
  84. upNext: "Próximo",
  85. autoplay: "Reprodução automática",
  86. autoplayTip: "Quando a reprodução automática é ativada, um vídeo sugerido será executado automaticamente em seguida."
  87. },
  88. ru: {
  89. subSuffixMatch: /( подписчиков)|( подписчик)/g,
  90. nonPublishMatch: /(Сейчас смотрят:)|(Прямой эфир состоялся)|(Дата премьеры:)/g,
  91. publishedOn: "Дата публикации: %s",
  92. uploadedOn: "Дата публикации: %s",
  93. upNext: "Следующее видео",
  94. autoplay: "Автовоспроизведение",
  95. autoplayTip: "Если функция включена, то следующий ролик начнет воспроизводиться автоматически."
  96. },
  97. hu: {
  98. subSuffixMatch: /( előfizetők)|( előfizető)/,
  99. nonPublishMatch: /(Megjelenés dátuma: )|(Élő adás )|(Az adás elkezdődött )/,
  100. publishedOn: "Közzététel: %s",
  101. uploadedOn: "Feltöltvel: %s",
  102. upNext: "Következő",
  103. autoplay: "Automatikus lejátszás",
  104. autoplayTip: "Ha engedélyezte az automatikus lejátszást, a javasolt film automatikusan aktiválódik a következő filmként."
  105. },
  106. de: {
  107. subSuffixMatch: /( Abonnenten)|( Abonnent)/,
  108. nonPublishMatch: /(Premier)|(Stream)|(Start)/,
  109. publishedOn: "Am %s veröffentlicht",
  110. uploadedOn: "Am %s hochgeladen",
  111. upNext: "Nächstes Video",
  112. autoplay: "Autoplay",
  113. autoplayTip: "Wenn die Autoplay aktiviert ist, wird als nächstes automatisch ein vorgeschlagenes Video abgespielt."
  114. }
  115. };
  116.  
  117. /**
  118. * Wait for a selector to exist
  119. *
  120. * @param {string} selector CSS Selector
  121. * @param {HTMLElement} base Element to search inside
  122. * @returns {Node}
  123. */
  124. async function waitForElm(selector, base = document) {
  125. if (!selector) return null;
  126. if (!base.querySelector) return null;
  127. while (base.querySelector(selector) == null) {
  128. await new Promise(r => requestAnimationFrame(r));
  129. };
  130. return base.querySelector(selector);
  131. };
  132.  
  133. /**
  134. * Get a string from the localization strings.
  135. *
  136. * @param {string} string Name of string to get
  137. * @param {string} hl Language to use.
  138. * @returns {string}
  139. */
  140. function getString(string, hl = "en") {
  141. if (!string) return "ERROR";
  142. if (w9ri18n[hl]) {
  143. if (w9ri18n[hl][string]) {
  144. return w9ri18n[hl][string];
  145. } else if (w9ri18n.en[string]) {
  146. return w9ri18n.en[string];
  147. } else {
  148. return "ERROR";
  149. }
  150. } else {
  151. if (w9ri18n.en[string]) return w9ri18n.en[string];
  152. return "ERROR";
  153. }
  154. }
  155.  
  156. /**
  157. * Format upload date string to include "Published on" or "Uploaded on" if applicable.
  158. *
  159. * @param {string} dateStr dateText from InnerTube ("Sep 13, 2022", "Premiered 2 hours ago", etc.)
  160. * @param {boolean} isPublic Is the video public?
  161. * @param {string} hl Language to use.
  162. * @returns {string}
  163. */
  164. function formatUploadDate(dateStr, isPublic, hl = "en") {
  165. var nonPublishMatch = getString("nonPublishMatch", hl);
  166. var string = isPublic ? getString("publishedOn", hl) : getString("uploadedOn", hl);
  167. if (nonPublishMatch.test(dateStr)) {
  168. return dateStr;
  169. } else {
  170. return string.replace("%s", dateStr);
  171. }
  172. }
  173.  
  174. /**
  175. * Format subscriber count string to only include count.
  176. *
  177. * @param {string} count Subscriber count string from InnerTube ("374K subscribers", "No subscribers", etc.)
  178. * @param {string} hl Language to use.
  179. * @returns {string}
  180. */
  181. function formatSubCount(count, hl = "en") {
  182. if (count == null) return "";
  183. var tmp = count.replace(getString("subSuffixMatch", hl), "");
  184. return tmp;
  185. }
  186.  
  187. /**
  188. * Parse document.cookie
  189. *
  190. * @returns {object}
  191. */
  192. function parseCookies() {
  193. var c = document.cookie.split(";"), o = {};
  194. for (var i = 0, j = c.length; i < j; i++) {
  195. var s = c[i].split("=");
  196. var n = s[0].replace(" ", "");
  197. s.splice(0, 1);
  198. s = s.join("=");
  199. o[n] = s;
  200. }
  201. return o;
  202. }
  203.  
  204. /**
  205. * Parse YouTube's PREF cookie.
  206. *
  207. * @param {string} pref PREF cookie content
  208. * @returns {object}
  209. */
  210. function parsePref(pref) {
  211. var a = pref.split("&"), o = {};
  212. for (var i = 0, j = a.length; i < j; i++) {
  213. var b = a[i].split("=");
  214. o[b[0]] = b[1];
  215. }
  216. return o;
  217. }
  218.  
  219. /**
  220. * Is autoplay enabled?
  221. *
  222. * @returns {boolean}
  223. */
  224. function autoplayState() {
  225. var cookies = parseCookies();
  226. if (cookies.PREF) {
  227. var pref = parsePref(cookies.PREF);
  228. if (pref.f5) {
  229. return !(pref.f5 & 8192)
  230. } else {
  231. return true; // default
  232. }
  233. } else {
  234. return true;
  235. }
  236. }
  237.  
  238. /**
  239. * Toggle autoplay.
  240. *
  241. * @returns {void}
  242. */
  243. function clickAutoplay() {
  244. var player = document.querySelector("#movie_player");
  245. var autoplay;
  246. if (autoplay = player.querySelector(".ytp-autonav-toggle-button-container")) {
  247. autoplay.parentNode.click();
  248. } else {
  249. var settings = player.querySelector('.ytp-settings-button');
  250. settings.click();settings.click();
  251. var item = player.querySelector('.ytp-menuitem[role="menuitemcheckbox"]');
  252. item.click();
  253. }
  254. }
  255.  
  256. /**
  257. * Should the Autoplay renderer be inserted?
  258. * (Basically, if there's a playlist active)
  259. *
  260. * @returns {boolean}
  261. */
  262. function shouldHaveAutoplay() {
  263. var playlist;
  264. if (playlist = document.querySelector("#playlist.ytd-watch-flexy")) {
  265. if (playlist.hidden && playlist.hidden == true) {
  266. return true;
  267. } else {
  268. return false;
  269. }
  270. } else {
  271. return true;
  272. }
  273. }
  274.  
  275. /**
  276. * Is a value in an array?
  277. *
  278. * @param {*} needle Value to search
  279. * @param {Array} haystack Array to search
  280. * @returns {boolean}
  281. */
  282. function inArray(needle, haystack) {
  283. for (var i = 0; i < haystack.length; i++) {
  284. if (needle == haystack[i]) return true;
  285. }
  286. return false;
  287. }
  288.  
  289. /**
  290. * Remove bloaty action buttons.
  291. *
  292. * @returns {void}
  293. */
  294. function removeBloatButtons() {
  295. var primaryInfo = document.querySelector("ytd-video-primary-info-renderer");
  296. var actionBtns = primaryInfo.data.videoActions.menuRenderer.topLevelButtons;
  297.  
  298. // Remove the action buttons accordingly.
  299. for (var i = 0; i < actionBtns.length; i++) {
  300. if (actionBtns[i].downloadButtonRenderer) {
  301. actionBtns.splice(i, 1);
  302. i--;
  303. } else if (actionBtns[i].buttonRenderer) {
  304. if (inArray(actionBtns[i].buttonRenderer.icon.iconType, ["MONEY_HEART", "CONTENT_CUT"])) {
  305. actionBtns.splice(i, 1);
  306. i--;
  307. }
  308. }
  309. }
  310.  
  311. // Refresh the primary info's data.
  312. var tmp = primaryInfo.data;
  313. primaryInfo.data = {};
  314. primaryInfo.data = tmp;
  315. }
  316.  
  317. /**
  318. * Is the current video public? Or is it unlisted/private?
  319. *
  320. * @returns {boolean}
  321. */
  322. function isVideoPublic() {
  323. const primaryInfo = document.querySelector("ytd-video-primary-info-renderer");
  324. if (primaryInfo.data.badges == null) return true;
  325. const badges = primaryInfo.data.badges;
  326.  
  327. for (var i = 0; i < badges.length; i++) {
  328. var iconType = badges[i].metadataBadgeRenderer.icon.iconType;
  329. if (iconType == "PRIVACY_UNLISTED" || iconType == "PRIVACY_PRIVATE") {
  330. return false;
  331. }
  332. }
  333. return true;
  334. }
  335.  
  336. /**
  337. * Get sidebar data.
  338. *
  339. * @returns {object}
  340. */
  341. async function getSidebarData() {
  342. const secondaryResults = document.querySelector("ytd-watch-next-secondary-results-renderer");
  343. const resultData = secondaryResults.data.results;
  344. var response = {};
  345.  
  346. if (yt.config_.LOGGED_IN == false) {
  347. response.element = await waitForElm("#items.ytd-watch-next-secondary-results-renderer");
  348. response.data = resultData;
  349. response.class = "ytd-watch-next-secondary-results-renderer";
  350. return response;
  351. } else {
  352. var tmp;
  353. if (tmp = resultData[0].relatedChipCloudRenderer) {
  354. response.element = await waitForElm("#contents.ytd-item-section-renderer", secondaryResults);
  355. response.data = resultData[1].itemSectionRenderer.contents;
  356. response.class = "ytd-item-section-renderer";
  357. return response;
  358. } else {
  359. response.element = await waitForElm("#items.ytd-watch-next-secondary-results-renderer");
  360. response.data = resultData;
  361. response.class = "ytd-watch-next-secondary-results-renderer";
  362. return response;
  363. }
  364. }
  365. }
  366.  
  367. /**
  368. * Build the classic compact autoplay renderer.
  369. *
  370. * @returns {void}
  371. */
  372. async function buildAutoplay() {
  373. // Prevent it from building autoplay twice
  374. if (document.querySelector("ytd-compact-autoplay-renderer") != null) return;
  375.  
  376. const watchFlexy = document.querySelector("ytd-watch-flexy");
  377. const sidebarItems = await getSidebarData();
  378. const language = yt.config_.HL.split("-")[0] ?? "en";
  379. const autoplayStub = `
  380. <ytd-compact-autoplay-renderer class="style-scope ${ sidebarItems.class }">
  381. <div id="head" class="style-scope ytd-compact-autoplay-renderer">
  382. <div id="upnext" class="style-scope ytd-compact-autoplay-renderer"></div>
  383. <div id="autoplay" class="style-scope ytd-compact-autoplay-renderer"></div>
  384. <tp-yt-paper-toggle-button id="toggle" noink="" class="style-scope ytd-compact-autoplay-renderer" role="button" aria-pressed="" tabindex="0" style="touch-action: pan-y;" toggles="" aria-disabled="false" aria-label="">
  385. <tp-yt-paper-tooltip id="tooltip" class="style-scope ytd-compact-autoplay-renderer" role="tooltip" tabindex="-1">${ getString("autoplayTip", language) }</tp-yt-paper-tooltip>
  386. </tp-yt-paper-toggle-button>
  387. </div>
  388. <div id="contents" class="style-scope ytd-compact-autoplay-renderer"></div>
  389. </ytd-compact-autoplay-renderer>
  390. `;
  391.  
  392.  
  393. // Insert the autoplay stub.
  394. sidebarItems.element.insertAdjacentHTML("beforebegin", autoplayStub);
  395. var autoplayRenderer = sidebarItems.element.parentNode.querySelector("ytd-compact-autoplay-renderer");
  396.  
  397. // Apply the appropriate localized text.
  398. autoplayRenderer.querySelector("#upnext").innerText = getString("upNext", language);
  399. autoplayRenderer.querySelector("#autoplay").innerText = getString("autoplay", language);
  400.  
  401. // Add event listener to toggle
  402. autoplayRenderer.querySelector("#toggle").addEventListener("click", clickAutoplay);
  403.  
  404. // Copy first video from data into autoplay renderer
  405. var firstVideo;
  406. for (var i = 0; i < sidebarItems.data.length; i++) {
  407. if (sidebarItems.data[i].compactVideoRenderer) {
  408. firstVideo = sidebarItems.data[i];
  409. break;
  410. }
  411. }
  412.  
  413. var videoRenderer = document.createElement("ytd-compact-video-renderer");
  414. videoRenderer.data = firstVideo.compactVideoRenderer;
  415. videoRenderer.classList.add("style-scope", "ytd-compact-autoplay-renderer")
  416. videoRenderer.setAttribute("lockup", "true");
  417. videoRenderer.setAttribute("thumbnail-width", "168");
  418. autoplayRenderer.querySelector("#contents").appendChild(videoRenderer);
  419.  
  420. // Add the interval to update toggle if it isn't already.
  421. if (!watchFlexy.getAttribute("autoplay-interval-active")) {
  422. setInterval(() => {
  423. if (autoplayState()) {
  424. autoplayRenderer.querySelector("#toggle").setAttribute("checked", "");
  425. } else {
  426. autoplayRenderer.querySelector("#toggle").removeAttribute("checked");
  427. }
  428. }, 100);
  429. }
  430. }
  431.  
  432. /**
  433. * Build new Watch9 elements and tweak currently existing elements accordingly.
  434. *
  435. * @returns {void}
  436. */
  437. function buildWatch9() {
  438. const watchFlexy = document.querySelector("ytd-watch-flexy");
  439. const primaryInfo = watchFlexy.querySelector("ytd-video-primary-info-renderer");
  440. const secondaryInfo = watchFlexy.querySelector("ytd-video-secondary-info-renderer");
  441. const viewCount = primaryInfo.querySelector("ytd-video-view-count-renderer");
  442. const subBtn = secondaryInfo.querySelector("#subscribe-button tp-yt-paper-button");
  443. const uploadDate = secondaryInfo.querySelector(".date.ytd-video-secondary-info-renderer"); // Old unused element that we inject the date into
  444. const language = yt.config_.HL.split("-")[0] ?? "en";
  445.  
  446. // Let script know we've done this initial build
  447. watchFlexy.setAttribute("watch9-built", "");
  448.  
  449. // Publish date
  450. var newUploadDate = formatUploadDate(primaryInfo.data.dateText.simpleText, isVideoPublic(), language);
  451. uploadDate.innerText = newUploadDate;
  452.  
  453. // Sub count
  454. var newSubCount;
  455. if (secondaryInfo.data.owner.videoOwnerRenderer.subscriberCountText) {
  456. newSubCount = formatSubCount(secondaryInfo.data.owner.videoOwnerRenderer.subscriberCountText.simpleText, language);
  457. } else {
  458. newSubCount = "0";
  459. }
  460. var w9rSubCount = document.createElement("yt-formatted-string");
  461. w9rSubCount.classList.add("style-scope", "deemphasize");
  462. w9rSubCount.text = {
  463. simpleText: newSubCount
  464. };
  465. subBtn.insertAdjacentElement("beforeend", w9rSubCount);
  466.  
  467. // Bloat buttons
  468. if (w9rOptions.removeBloatButtons) removeBloatButtons();
  469.  
  470. // Autoplay
  471. if (w9rOptions.oldAutoplay && shouldHaveAutoplay()) buildAutoplay();
  472. }
  473.  
  474. /**
  475. * Update currently existing Watch9 elements.
  476. *
  477. * @returns {void}
  478. */
  479. function updateWatch9() {
  480. const primaryInfo = document.querySelector("ytd-video-primary-info-renderer");
  481. const secondaryInfo = document.querySelector("ytd-video-secondary-info-renderer");
  482. const subCnt = secondaryInfo.querySelector("yt-formatted-string.deemphasize");
  483. const uploadDate = secondaryInfo.querySelector(".date.ytd-video-secondary-info-renderer");
  484. const language = yt.config_.HL.split("-")[0] ?? "en";
  485.  
  486. // Publish date
  487. var newUploadDate = formatUploadDate(primaryInfo.data.dateText.simpleText, isVideoPublic(), language);
  488. uploadDate.innerText = newUploadDate;
  489.  
  490. // Sub count
  491. var newSubCount = formatSubCount(secondaryInfo.data.owner.videoOwnerRenderer.subscriberCountText.simpleText, language);
  492. subCnt.text = {
  493. simpleText: newSubCount
  494. };
  495.  
  496. // Bloat buttons
  497. if (w9rOptions.removeBloatButtons) removeBloatButtons();
  498.  
  499. // Autoplay
  500. if (w9rOptions.oldAutoplay && shouldHaveAutoplay()) buildAutoplay();
  501. }
  502.  
  503. /**
  504. * Run the Watch9 build/update functions.
  505. */
  506. document.addEventListener("yt-page-data-updated", (e) => {
  507. if (e.detail.pageType == "watch") {
  508. if (document.querySelector("ytd-compact-autoplay-renderer")) {
  509. document.querySelector("ytd-compact-autoplay-renderer").remove();
  510. }
  511.  
  512. if (document.querySelector("ytd-watch-flexy").getAttribute("watch9-built") != null) {
  513. updateWatch9();
  514. } else {
  515. buildWatch9();
  516. }
  517. }
  518. });
  519.  
  520. /**
  521. * Inject styles.
  522. */
  523. document.addEventListener("DOMContentLoaded", function tmp() {
  524. document.head.insertAdjacentHTML("beforeend", `
  525. <style id="watch9-fix">
  526. ytd-watch-metadata {
  527. display: block !important;
  528. margin-bottom: -62px !important;
  529. }
  530.  
  531. ytd-video-primary-info-renderer, ytd-video-secondary-info-renderer {
  532. border-bottom: 0px !important;
  533. }
  534.  
  535. #meta-contents[hidden],
  536. #info-contents[hidden] {
  537. display: block !important;
  538. }
  539.  
  540. div#owner.item.style-scope.ytd-watch-metadata, div#bottom-row.style-scope.ytd-watch-metadata {
  541. display: none !important;
  542. }
  543.  
  544. ytd-watch-metadata.watch-active-metadata.style-scope.ytd-watch-flexy.style-scope.ytd-watch-flexy {
  545. padding-bottom: 0px !important;
  546. border-bottom-width: 0px !important;
  547. }
  548.  
  549. ytd-watch-metadata #top-row > #info > * {
  550. display: none !important;
  551. }
  552.  
  553. yt-formatted-string.ytd-video-primary-info-renderer {
  554. display: none !important;
  555. }
  556.  
  557. ytd-menu-renderer.style-scope.ytd-video-primary-info-renderer {
  558. display: none !important;
  559. }
  560.  
  561. ytd-video-primary-info-renderer.style-scope.ytd-watch-flexy {
  562. padding: 28px 0 16px 0 !important;
  563. }
  564.  
  565. ytd-video-view-count-renderer[small] {
  566. font-size: 1.6rem !important;
  567. line-height: 2.2rem !important;
  568. }
  569.  
  570. yt-formatted-string.deemphasize {
  571. opacity: .85;
  572. margin-left: 6px;
  573. }
  574.  
  575. yt-formatted-string.deemphasize:empty {
  576. margin-left: 0;
  577. }
  578.  
  579. /**
  580. * Prevent sub count from appearing on the "Edit video" button since
  581. * it uses the same element as subscribe button
  582. */
  583. ytd-button-renderer.style-primary yt-formatted-string.deemphasize {
  584. display: none;
  585. }
  586.  
  587. #info-strings.ytd-video-primary-info-renderer,
  588. #owner-sub-count.ytd-video-owner-renderer {
  589. display: none !important;
  590. }
  591.  
  592. div#container.style-scope.ytd-video-primary-info-renderer ytd-badge-supported-renderer,
  593. a.yt-simple-endpoint.style-scope.ytd-video-primary-info-renderer {
  594. display: none
  595. }
  596.  
  597. /* Revert video list (aka compact right sidebar) */
  598. ytd-watch-flexy #comment-teaser.ytd-watch-metadata {
  599. display: none;
  600. }
  601.  
  602. ytd-watch-flexy #dismissible.ytd-rich-grid-media {
  603. flex-direction: row;
  604. }
  605.  
  606. ytd-watch-flexy #attached-survey.ytd-rich-grid-media,
  607. ytd-watch-flexy #avatar-link.ytd-rich-grid-media {
  608. display: none;
  609. }
  610.  
  611. ytd-watch-flexy ytd-thumbnail.ytd-rich-grid-media,
  612. ytd-watch-flexy ytd-playlist-thumbnail.ytd-rich-grid-media {
  613. margin-right: 8px;
  614. height: 94px;
  615. width: 168px;
  616. }
  617.  
  618. ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail,
  619. ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail {
  620. margin: 4px;
  621. }
  622.  
  623. ytd-watch-flexy ytd-rich-item-renderer,
  624. ytd-watch-flexy ytd-rich-grid-row #contents.ytd-rich-grid-row {
  625. margin: 0;
  626. }
  627.  
  628. ytd-watch-flexy ytd-rich-item-renderer[reduced-bottom-margin] {
  629. margin-top: 8px;
  630. margin-bottom: 0;
  631. }
  632.  
  633. ytd-watch-flexy ytd-rich-grid-renderer[reduced-top-margin] #contents.ytd-rich-grid-renderer {
  634. padding-top: 0px;
  635. }
  636.  
  637. ytd-watch-flexy ytd-rich-grid-media {
  638. margin-bottom: 8px;
  639. }
  640.  
  641. ytd-watch-flexy #details.ytd-rich-grid-media {
  642. width: 100%;
  643. min-width: 0;
  644. }
  645.  
  646. ytd-watch-flexy ytd-video-meta-block[rich-meta] #metadata-line.ytd-video-meta-block,
  647. ytd-watch-flexy #channel-name.ytd-video-meta-block {
  648. font-family: "Roboto", "Arial", sans-serif;
  649. font-size: 1.2rem;
  650. line-height: 1.8rem;
  651. font-weight: 400;
  652. }
  653.  
  654. ytd-watch-flexy #video-title.ytd-rich-grid-media {
  655. margin: 0 0 4px 0;
  656. display: block;
  657. font-family: "Roboto", "Arial", sans-serif;
  658. font-size: 1.4rem;
  659. line-height: 2rem;
  660. font-weight: 500;
  661. overflow: hidden;
  662. display: block;
  663. max-height: 4rem;
  664. -webkit-line-clamp: 2;
  665. display: box;
  666. display: -webkit-box;
  667. -webkit-box-orient: vertical;
  668. text-overflow: ellipsis;
  669. white-space: normal;
  670. }
  671.  
  672. ytd-watch-flexy h3.ytd-rich-grid-media {
  673. margin: 0;
  674. }
  675.  
  676. ytd-watch-flexy .title-badge.ytd-rich-grid-media, ytd-watch-flexy .video-badge.ytd-rich-grid-media {
  677. margin-top: 0;
  678. }
  679.  
  680. ytd-watch-flexy ytd-rich-section-renderer.style-scope.ytd-rich-grid-renderer {
  681. display: none;
  682. }
  683. </style>
  684. `);
  685. if (w9rOptions.oldAutoplay) document.head.insertAdjacentHTML("beforeend", `
  686. <style id="compact-autoplay-fix">
  687. yt-related-chip-cloud-renderer {
  688. display: none;
  689. }
  690.  
  691. ytd-compact-autoplay-renderer {
  692. padding-bottom: 8px;
  693. border-bottom: 1px solid var(--yt-spec-10-percent-layer);
  694. margin-bottom: 16px;
  695. display: flex;
  696. flex-direction: column;
  697. }
  698.  
  699. ytd-compact-autoplay-renderer ytd-compact-video-renderer {
  700. margin: 0 !important;
  701. padding-bottom: 8px;
  702. }
  703.  
  704. #head.ytd-compact-autoplay-renderer {
  705. margin-bottom: 12px;
  706. display: flex;
  707. align-items: center;
  708. }
  709.  
  710. #upnext.ytd-compact-autoplay-renderer {
  711. color: var(--yt-spec-text-primary);
  712. font-size: 1.6rem;
  713. flex-grow: 1;
  714. }
  715.  
  716. #autoplay.ytd-compact-autoplay-renderer {
  717. color: var(--yt-spec-text-secondary);
  718. font-size: 1.3rem;
  719. font-weight: 500;
  720. text-transform: uppercase;
  721. line-height: 1;
  722. }
  723.  
  724. #toggle.ytd-compact-autoplay-renderer {
  725. margin-left: 8px;
  726. }
  727.  
  728. ytd-watch-next-secondary-results-renderer #contents.ytd-item-section-renderer > * {
  729. margin-top: 0 !important;
  730. margin-bottom: var(--ytd-item-section-item-margin,16px);
  731. }
  732.  
  733. #items.ytd-watch-next-secondary-results-renderer > ytd-compact-video-renderer:first-of-type,
  734. ytd-watch-next-secondary-results-renderer #contents.ytd-item-section-renderer > ytd-compact-video-renderer:first-of-type {
  735. display: none !important;
  736. }
  737. </style>
  738. `);
  739. document.removeEventListener("DOMContentLoaded", tmp);
  740. });