YouTube Alchemy

Toolkit for YouTube with 130+ options accessible via settings panels. Key features include: tab view, playback speed control, set video quality, export transcripts, prevent autoplay, hide shorts, hide ad slots, disable play on hover, square design, auto-theater mode, number of videos per row, display remaining time—adjusted for playback speed and SponsorBlock segments, persistent progress bar with chapter markers and SponsorBlock support, modify or hide various UI elements, and much more.

  1. // ==UserScript==
  2. // @name YouTube Alchemy
  3. // @description Toolkit for YouTube with 130+ options accessible via settings panels. Key features include: tab view, playback speed control, set video quality, export transcripts, prevent autoplay, hide shorts, hide ad slots, disable play on hover, square design, auto-theater mode, number of videos per row, display remaining time—adjusted for playback speed and SponsorBlock segments, persistent progress bar with chapter markers and SponsorBlock support, modify or hide various UI elements, and much more.
  4. // @author Tim Macy
  5. // @license GNU AFFERO GENERAL PUBLIC LICENSE-3.0
  6. // @version 7.7.3.1
  7. // @namespace TimMacy.YouTubeAlchemy
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @match https://*.youtube.com/*
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12. // @run-at document-start
  13. // @noframes
  14. // @homepageURL https://github.com/TimMacy/YouTubeAlchemy
  15. // @supportURL https://github.com/TimMacy/YouTubeAlchemy/issues
  16. // ==/UserScript==
  17.  
  18. /************************************************************************
  19. * *
  20. * Copyright © 2025 Tim Macy *
  21. * GNU Affero General Public License v3.0 *
  22. * Version: 7.7.3.1 - YouTube Alchemy *
  23. * All Rights Reserved. *
  24. * *
  25. * Visit: https://github.com/TimMacy *
  26. * *
  27. ************************************************************************/
  28.  
  29. (async function() {
  30. 'use strict';
  31. // CSS
  32. const styleSheet = document.createElement('style');
  33. styleSheet.textContent = `
  34. /* main CSS */
  35. .CentAnni-overlay {
  36. position: fixed;
  37. display: flex;
  38. z-index: 2053;
  39. left: 0;
  40. top: 0;
  41. width: 100%;
  42. height: 100%;
  43. align-items: center;
  44. justify-content: center;
  45. background-color: rgba(0,0,0,0.5);
  46. backdrop-filter: blur(5px);
  47. }
  48.  
  49. .modal-content {
  50. z-index: 2077;
  51. background-color: rgba(17, 17, 17, 0.8);
  52. padding: 20px 0 20px 20px;
  53. border: 1px solid rgba(255, 255, 255, 0.25);
  54. border-radius: 8px;
  55. width: 420px;
  56. max-height: 90vh;
  57. font-family: "Roboto","Arial",sans-serif;
  58. font-size: 9px;
  59. line-height: 1.2;
  60. color: white;
  61. text-rendering: optimizeLegibility !important;
  62. -webkit-font-smoothing: antialiased !important;
  63. -moz-osx-font-smoothing: grayscale !important;
  64. }
  65.  
  66. #yt-transcript-settings-form {
  67. max-height: calc(90vh - 40px);
  68. overflow-y: auto;
  69. padding-right: 20px;
  70. }
  71.  
  72. .notification {
  73. background:hsl(0,0%,7%);
  74. padding: 20px 30px;
  75. border-radius: 8px;
  76. border: 1px solid hsl(0,0%,18.82%);
  77. max-width: 80%;
  78. text-align: center;
  79. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  80. font-size: 16px;
  81. color: white;
  82. -webkit-user-select: none;
  83. -moz-user-select: none;
  84. -ms-user-select: none;
  85. user-select: none;
  86. }
  87.  
  88. .header-wrapper {
  89. display: grid;
  90. grid-template-columns: 1fr auto 1fr;
  91. align-items: baseline;
  92. justify-content: center;
  93. margin: 0 20px 20px 0;
  94. width: calc(100% - 20px);
  95. }
  96.  
  97. .header {
  98. margin: 0;
  99. padding: 0;
  100. border: 0;
  101. grid-column: 2;
  102. text-align:center;
  103. text-decoration: none;
  104. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  105. font-size: 2.5em;
  106. line-height: 1.1em;
  107. font-weight: 700;
  108. text-overflow: ellipsis;
  109. white-space: normal;
  110. text-shadow: 0 0 20px black;
  111. cursor: pointer;
  112. background: transparent;
  113. display: block;
  114. background-image: linear-gradient(45deg, #FFFFFF, #F2F2F2, #E6E6E6, #D9D9D9, #CCCCCC);
  115. -webkit-background-clip: text;
  116. background-clip: text;
  117. color: transparent;
  118. transition: color .3s ease-in-out;
  119. -webkit-user-select: none;
  120. -moz-user-select: none;
  121. -ms-user-select: none;
  122. user-select: none;
  123. }
  124.  
  125. .header:hover { color: white; }
  126.  
  127. .version-label {
  128. grid-column: 3;
  129. padding: 0;
  130. margin: 0 0 0 5px;
  131. color: ghostwhite;
  132. cursor: default;
  133. opacity: .3;
  134. transition: opacity .5s;
  135. justify-self: start;
  136. max-width: 10ch;
  137. white-space: nowrap;
  138. overflow: hidden;
  139. text-overflow: ellipsis;
  140. }
  141.  
  142. .header:hover + .version-label {
  143. opacity: 1;
  144. }
  145.  
  146. .label-style-settings {
  147. display: block;
  148. margin-bottom: 5px;
  149. font-family: "Roboto","Arial",sans-serif;
  150. font-size: 1.4em;
  151. line-height: 1.5em;
  152. font-weight: 500;
  153. }
  154.  
  155. .reset-prompt-text {
  156. display: inline-block;
  157. float: right;
  158. cursor: pointer;
  159. margin: 4px 10px 0 0;
  160. font-size: inherit;
  161. font-weight: 400;
  162. line-height: inherit;
  163. color: ghostwhite;
  164. }
  165.  
  166. .reset-prompt-text:hover { color: red; }
  167. .reset-prompt-text:active { color: magenta; }
  168.  
  169. .label-NotebookLM { color: hsl(134, 61%, 40%); }
  170. .label-ChatGPT { color: hsl(217, 91%, 59%); }
  171. .label-download { color: hsl(359, 88%, 57%); }
  172. .label-settings { color: hsl(0, 0%, 100%); }
  173. .input-field-targetNotebookLMUrl:focus { border: 1px solid hsl(134, 61%, 40%); }
  174. .input-field-targetChatGPTUrl:focus { border: 1px solid hsl(217, 91%, 59%); }
  175. .buttonIconNotebookLM-input-field:focus { border: 1px solid hsl(134, 61%, 40%); }
  176. .buttonIconChatGPT-input-field:focus { border: 1px solid hsl(217, 91%, 59%); }
  177. .buttonIconDownload-input-field:focus { border: 1px solid hsl(359, 88%, 57%); }
  178.  
  179. .buttonIconSettings-input-field:focus,
  180. .links-header-container input:focus,
  181. .sidebar-container input:focus,
  182. #custom-css-form .select-file-naming:focus,
  183. #custom-css-form .dropdown-list {
  184. border: 1px solid hsl(0, 0%, 100%);
  185. }
  186.  
  187. .input-field-targetNotebookLMUrl:hover,
  188. .input-field-targetChatGPTUrl:hover,
  189. .buttonIconNotebookLM-input-field:hover,
  190. .buttonIconChatGPT-input-field:hover,
  191. .buttonIconDownload-input-field:hover,
  192. .buttonIconSettings-input-field:hover,
  193. .select-file-naming:hover,
  194. .input-field-url:hover,
  195. .chatgpt-prompt-textarea:hover
  196. { background-color: hsl(0, 0%, 10.37%); }
  197.  
  198. .btn-style-settings {
  199. padding: 5px 10px;
  200. cursor: pointer;
  201. color: whitesmoke;
  202. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  203. font-size: 1.4em;
  204. line-height: 1.5em;
  205. font-weight: 400;
  206. background-color: hsl(0, 0%, 7%);
  207. border: 1px solid hsl(0, 0%, 18.82%);
  208. border-radius: 2px;
  209. transition: all 0.2s ease-out;
  210. }
  211.  
  212. .btn-style-settings:hover { color: white; background-color: hsl(0, 0%, 25%); border-color: transparent; }
  213. .btn-style-settings:active { color: white; background-color: hsl(0, 0%, 35%); border-color: hsl(0, 0%, 45%); }
  214. .button-icons { display: block; font-family: "Roboto","Arial",sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; }
  215. .icons-container { display: flex; justify-content: space-between; margin-bottom: 20px; }
  216. .container-button { display: flex; flex-direction: column; align-items: center; margin: 5px 0 0 0; }
  217.  
  218. .button-icons.features-text {
  219. margin: 20px 0 -5px 0;
  220. font-size: 1.7em;
  221. display: flex;
  222. justify-content: center;
  223. }
  224.  
  225. .container-button-input {
  226. width: 80px;
  227. padding: 8px;
  228. text-align: center;
  229. color: ghostwhite;
  230. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  231. font-size: 2em;
  232. line-height: 1.5em;
  233. font-weight: 400;
  234. transition: all .5s ease-in-out;
  235. outline: none;
  236. background-color: hsl(0,0%,7%);
  237. border: 1px solid hsl(0,0%,18.82%);
  238. border-radius: 1px;
  239. box-sizing: border-box;
  240. }
  241.  
  242. .container-button-label {
  243. margin-top: 5px;
  244. text-align: center;
  245. font-family: "Roboto","Arial",sans-serif;
  246. font-size: 1.4em;
  247. line-height: 1.5em;
  248. font-weight: 500;
  249. }
  250.  
  251. .container-button-input:focus { color: white; background-color: hsl(0, 0%, 10.37%); border-radius: 2px; }
  252. .spacer-5 { height: 5px; }
  253. .spacer-10 { height: 10px; }
  254. .spacer-15 { height: 15px; }
  255. .spacer-20 { height: 20px; }
  256.  
  257. .copyright {
  258. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  259. font-size: 1.4em;
  260. line-height: 1.5em;
  261. font-weight: 500;
  262. color: white;
  263. text-decoration: none;
  264. transition: color 0.2s ease-in-out;
  265. }
  266.  
  267. .copyright:hover { color: #369eff; }
  268. .url-container { margin-bottom: 10px; }
  269.  
  270. .input-field-url {
  271. width: 100%;
  272. padding: 8px;
  273. color: ghostwhite;
  274. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  275. font-size: 1.4em;
  276. line-height: 1.5em;
  277. font-weight: 400;
  278. transition: all .5s ease-in-out;
  279. outline: none;
  280. background-color:hsl(0,0%,7%);
  281. border: 1px solid hsl(0,0%,18.82%);
  282. border-radius: 1px;
  283. box-sizing: border-box;
  284. }
  285.  
  286. .input-field-url:focus { color: white; background-color: hsl(0, 0%, 10.37%); border-radius: 2px; }
  287. .file-naming-container { position: relative; margin-bottom: 20px; }
  288.  
  289. .select-file-naming {
  290. width: 100%;
  291. padding: 8px;
  292. cursor:pointer;
  293. color: ghostwhite;
  294. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  295. font-size: 1.4em;
  296. line-height: 1.5em;
  297. font-weight: 400;
  298. transition: all .5s ease-in-out;
  299. outline: none;
  300. appearance: none;
  301. -webkit-appearance: none;
  302. background-color:hsl(0,0%,7%);
  303. border: 1px solid hsl(0,0%,18.82%);
  304. border-radius: 1px;
  305. box-sizing: border-box;
  306.  
  307. background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24" focusable="false" aria-hidden="true" style="pointer-events: none; display: inherit; width: 100%; height: 100%;" fill="ghostwhite"%3E%3Cpath d="m18 9.28-6.35 6.35-6.37-6.35.72-.71 5.64 5.65 5.65-5.65z"%3E%3C/path%3E%3C/svg%3E');
  308. background-repeat: no-repeat;
  309. background-position: right 10px center;
  310. background-size: 20px;
  311.  
  312. -webkit-user-select: none;
  313. -moz-user-select: none;
  314. -ms-user-select: none;]
  315. user-select: none;
  316. }
  317.  
  318. .hidden-select {
  319. position: absolute;
  320. visibility: hidden;
  321. opacity: 0;
  322. pointer-events: none;
  323. width: 100%;
  324. height: 100%;
  325. top: 0;
  326. left: 0;
  327. }
  328.  
  329. .dropdown-list {
  330. visibility: hidden;
  331. opacity: 0;
  332. position: absolute;
  333. z-index: 2100;
  334. top: 115%;
  335. left: 0;
  336. width: 100%;
  337. max-height: 60vh;
  338. overflow-y: auto;
  339. background-color:hsl(0,0%,7%);
  340. border: 1px solid hsl(359,88%,57%);
  341. border-radius: 1px 1px 8px 8px;
  342. box-sizing: border-box;
  343. transition: opacity .5s ease-in-out, transform .5s ease-in-out;
  344. transform: translateY(-10px);
  345. }
  346.  
  347. .dropdown-list.show {
  348. visibility: visible;
  349. opacity: 1;
  350. transform: translateY(0);
  351. }
  352.  
  353. .dropdown-item {
  354. padding: 15px;
  355. cursor: pointer;
  356. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  357. font-size: 1.47em;
  358. line-height: 1em;
  359. font-weight: 400;
  360. color: lightgray;
  361. position: relative;
  362. transition: background-color .3s;
  363. padding-left: 1.6em;
  364. }
  365.  
  366. .dropdown-item:hover {
  367. color: ghostwhite;
  368. background-color: rgba(255, 255, 255, .05);
  369. }
  370.  
  371. .dropdown-item:hover::before {
  372. color: ghostwhite;
  373. font-weight: 600;
  374. }
  375.  
  376. .dropdown-item-selected {
  377. color: hsl(359,88%,57%);
  378. font-weight: 600;
  379. }
  380.  
  381. .dropdown-item-selected::before {
  382. content: '✓';
  383. position: absolute;
  384. left: 6px;
  385. color: hsl(359,88%,57%);
  386. }
  387.  
  388. .select-file-naming:focus {
  389. color: white;
  390. background-color: hsl(0, 0%, 10.37%);
  391. border-radius: 2px;
  392. border-color: hsl(359, 88%, 57%);
  393. }
  394.  
  395. .checkbox-label,
  396. .number-input-label span {
  397. display: flex;
  398. align-items: center;
  399. font-family: "Roboto","Arial",sans-serif;
  400. font-size: 1.4em;
  401. line-height: 1.5em;
  402. font-weight: 500;
  403. cursor: pointer;
  404. text-decoration: none;
  405. -webkit-user-select: none;
  406. -moz-user-select: none;
  407. -ms-user-select: none;
  408. user-select: none;
  409. }
  410.  
  411. .checkbox-container { margin-bottom: 5px; }
  412. .checkbox-label:hover { text-decoration: underline; }
  413. .checkbox-field { margin-right: 10px; }
  414.  
  415. .CentAnni-info-text {
  416. color: rgba(175, 175, 175, .9);
  417. font-family: "Roboto","Arial",sans-serif;
  418. font-size: 1.2em;
  419. line-height: 1.5em;
  420. font-weight: 400;
  421. display: block;
  422. margin:-5px 0px 5px 24px;
  423. pointer-events: none;
  424. cursor: default;
  425. }
  426.  
  427. .extra-button-container {
  428. display: flex;
  429. justify-content: center;
  430. gap: 5%;
  431. margin: 20px 0;
  432. }
  433.  
  434. .chatgpt-prompt-textarea {
  435. width: 100%;
  436. padding: 8px;
  437. height: 65px;
  438. transition: all .7s ease-in-out;
  439. outline: none;
  440. resize: none;
  441. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  442. font-size: 1.4em;
  443. line-height: 1.5em;
  444. font-weight: 400;
  445. color: ghostwhite;
  446. background-color:hsl(0,0%,7%);
  447. border: 1px solid hsl(0,0%,18.82%);
  448. border-radius: 1px;
  449. box-sizing: border-box;
  450. }
  451.  
  452. .chatgpt-prompt-textarea:focus {
  453. height: 569px;
  454. color: white;
  455. background-color: hsl(0, 0%, 10.37%);
  456. border: 1px solid hsl(217, 91%, 59%);
  457. border-radius: 2px;
  458. }
  459.  
  460. .button-container-end {
  461. display: flex;
  462. flex-direction: column;
  463. gap: 10px;
  464. margin-top: 20px;
  465. padding-top: 10px;
  466. border: none;
  467. border-top: solid 1px transparent;
  468. border-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, .5), rgba(255, 255, 255, 0));
  469. border-image-slice: 1;
  470. -webkit-user-select: none;
  471. -moz-user-select: none;
  472. -ms-user-select: none;
  473. user-select: none;
  474. }
  475.  
  476. .button-container-backup {
  477. display: flex;
  478. justify-content: end;
  479. gap: 23.5px;
  480. }
  481.  
  482. .button-container-settings {
  483. display: flex;
  484. align-items: center;
  485. justify-content: end;
  486. gap: 10px;
  487.  
  488. }
  489.  
  490. #voice-search-button.ytd-masthead {
  491. margin: 0 12px;
  492. }
  493.  
  494. #masthead-skeleton-icons {
  495. display: none !important;
  496. }
  497.  
  498. .button-wrapper {
  499. position: relative;
  500. margin-right: 8px;
  501. display: flex;
  502. background-color: transparent;
  503. text-rendering: optimizeLegibility !important;
  504. -webkit-font-smoothing: antialiased !important;
  505. -moz-osx-font-smoothing: grayscale !important;
  506. }
  507.  
  508. .button-wrapper:not(:has(.button-style-settings)):hover { background-color: rgba(255, 255, 255, 0.1); border-radius: 24px; }
  509. .button-wrapper:not(:has(.button-style-settings)):active { background-color: rgba(255, 255, 255, 0.2); border-radius: 24px; }
  510.  
  511. .button-style {
  512. width: 40px;
  513. height: 40px;
  514. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  515. font-size: 24px;
  516. display: inline-block;
  517. position: relative;
  518. box-sizing: border-box;
  519. vertical-align: middle;
  520. color: white;
  521. outline: none;
  522. background: transparent;
  523. margin: 0;
  524. border: none;
  525. padding: 0;
  526. cursor: pointer;
  527. -webkit-tap-highlight-color: rgba(0,0,0,0);
  528. -webkit-tap-highlight-color: transparent;
  529. }
  530.  
  531. .button-style-settings { width: 10px; color: rgb(170, 170, 170); }
  532. .button-style-settings:hover { color: white; }
  533.  
  534. .button-tooltip {
  535. visibility: hidden;
  536. background-color: black;
  537. color: white;
  538. text-align: center;
  539. border-radius: 2px;
  540. padding: 6px 8px;
  541. position: absolute;
  542. z-index: 2053;
  543. top: 120%;
  544. left: 50%;
  545. transform: translateX(-50%);
  546. opacity: 0;
  547. white-space: nowrap;
  548. font-size: 12px;
  549. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  550. border-top: solid 1px white;
  551. border-bottom: solid 1px black;
  552. border-left: solid 1px transparent;
  553. border-right: solid 1px transparent;
  554. border-image: linear-gradient(to bottom, white, black);
  555. border-image-slice: 1;
  556. }
  557.  
  558. .button-tooltip-arrow {
  559. position: absolute;
  560. top: -5px;
  561. left: 50%;
  562. transform: translateX(-50%) rotate(45deg);
  563. width: 10px;
  564. height: 10px;
  565. background: linear-gradient(135deg, white 0%, white 50%, black 50%, black 100%);
  566. z-index: -1;
  567. }
  568.  
  569. .CentAnni-remaining-time-container {
  570. position: relative;
  571. display: block;
  572. height: 0;
  573. top: 4px;
  574. text-align: right;
  575. font-family: "Roboto", "Arial", sans-serif;
  576. font-size: 1.4rem;
  577. font-weight: 500;
  578. line-height: 1em;
  579. color: var(--yt-spec-text-primary);
  580. pointer-events: auto;
  581. cursor: default;
  582. text-rendering: optimizeLegibility !important;
  583. -webkit-font-smoothing: antialiased !important;
  584. -moz-osx-font-smoothing: grayscale !important;
  585. }
  586.  
  587. .CentAnni-remaining-time-container.live,
  588. #movie_player .CentAnni-remaining-time-container.live {
  589. display: none;
  590. pointer-events: none;
  591. cursor: default;
  592. }
  593.  
  594. #movie_player .CentAnni-remaining-time-container {
  595. position: absolute;
  596. display: none;
  597. z-index: 2053;
  598. bottom: 15px;
  599. left: 50%;
  600. transform: translateX(-50%);
  601. font-weight: 800 !important;
  602. font-size: 109%;
  603. vertical-align: top;
  604. white-space: nowrap;
  605. line-height: 53px;
  606. color: ghostwhite;
  607. text-shadow: black 0 0 3px !important;
  608. }
  609.  
  610. .ytp-autohide .ytp-chrome-bottom .CentAnni-remaining-time-container {
  611. display: inline-block !important;
  612. }
  613.  
  614. .transcript-preload {
  615. position: fixed !important;
  616. top: var(--ytd-toolbar-height) !important;
  617. left: 50% !important;
  618. transform: translateX(-50%) !important;
  619. z-index: -1 !important;
  620. opacity: 0 !important;
  621. pointer-events: none !important;
  622. }
  623.  
  624. .notification-error {
  625. z-index: 2053;
  626. box-shadow: none;
  627. text-decoration: none;
  628. display: inline-block;
  629. background-color: hsl(0, 0%, 7%);
  630. padding: 10px 12px;
  631. margin: 0 8px;
  632. border: 1px solid hsl(0, 0%, 18.82%);
  633. border-radius: 5px;
  634. pointer-events: none;
  635. cursor: default;
  636. font-family: 'Roboto', Arial, sans-serif;
  637. color: rgba(255, 255, 255, 0.5);
  638. text-align: center;
  639. font-weight: 500;
  640. font-size: 14px;
  641. text-rendering: optimizeLegibility !important;
  642. -webkit-font-smoothing: antialiased !important;
  643. -moz-osx-font-smoothing: grayscale !important;
  644. }
  645.  
  646. #CentAnni-playback-speed-popup {
  647. position: fixed;
  648. top: var(--ytd-masthead-height,var(--ytd-toolbar-height));
  649. left: 50%;
  650. transform: translateX(-50%);
  651. padding: 8px 16px;
  652. background:hsl(0,0%,7%);
  653. border-radius: 2px;
  654. border: 1px solid hsl(0,0%,18.82%);
  655. color: whitesmoke;
  656. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  657. font-size: 2.3rem;
  658. font-weight: 600;
  659. text-align: center;
  660. z-index: 2500;
  661. transition: opacity .3s ease;
  662. opacity: 0;
  663. -webkit-user-select: none;
  664. -moz-user-select: none;
  665. -ms-user-select: none;
  666. user-select: none;
  667. text-rendering: optimizeLegibility !important;
  668. -webkit-font-smoothing: antialiased !important;
  669. -moz-osx-font-smoothing: grayscale !important;
  670. }
  671.  
  672. #CentAnni-playback-speed-popup.active {
  673. opacity: 1;
  674. }
  675.  
  676. .loading span::before {
  677. content: "Transcript Is Loading";
  678. position: absolute;
  679. inset: initial;
  680. color: rgba(255, 250, 250, .86);
  681. opacity: 0;
  682. -webkit-animation: pulse 1.5s infinite;
  683. animation: pulse 1.5s infinite;
  684. text-rendering: optimizeLegibility !important;
  685. -webkit-font-smoothing: antialiased !important;
  686. -moz-osx-font-smoothing: grayscale !important;
  687. }
  688.  
  689. @-webkit-keyframes pulse {
  690. 0% { opacity: 0; }
  691. 50% { opacity: .71; }
  692. 100% { opacity: 0; }
  693. }
  694.  
  695. @keyframes pulse {
  696. 0% { opacity: 0; }
  697. 50% { opacity: .71; }
  698. 100% { opacity: 0; }
  699. }
  700.  
  701. .buttons-left {
  702. font-family: "Roboto", "Arial", sans-serif;
  703. font-size: 14px;
  704. font-weight: 500;
  705. line-height: 1em;
  706. display: inline-block;
  707. position: relative;
  708. color: ghostwhite;
  709. text-decoration: none;
  710. cursor: pointer;
  711. margin: 0 8px;
  712. outline: none;
  713. background: transparent;
  714. border: none;
  715. text-align: center;
  716. text-rendering: optimizeLegibility !important;
  717. -webkit-font-smoothing: antialiased !important;
  718. -moz-osx-font-smoothing: grayscale !important;
  719. }
  720.  
  721. .buttons-left:hover { color: #ff0000 !important; }
  722. .buttons-left:active { color:rgb(200, 25, 25) !important; }
  723.  
  724. .sub-panel-overlay {
  725. position: fixed;
  726. z-index: 2100;
  727. left: 0;
  728. top: 0;
  729. width: 100%;
  730. height: 100%;
  731. display: none;
  732. background-color: rgba(0,0,0,0.5);
  733. justify-content: center;
  734. align-items: center;
  735. backdrop-filter: blur(1px);
  736. }
  737.  
  738. .sub-panel-overlay.active {
  739. display: flex;
  740. }
  741.  
  742. .sub-panel {
  743. z-index: 2177;
  744. background-color: rgba(17, 17, 17, 0.8);
  745. padding: 20px;
  746. border: 1px solid rgba(255, 255, 255, 0.25);
  747. border-radius: 8px;
  748. width: 50vw;
  749. max-width: 70vw;
  750. max-height: 90vh;
  751. position: relative;
  752. display: flex;
  753. flex-direction: column;
  754. overflow-y: auto;
  755. color: whitesmoke;
  756. text-rendering: optimizeLegibility !important;
  757. -webkit-font-smoothing: antialiased !important;
  758. -moz-osx-font-smoothing: grayscale !important;
  759. }
  760.  
  761. .sub-panel button {
  762. position: sticky;
  763. top: 0;
  764. align-self: flex-end;
  765. box-shadow: 0 0 20px 10px black;
  766. }
  767.  
  768. .sub-panel-header {
  769. margin: -24px 60px 20px 0px;
  770. padding: 0px 0px 0px 0px;
  771. border: 0;
  772. text-align: left;
  773. text-decoration: none;
  774. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  775. font-size: 2em;
  776. line-height: 1em;
  777. font-weight: 700;
  778. text-overflow: ellipsis;
  779. white-space: normal;
  780. text-shadow: 0 0 30px 20px black;
  781. cursor: auto;
  782. color: white;
  783. align-self: left;
  784. position: relative;
  785. z-index: 2180;
  786. }
  787.  
  788. #links-in-header-form .CentAnni-info-text {
  789. margin: -10px 80px 20px 0px;
  790. }
  791.  
  792. .links-header-container {
  793. display: flex;
  794. align-items: center;
  795. gap: 20px;
  796. }
  797.  
  798. .links-header-container label {
  799. color: whitesmoke;
  800. }
  801.  
  802. .links-header-container .url-container:first-child {
  803. flex: 1;
  804. }
  805.  
  806. .links-header-container .url-container:last-child {
  807. flex: 2;
  808. }
  809.  
  810. .sidebar-container {
  811. display: flex;
  812. align-items: center;
  813. margin: 10px 0 0 0;
  814. color: whitesmoke;
  815. justify-content: flex-start;
  816. gap: 20px;
  817. }
  818.  
  819. .sidebar-container .checkbox-container {
  820. margin-bottom: 0 !important;
  821. flex: 1;
  822. }
  823.  
  824. .sidebar-container .url-container {
  825. flex: 2;
  826. }
  827.  
  828. .playback-speed-container {
  829. display: flex;
  830. gap: 10px;
  831. margin: 10px 0;
  832. }
  833.  
  834. #custom-css-form .playback-speed-container .checkbox-container {
  835. max-width: calc(55% - 5px);
  836. margin: 0;
  837. align-content: center;
  838. flex: 0 0 auto;
  839. }
  840.  
  841. #custom-css-form .playback-speed-container .number-input-container {
  842. max-width: calc(45% - 5px);
  843. margin: 0;
  844. align-self: center;
  845. flex: 1 0 auto;
  846. }
  847.  
  848. #custom-css-form .playback-speed-container .number-input-container .number-input-field {
  849. width: 5ch;
  850. }
  851.  
  852. #custom-css-form .playback-speed-container .number-input-label {
  853. display: flex;
  854. }
  855.  
  856. #color-code-videos-form .checkbox-container { margin: 20px 0 0 0; }
  857. #color-code-videos-form .label-style-settings {margin: 0; }
  858. #color-code-videos-form > div.videos-old-container > span { margin: 0; }
  859. #color-code-videos-form .CentAnni-info-text { margin: 5px 80px 20px 0px; }
  860. #custom-css-form .checkbox-container { margin: 10px 0; }
  861.  
  862. #custom-css-form .file-naming-container {
  863. max-width: 90%;
  864. margin: 20px 0;
  865. display: flex;
  866. gap: 25px;
  867. align-content: center;
  868. }
  869.  
  870. #custom-css-form .label-style-settings {
  871. margin-bottom: 0;
  872. white-space: nowrap;
  873. align-content: center;
  874. }
  875.  
  876. #custom-css-form .dropdown-item-selected,
  877. #custom-css-form .dropdown-item-selected::before {
  878. color: hsl(0, 0%, 100%);
  879. }
  880.  
  881. input[type="range"] {
  882. -webkit-appearance: none;
  883. appearance: none;
  884. width: 100%;
  885. height: 6px;
  886. background: #ccc;
  887. border-radius: 5px;
  888. outline: none;
  889. }
  890.  
  891. input[type="range"]::-moz-range-thumb,
  892. input[type="range"]::-webkit-slider-thumb {
  893. -webkit-appearance: none;
  894. appearance: none;
  895. width: 16px;
  896. height: 16px;
  897. background: #007bff;
  898. border-radius: 50%;
  899. cursor: pointer;
  900. border: 2px solid #ffffff;
  901. }
  902.  
  903. input[type="range"]::-moz-range-track,
  904. input[type="range"]::-webkit-slider-runnable-track {
  905. background: #007bff;
  906. height: 6px;
  907. border-radius: 5px;
  908. }
  909.  
  910. .videos-old-container {
  911. display: flex;
  912. max-width: 90%;
  913. align-items: center;
  914. gap: 25px;
  915. margin: 20px 0;
  916. }
  917.  
  918. .slider-container {
  919. display: flex;
  920. align-items: center;
  921. gap: 10px;
  922. flex: 1;
  923. }
  924.  
  925. .slider-container input[type="range"] {
  926. flex: 1;
  927. }
  928.  
  929. .videos-colorpicker-container {
  930. display: flex;
  931. flex-direction: column;
  932. align-items: center;
  933. gap: 30px;
  934. }
  935.  
  936. .videos-colorpicker-row {
  937. display: flex;
  938. justify-content: flex-start;
  939. align-items: center;
  940. width: 100%;
  941. gap: 20px;
  942. margin: 0;
  943. }
  944.  
  945. .videos-colorpicker-row span {
  946. text-align: right;
  947. flex: 1;
  948. max-width: 50%;
  949. }
  950.  
  951. .videos-colorpicker-row input {
  952. flex: 1;
  953. margin: 0;
  954. padding: 0;
  955. max-width: 62px;
  956. height: 26px;
  957. cursor: pointer;
  958. background: none;
  959. border: none;
  960. box-shadow: none;
  961. appearance: none;
  962. -webkit-appearance: none;
  963. -moz-appearance: none;
  964. }
  965.  
  966. input[type="color"]::-webkit-color-swatch-wrapper {
  967. border: none;
  968. padding: 0;
  969. }
  970.  
  971. input[type="color"]::-webkit-color-swatch {
  972. border: none;
  973. }
  974.  
  975.  
  976. #custom-css-form .videos-colorpicker-row span {
  977. text-align: left;
  978. flex: initial;
  979. }
  980.  
  981. #custom-css-form .videos-colorpicker-row input {
  982. margin: 0 0 0 -3px;
  983. }
  984.  
  985. #custom-css-form .videos-colorpicker-row {
  986. gap: 10px;
  987. }
  988.  
  989. .number-input-container {
  990. margin: 10px 0;
  991. }
  992.  
  993. .number-input-field {
  994. width: 5ch;
  995. margin: 0 10px 0 0;
  996. align-items: center;
  997. font-size: 1.4em;
  998. line-height: 1.5em;
  999. font-weight: 700;
  1000. cursor: auto;
  1001. text-decoration: none;
  1002. text-align: center;
  1003. display: inline-block;
  1004.  
  1005. }
  1006.  
  1007. .number-input-label span {
  1008. display: initial;
  1009. cursor: auto;
  1010. }
  1011.  
  1012. .selection-color-container {
  1013. margin: 10px 0;
  1014. flex-direction: row;
  1015. }
  1016.  
  1017. .selection-color-container > .checkbox-container {
  1018. margin: 0 !important;
  1019. }
  1020.  
  1021. .selection-color-container > .videos-colorpicker-row {
  1022. width: auto;
  1023. }
  1024.  
  1025. :root:not(:has(:is(
  1026. ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"],
  1027. ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"]
  1028. ))) :is(.CentAnni-tabView-tab[data-tab="tab-4"], .CentAnni-chapter-title, #movie_player .CentAnni-chapter-title),
  1029. :root:not(:has(ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"])) .CentAnni-tabView-tab[data-tab="tab-5"] {
  1030. display: none !important;
  1031. }
  1032.  
  1033. :root:has(ytd-watch-flexy ytd-playlist-panel-renderer[hidden]) .CentAnni-tabView-tab[data-tab="tab-6"] {
  1034. display: none;
  1035. }
  1036.  
  1037. /* progress bar css */
  1038. .CentAnni-progress-bar {
  1039. #progress-bar-bar {
  1040. width: 100%;
  1041. height: 3px;
  1042. background: rgba(255, 255, 255, 0.2);
  1043. position: absolute;
  1044. bottom: 0;
  1045. opacity: 0;
  1046. z-index: 58;
  1047. pointer-events: none;
  1048. }
  1049.  
  1050. #progress-bar-progress, #progress-bar-buffer {
  1051. width: 100%;
  1052. height: 3px;
  1053. transform-origin: 0 0;
  1054. position: absolute;
  1055. }
  1056.  
  1057. #progress-bar-progress {
  1058. background: var(--progressBarColor);
  1059. filter: none;
  1060. z-index: 1;
  1061. }
  1062.  
  1063. .ytp-autohide .ytp-chrome-bottom .ytp-load-progress, .ytp-autohide .ytp-chrome-bottom .ytp-play-progress { display: none !important; }
  1064. .ytp-autohide .ytp-chrome-bottom { opacity: 1 !important; display: block !important; }
  1065. .ytp-autohide .ytp-chrome-bottom .ytp-chrome-controls { opacity: 0 !important; }
  1066. .ad-interrupting #progress-bar-progress { background: transparent; }
  1067. .ytp-ad-persistent-progress-bar-container { display: none; }
  1068. #progress-bar-buffer { background: rgba(255, 255, 255, 0.4); }
  1069.  
  1070. .ytp-autohide #progress-bar-start.active,
  1071. .ytp-autohide #progress-bar-bar.active,
  1072. .ytp-autohide #progress-bar-end.active
  1073. { opacity: 1; }
  1074.  
  1075. .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container {
  1076. bottom: 0px !important;
  1077. opacity: 1 !important;
  1078. height: 4px !important;
  1079. transform: translateX(0px) !important;
  1080. z-index: 100;
  1081. }
  1082.  
  1083. .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar,
  1084. .ytp-autohide .ytp-chrome-bottom .ytp-progress-list {
  1085. background: transparent !important;
  1086. box-shadow: none !important;
  1087. }
  1088.  
  1089. .ytp-autohide .ytp-chrome-bottom .previewbar {
  1090. height: calc(100% + 1px) !important;
  1091. bottom: -1px !important;
  1092. margin-bottom: 0px !important;
  1093. opacity: 1 !important;
  1094. border: none !important;
  1095. box-shadow: none !important;
  1096. will-change: opacity, transform !important;
  1097. }
  1098.  
  1099. .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container:not(.active) .ytp-scrubber-container {
  1100. opacity: 0;
  1101. pointer-events: none;
  1102. }
  1103.  
  1104. #progress-bar-start, #progress-bar-end {
  1105. position: absolute;
  1106. height: 3px;
  1107. width: 12px;
  1108. bottom: 0;
  1109. z-index: 2077;
  1110. opacity: 0;
  1111. pointer-events: none;
  1112. }
  1113.  
  1114. :fullscreen #progress-bar-start, :fullscreen #progress-bar-end { width: 24px; }
  1115. :-webkit-full-screen #progress-bar-start, :-webkit-full-screen #progress-bar-end { width: 24px; }
  1116. .html5-video-player.ytp-fullscreen #progress-bar-start, .html5-video-player.ytp-fullscreen #progress-bar-end { width: 24px !important; }
  1117.  
  1118. #progress-bar-start {
  1119. left: 0;
  1120. background: var(--progressBarColor);
  1121. }
  1122.  
  1123. #progress-bar-end {
  1124. right: 0;
  1125. background: rgba(255, 255, 255, 0.2);
  1126. }
  1127. }
  1128.  
  1129. /* playback speed css */
  1130. .CentAnni-playback-speed {
  1131. #actions.ytd-watch-metadata {
  1132. justify-content: end;
  1133. }
  1134.  
  1135. .yt-spec-button-view-model.style-scope.ytd-menu-renderer {
  1136. display: flex;
  1137. height: 36px;
  1138. width: 36px;
  1139. margin-right: 8px;
  1140. overflow: hidden;
  1141. }
  1142.  
  1143. ytd-menu-renderer[has-items] yt-button-shape.ytd-menu-renderer {
  1144. margin-left: 0 !important;
  1145. }
  1146.  
  1147. ytd-menu-renderer[has-flexible-items][safe-area] .top-level-buttons.ytd-menu-renderer {
  1148. margin-bottom: 0;
  1149. }
  1150.  
  1151. #top-level-buttons-computed > yt-button-view-model > button-view-model > button {
  1152. padding: 0 0 0 12px;
  1153. }
  1154.  
  1155. .CentAnni-playback-control {
  1156. display: flex;
  1157. justify-content: center;
  1158. align-items: center;
  1159. flex: 1;
  1160. margin: 0 8px;
  1161. }
  1162.  
  1163. .CentAnni-playback-speed-icon {
  1164. height: 24px;
  1165. width: 24px;
  1166. padding: 0 4px 0 0;
  1167. opacity: .9;
  1168. }
  1169.  
  1170. .CentAnni-playback-speed-display {
  1171. background: rgba(255,255,255,0.1);
  1172. height: 36px;
  1173. justify-content: center;
  1174. align-items: center;
  1175. cursor: default;
  1176. user-select: none;
  1177. -webkit-user-select: none;
  1178. -moz-user-select: none;
  1179. -ms-user-select: none;
  1180. }
  1181.  
  1182. .CentAnni-playback-speed-button {
  1183. cursor: pointer;
  1184. user-select: none;
  1185. -webkit-user-select: none;
  1186. -moz-user-select: none;
  1187. -ms-user-select: none;
  1188. }
  1189.  
  1190. .CentAnni-playback-speed-button:nth-child(2) {
  1191. border-top-right-radius: 0;
  1192. border-bottom-right-radius: 0;
  1193. }
  1194.  
  1195. .CentAnni-playback-speed-button:last-child {
  1196. border-top-left-radius: 0;
  1197. border-bottom-left-radius: 0;
  1198. }
  1199.  
  1200. .CentAnni-playback-speed-button:active {
  1201. background: rgb(72,72,72) !important;
  1202. }
  1203.  
  1204. .CentAnni-playback-speed-display::before {
  1205. content: "";
  1206. background: rgba(255,255,255,0.2);
  1207. position: initial;
  1208. right: 0;
  1209. top: 6px;
  1210. height: 24px;
  1211. width: 1px;
  1212. margin-right: 10px;
  1213. }
  1214.  
  1215. .CentAnni-playback-speed-display::after {
  1216. content: "";
  1217. background: rgba(255,255,255,0.2);
  1218. position: initial;
  1219. right: 0;
  1220. top: 6px;
  1221. height: 24px;
  1222. width: 1px;
  1223. margin-left: 10px;
  1224. }
  1225.  
  1226. ytd-watch-metadata[flex-menu-enabled] #actions-inner.ytd-watch-metadata {
  1227. width: 10%;
  1228. }
  1229. }
  1230.  
  1231. /* video tab view css */
  1232. .CentAnni-video-tabView {
  1233. .CentAnni-tabView {
  1234. position: relative;
  1235. display: flex;
  1236. flex-direction: column;
  1237. width: var(--ytd-watch-flexy-sidebar-width);
  1238. min-width: var(--ytd-watch-flexy-sidebar-min-width);
  1239. font-family: "Roboto", "Arial", sans-serif;
  1240. margin: 0;
  1241. padding: 0;
  1242. border-radius: 12px 12px 0 0;
  1243. border: 1px solid rgba(255, 255, 255, 0.2);
  1244. overflow: hidden;
  1245. box-sizing: border-box;
  1246. background-color: transparent;
  1247. z-index: 10;
  1248. }
  1249.  
  1250. .CentAnni-tabView:has(.CentAnni-tabView-tab.active[data-tab="tab-2"]) {
  1251. border-radius: 12px;
  1252. }
  1253.  
  1254. .CentAnni-tabView:not(:has(.CentAnni-tabView-tab[data-tab="tab-2"])) ytd-comments#comments {
  1255. display: none;
  1256. }
  1257.  
  1258. .CentAnni-tabView-header {
  1259. display: flex;
  1260. position: relative;
  1261. overflow-x: auto;
  1262. overflow-y: hidden;
  1263. height: 50px;
  1264. padding: 0;
  1265. margin: 0;
  1266. color: var(--yt-spec-text-primary);
  1267. background-color: var(--yt-spec-brand-background-primary);
  1268. z-index: 7;
  1269. }
  1270.  
  1271. .CentAnni-tabView-subheader {
  1272. display: flex;
  1273. flex-direction: row;
  1274. height: 50px;
  1275. font-size: 9px;
  1276. line-height: 1;
  1277. padding: 0 8px;
  1278. align-items: center;
  1279. gap: 8px;
  1280. z-index: 8;
  1281. }
  1282.  
  1283. .CentAnni-tabView-tab {
  1284. align-items: center;
  1285. border: none;
  1286. border-radius: 8px;
  1287. display: inline-flex;
  1288. height: 32px;
  1289. min-width: 12px;
  1290. white-space: nowrap;
  1291. font-family: "Roboto","Arial",sans-serif;
  1292. font-size: 1.4em;
  1293. line-height: 2em;
  1294. font-weight: 500;
  1295. margin: 0;
  1296. background-color: rgba(255,255,255,0.1);
  1297. color: #f1f1f1;
  1298. text-decoration: none;
  1299. padding: 0 12px;
  1300. z-index: 10;
  1301. }
  1302.  
  1303. .CentAnni-tabView-tab:hover {
  1304. background: rgba(255,255,255,0.2);
  1305. border-color: transparent;
  1306. }
  1307.  
  1308. .CentAnni-tabView-tab.active {
  1309. background-color: #f1f1f1;
  1310. color: #0f0f0f;
  1311. }
  1312.  
  1313. .CentAnni-tabView-content {
  1314. overflow-y: auto;
  1315. overflow-x: hidden;
  1316. padding: 0;
  1317. margin: 0;
  1318. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1319. }
  1320.  
  1321. .CentAnni-tabView-content {
  1322. display: none;
  1323. }
  1324.  
  1325. .CentAnni-tabView-content.active {
  1326. display: block !important;
  1327. }
  1328.  
  1329. .CentAnni-tabView-content-hidden {
  1330. display: none;
  1331. opacity: 0;
  1332. visibility: hidden;
  1333. }
  1334.  
  1335. .CentAnni-tabView-content-active {
  1336. display: block !important;
  1337. opacity: 1 !important;
  1338. visibility: visible !important;
  1339. }
  1340.  
  1341. ytd-watch-flexy ytd-playlist-panel-renderer[hidden] {
  1342. display: none !important;
  1343. }
  1344.  
  1345.  
  1346. .CentAnni-tabView-content-nascosta {
  1347. opacity: 0;
  1348. visibility: hidden;
  1349. }
  1350.  
  1351. .CentAnni-tabView-content-attiva {
  1352. opacity: 1 !important;
  1353. visibility: visible !important;
  1354. }
  1355.  
  1356.  
  1357. .CentAnni-tabView-content-block {
  1358. display: block !important;
  1359. }
  1360.  
  1361. .CentAnni-tabView-content-none {
  1362. display: none;
  1363. }
  1364.  
  1365. #tab-2 {
  1366. border-top: 1px solid rgba(255, 255, 255, 0.2);
  1367. }
  1368.  
  1369. #related.style-scope.ytd-watch-flexy {
  1370. position: absolute;
  1371. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1372. width: var(--ytd-watch-flexy-sidebar-width);
  1373. top: 76px;
  1374. left: 0;
  1375. margin: 0;
  1376. padding: 15px 10px 0 10px;
  1377. overflow-y: auto;
  1378. overflow-x: hidden;
  1379. visibility: hidden;
  1380. opacity: 0;
  1381. z-index: 5;
  1382. border: 1px solid rgba(255, 255, 255, 0.2);
  1383. border-top: none;
  1384. box-sizing: border-box;
  1385. border-radius: 0 0 12px 12px;
  1386. }
  1387.  
  1388. ytd-watch-metadata.ytd-watch-flexy {
  1389. margin: 12px 10px 0 10px;
  1390. }
  1391.  
  1392. ytd-watch-flexy ytd-comments {
  1393. margin: 0;
  1394. padding: 0 10px;
  1395. }
  1396.  
  1397. #comments > #sections > #header > ytd-comments-header-renderer > #title > #additional-section {
  1398. margin-left: auto;
  1399. }
  1400.  
  1401. #comments > #sections > #header {
  1402. margin: 0 12px;
  1403. }
  1404.  
  1405. ytd-engagement-panel-section-list-renderer[modern-panels]:not([live-chat-engagement-panel]) {
  1406. border-radius: 0 0 12px 12px;
  1407. }
  1408.  
  1409. #related.style-scope.ytd-watch-flexy[full-bleed-player] {
  1410. top: 30px;
  1411. }
  1412.  
  1413. ytd-watch-flexy #header.style-scope.ytd-engagement-panel-section-list-renderer {
  1414. display: none;
  1415. }
  1416.  
  1417. ytd-engagement-panel-section-list-renderer {
  1418. box-sizing: content-box;
  1419. display: flexbox;
  1420. display: flex;
  1421. flex-direction: column;
  1422. }
  1423.  
  1424. ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"],
  1425. ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] {
  1426. position: absolute;
  1427. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1428. width: var(--ytd-watch-flexy-sidebar-width);
  1429. top: 76px;
  1430. left: 0;
  1431. margin: 0;
  1432. padding: 0;
  1433. overflow-y: auto;
  1434. overflow-x: hidden;
  1435. z-index: 5;
  1436. border: 1px solid rgba(255, 255, 255, 0.2);
  1437. border-top: none;
  1438. box-sizing: border-box;
  1439. }
  1440.  
  1441. ytd-watch-flexy #description.ytd-expandable-video-description-body-renderer {
  1442. padding-right: 10px !important;
  1443. }
  1444.  
  1445. #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy {
  1446. margin-bottom: 0;
  1447. }
  1448.  
  1449. ytd-watch-flexy[flexy] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy,
  1450. ytd-watch-flexy[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy {
  1451. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1452. border: 1px solid rgba(255, 255, 255, 0.2);
  1453. border-top: none;
  1454. box-sizing: border-box;
  1455. }
  1456.  
  1457. ytd-watch-flexy ytd-structured-description-content-renderer[engagement-panel] ytd-video-description-header-renderer.ytd-structured-description-content-renderer {
  1458. display: none;
  1459. }
  1460.  
  1461. ytd-engagement-panel-section-list-renderer ytd-merch-shelf-renderer {
  1462. display: none;
  1463. }
  1464.  
  1465. ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-structured-description"] {
  1466. position: absolute;
  1467. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1468. width: var(--ytd-watch-flexy-sidebar-width);
  1469. top: 76px;
  1470. left: 0;
  1471. margin: 0;
  1472. padding: 10px 0 0 10px;
  1473. overflow-y: auto;
  1474. overflow-x: hidden;
  1475. z-index: 5;
  1476. border: 1px solid rgba(255, 255, 255, 0.2);
  1477. border-top: none;
  1478. box-sizing: border-box;
  1479. }
  1480.  
  1481. ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id^="shopping_panel_for_entry_point_"] {
  1482. display: none;
  1483. }
  1484.  
  1485. ytd-watch-flexy[flexy] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-structured-description],
  1486. ytd-watch-flexy[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-structured-description"] {
  1487. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1488. background-color: black;
  1489. }
  1490.  
  1491. ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"] {
  1492. position: absolute;
  1493. top: 0;
  1494. left: 0;
  1495. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1496. width: var(--ytd-watch-flexy-sidebar-width);
  1497. top: 76px;
  1498. margin: 0;
  1499. padding: 0;
  1500. z-index: 5;
  1501. border: none;
  1502. box-sizing: border-box;
  1503. }
  1504.  
  1505. #body.ytd-transcript-renderer {
  1506. display: none;
  1507. }
  1508.  
  1509. ytd-transcript-renderer {
  1510. flex: 1 1 auto;
  1511. }
  1512.  
  1513. ytd-transcript-segment-list-renderer {
  1514. height: 100%;
  1515. }
  1516.  
  1517. ytd-text-inline-expander[engagement-panel] {
  1518. display: block;
  1519. margin: 0;
  1520. padding: 0;
  1521. border: none;
  1522. border-radius: 0;
  1523. background: transparent;
  1524. padding-right: 10px;
  1525. }
  1526.  
  1527. ytd-expandable-video-description-body-renderer > ytd-text-inline-expander > #snippet {
  1528. max-height: 100% !important;
  1529. }
  1530.  
  1531. #media-lockups > ytd-structured-description-playlist-lockup-renderer > #lockup-container > #playlist-thumbnail {
  1532. width: 100%;
  1533. }
  1534.  
  1535. #items > yt-video-attributes-section-view-model > div > div.yt-video-attributes-section-view-model__video-attributes.yt-video-attributes-section-view-model__scroll-container > div > yt-video-attribute-view-model > div:hover,
  1536. #media-lockups > ytd-structured-description-playlist-lockup-renderer > #lockup-container:hover {
  1537. filter: brightness(1.1);
  1538. }
  1539.  
  1540. ytd-text-inline-expander,
  1541. #navigation-button.ytd-rich-list-header-renderer,
  1542. ytd-expandable-video-description-body-renderer > ytd-expander > tp-yt-paper-button#more.ytd-expander {
  1543. display: none;
  1544. }
  1545.  
  1546. ytd-expandable-video-description-body-renderer[engagement-panel]:not([shorts-panel]) ytd-expander.ytd-expandable-video-description-body-renderer {
  1547. border-radius: 0;
  1548. padding: 0;
  1549. background: transparent;
  1550. }
  1551.  
  1552. ytd-structured-description-content-renderer[engagement-panel] ytd-expandable-video-description-body-renderer.ytd-structured-description-content-renderer {
  1553. padding: 0;
  1554. }
  1555.  
  1556. ytd-watch-flexy ytd-engagement-panel-section-list-renderer[enable-anchored-panel][target-id="engagement-panel-structured-description"] #content.ytd-engagement-panel-section-list-renderer .ytd-engagement-panel-section-list-renderer:first-child,
  1557. ytd-watch-flexy ytd-structured-description-content-renderer[engagement-panel] #items.ytd-structured-description-content-renderer {
  1558. padding: 0;
  1559. }
  1560.  
  1561. ytd-video-description-transcript-section-renderer {
  1562. position: fixed;
  1563. bottom: 0;
  1564. left: 0;
  1565. z-index: -9999;
  1566. pointer-events: none;
  1567. opacity: 0;
  1568. }
  1569.  
  1570. ytd-video-description-infocards-section-renderer[engagement-panel] #social-links.ytd-video-description-infocards-section-renderer {
  1571. margin: 0 0 16px 0;
  1572. padding: 0;
  1573. }
  1574.  
  1575. ytd-structured-description-content-renderer[engagement-panel] ytd-video-description-infocards-section-renderer.ytd-structured-description-content-renderer {
  1576. padding: 16px 0 0 0;
  1577. }
  1578.  
  1579. #infocards-section > ytd-compact-infocard-renderer:last-of-type {
  1580. margin-bottom: 0;
  1581. }
  1582.  
  1583. #bottom-row > #description {
  1584. cursor: default;
  1585. pointer-events: none;
  1586. }
  1587.  
  1588. #content > #description a:hover,
  1589. #snippet > #snippet-text > yt-attributed-string a:hover {
  1590. text-decoration: underline;
  1591. }
  1592.  
  1593. ytd-compact-video-renderer:hover,
  1594. ytd-video-description-course-section-renderer > #topic-link:hover,
  1595. #infocards-section > ytd-compact-infocard-renderer > #content:hover,
  1596. #items > ytd-video-description-infocards-section-renderer > #header:hover,
  1597. ytd-watch-card-compact-video-renderer.ytd-vertical-watch-card-list-renderer:not([is-condensed]):hover,
  1598. #always-shown > ytd-rich-metadata-row-renderer > #contents > ytd-rich-metadata-renderer > #endpoint-link:hover,
  1599. #items > ytd-video-description-infocards-section-renderer > #infocards-section > ytd-compact-infocard-renderer > #content:hover,
  1600. ytd-playlist-panel-renderer h3 yt-formatted-string[has-link-only_]:not([force-default-style]) a.yt-simple-endpoint.yt-formatted-string:hover {
  1601. background: var(--yt-spec-badge-chip-background);
  1602. }
  1603.  
  1604. ytd-playlist-panel-renderer #publisher-container yt-formatted-string[has-link-only_]:not([force-default-style]) a.yt-simple-endpoint.yt-formatted-string:hover {
  1605. color: red;
  1606. }
  1607.  
  1608. #playlist-actions.ytd-playlist-panel-renderer {
  1609. cursor: default;
  1610. }
  1611.  
  1612. ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a {
  1613. cursor: pointer;
  1614. pointer-events: all;
  1615. color:var(--yt-endpoint-visited-color);
  1616. }
  1617.  
  1618. ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a:hover {
  1619. color: var(--yt-spec-call-to-action);
  1620. }
  1621.  
  1622. #description > #description-inner > #ytd-watch-info-text > tp-yt-paper-tooltip {
  1623. display: none;
  1624. }
  1625.  
  1626. #description > #description-interaction {
  1627. display: none;
  1628. }
  1629.  
  1630. .yt-animated-icon.lottie-player.style-scope {
  1631. pointer-events: none;
  1632. }
  1633.  
  1634. #description.ytd-watch-metadata {
  1635. background: none;
  1636. }
  1637.  
  1638. ytd-transcript-search-box-renderer {
  1639. margin: 12px 0;
  1640. }
  1641.  
  1642. .CentAnni-info-date {
  1643. margin-left: 6px;
  1644. }
  1645.  
  1646. #ytd-watch-info-text.ytd-watch-metadata {
  1647. height: 18px;
  1648. }
  1649.  
  1650. #middle-row.ytd-watch-metadata {
  1651. padding: 10px 0;
  1652. }
  1653.  
  1654. .content.style-scope.ytd-info-panel-content-renderer {
  1655. padding: 10px 16px;
  1656. }
  1657.  
  1658. #description-inner.ytd-watch-metadata {
  1659. margin: 0px 12px 0px 52px;
  1660. }
  1661.  
  1662. #view-count.ytd-watch-info-text, #date-text.ytd-watch-info-text {
  1663. align-items: center;
  1664. }
  1665.  
  1666. ytd-watch-info-text:not([detailed]) #info.ytd-watch-info-text {
  1667. align-content: center;
  1668. }
  1669.  
  1670. #bottom-row.ytd-watch-metadata {
  1671. flex-direction: column;
  1672. }
  1673.  
  1674. .ytVideoMetadataCarouselViewModelHost {
  1675. flex-direction: row;
  1676. padding: 12px;
  1677. height: 100%;
  1678. margin-bottom: 24px;
  1679. gap: 20px;
  1680. align-items: center;
  1681. }
  1682.  
  1683. .ytVideoMetadataCarouselViewModelCarouselContainer {
  1684. margin-top: 0;
  1685. }
  1686.  
  1687. /* live chat adjustments */
  1688. ytd-live-chat-frame[modern-buttons][collapsed] {
  1689. display: none;
  1690. }
  1691.  
  1692. #columns > #secondary > #secondary-inner > #chat-container {
  1693. top: 24px;
  1694. position: absolute;
  1695. width: var(--ytd-watch-flexy-sidebar-width);
  1696. min-width: var(--ytd-watch-flexy-sidebar-min-width);
  1697. }
  1698.  
  1699. ytd-watch-flexy[flexy]:not([fixed-panels]) #chat.ytd-watch-flexy:not([collapsed]) {
  1700. height: calc(90vh - 10px);
  1701. border: 1px solid rgb(51, 51, 51);
  1702. }
  1703.  
  1704. #chat.ytd-watch-flexy {
  1705. margin-bottom: 0;
  1706. }
  1707.  
  1708. /* theater mode active */
  1709. ytd-watch-flexy[theater] #playlist,
  1710. ytd-watch-flexy[theater] ytd-comments,
  1711. ytd-watch-flexy[theater] #related.style-scope.ytd-watch-flexy,
  1712. ytd-watch-flexy[theater] ytd-playlist-panel-renderer[collapsible] .header.ytd-playlist-panel-renderer,
  1713. ytd-watch-flexy[theater] ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-searchable-transcript],
  1714. ytd-watch-flexy[theater] ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-structured-description],
  1715. ytd-watch-flexy[theater] ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-auto-chapters],
  1716. ytd-watch-flexy[theater] ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-description-chapters],
  1717. ytd-watch-flexy[theater][flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy,
  1718. ytd-watch-flexy[theater][flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-structured-description"] {
  1719. height: 0;
  1720. padding-top: 0;
  1721. padding-bottom: 0;
  1722. margin-top: 0;
  1723. margin-bottom: 0;
  1724. opacity: 0;
  1725. visibility: hidden;
  1726. z-index: -1;
  1727. pointer-events: none;
  1728. }
  1729.  
  1730. ytd-watch-flexy[theater][flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-clip-create"] {
  1731. display: none;
  1732. }
  1733.  
  1734. ytd-watch-flexy[theater] #donation-shelf {
  1735. display: none !important;
  1736. }
  1737.  
  1738. ytd-watch-flexy[theater] .CentAnni-tabView-content {
  1739. display: none !important;
  1740. }
  1741.  
  1742. ytd-watch-flexy[theater][cinematics-enabled] #secondary.ytd-watch-flexy {
  1743. align-content: center;
  1744. }
  1745.  
  1746. ytd-watch-flexy[theater][is-two-columns_][full-bleed-player] #secondary.ytd-watch-flexy {
  1747. margin-top: 34.5px !important;
  1748. display: flex;
  1749. justify-content: center;
  1750. align-items: flex-start;
  1751. }
  1752.  
  1753. ytd-watch-flexy[theater] .CentAnni-tabView {
  1754. border-radius: 25px;
  1755. }
  1756.  
  1757. ytd-watch-flexy[theater] .CentAnni-tabView-tab {
  1758. border-radius: 18px;
  1759. }
  1760.  
  1761. ytd-watch-flexy[is-two-columns_][theater] #columns {
  1762. max-height: calc(100vh - 56.25vw - var(--ytd-masthead-height,var(--ytd-toolbar-height)));
  1763. min-height: calc(169px - var(--ytd-masthead-height, var(--ytd-toolbar-height)));
  1764. }
  1765.  
  1766. ytd-watch-flexy[theater] #primary {
  1767. overflow-x: hidden;
  1768. }
  1769.  
  1770. #donation-shelf {
  1771. display: none;
  1772. opacity: 0;
  1773. visibility: hidden;
  1774. }
  1775.  
  1776. #donation-shelf.ytd-watch-flexy ytd-donation-shelf-renderer.ytd-watch-flexy {
  1777. margin-bottom: 0;
  1778. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1779. overflow-y: auto;
  1780. }
  1781.  
  1782. ytd-donation-shelf-renderer[modern-panels] {
  1783. border-radius: 0px 0px 12px 12px;
  1784. }
  1785.  
  1786. ytd-donation-shelf-renderer {
  1787. border-top: none;
  1788. }
  1789.  
  1790. #collapse-controls-section {
  1791. display: none;
  1792. }
  1793.  
  1794. ytd-watch-flexy #playlist {
  1795. position: absolute;
  1796. margin: 0;
  1797. padding: 0;
  1798. top: 76px;
  1799. left: 0;
  1800. width: var(--ytd-watch-flexy-sidebar-width);
  1801. z-index: 5;
  1802. display: none;
  1803. opacity: 0;
  1804. visibility: hidden;
  1805. }
  1806.  
  1807. ytd-playlist-panel-renderer[modern-panels]:not([within-miniplayer]) #container.ytd-playlist-panel-renderer {
  1808. border-radius: 0 0 12px 12px;
  1809. }
  1810.  
  1811. #playlist.ytd-watch-flexy {
  1812. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px) !important;
  1813. margin-bottom: 0;
  1814. }
  1815.  
  1816. ytd-watch-flexy[flexy] #playlist.ytd-watch-flexy,
  1817. ytd-playlist-panel-renderer[js-panel-height] #container.ytd-playlist-panel-renderer,
  1818. ytd-watch-flexy[default-layout]:not([no-top-margin]):not([reduced-top-margin]) #secondary.ytd-watch-flexy {
  1819. max-height: calc(100vh - var(--ytd-masthead-height,var(--ytd-toolbar-height)) - 2 * var(--ytd-margin-6x) - 52px);
  1820. }
  1821.  
  1822. #container.ytd-playlist-panel-renderer {
  1823. border-top: none;
  1824. }
  1825.  
  1826. ytd-playlist-panel-renderer[collapsible] .header.ytd-playlist-panel-renderer {
  1827. padding: 12px 0 0 25px;
  1828. margin-right: 0;
  1829. }
  1830.  
  1831. #trailing-button.ytd-playlist-panel-renderer {
  1832. display: none;
  1833. }
  1834.  
  1835. .playlist-items.ytd-playlist-panel-renderer {
  1836. padding: 0;
  1837. }
  1838.  
  1839. /* single columns */
  1840. ytd-watch-flexy:not([is-two-columns_]) #columns.ytd-watch-flexy {
  1841. flex-direction: column;
  1842. }
  1843.  
  1844. ytd-watch-flexy:not([is-two-columns_])[flexy] #primary.ytd-watch-flexy {
  1845. flex-basis: auto;
  1846. }
  1847.  
  1848. ytd-watch-flexy:not([is-two-columns_])[theater][full-bleed-player] #secondary.ytd-watch-flexy,
  1849. ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy {
  1850. display: flex;
  1851. margin: 0;
  1852. padding: 0;
  1853. justify-content: center;
  1854. align-items: center;
  1855. width: 100%;
  1856. }
  1857.  
  1858. ytd-watch-flexy:not([is-two-columns_]) .CentAnni-tabView {
  1859. margin-top: 24px;
  1860. width: 90vw;
  1861. }
  1862.  
  1863. ytd-watch-flexy:not([is-two-columns_])[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-structured-description"] {
  1864. max-height: 50vh;
  1865. width: 90vw;
  1866. top: 171px;
  1867. margin: 0;
  1868. background-color: black;
  1869. }
  1870.  
  1871. ytd-watch-flexy:not([is-two-columns_]) ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-structured-description"] {
  1872. align-self: center;
  1873. left: initial;
  1874. }
  1875.  
  1876. ytd-watch-flexy:not([is-two-columns_]) .CentAnni-tabView-content {
  1877. max-height: 50vh;
  1878. }
  1879.  
  1880. ytd-watch-flexy:not([is-two-columns_]) #related.style-scope.ytd-watch-flexy {
  1881. max-height: 50vh;
  1882. width: 90vw;
  1883. top: 171px;
  1884. left: 50%;
  1885. transform: translateX(-50%);
  1886. }
  1887.  
  1888. ytd-watch-flexy:not([is-two-columns_])[default-layout]:not([no-top-margin]):not([reduced-top-margin]) #secondary.ytd-watch-flexy {
  1889. padding: 0;
  1890. }
  1891.  
  1892. ytd-watch-flexy:not([is-two-columns_])[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy {
  1893. max-height: 50vh;
  1894. width: 90vw;
  1895. top: 147px;
  1896. left: 50%;
  1897. transform: translateX(-50%);
  1898. }
  1899.  
  1900. ytd-watch-flexy #columns #below > ytd-watch-metadata #title > ytd-badge-supported-renderer {
  1901. bottom: 0px;
  1902. right: 10px;
  1903. position: absolute;
  1904. cursor: default;
  1905. }
  1906. }
  1907.  
  1908. .CentAnni-tabView-chapters {
  1909. .ytp-chapter-title-chevron,
  1910. ytd-watch-flexy .ytp-chapter-title-prefix {
  1911. display: none;
  1912. }
  1913.  
  1914. ytd-watch-flexy .ytp-chapter-container {
  1915. padding: 0;
  1916. font-size: inherit;
  1917. line-height: inherit;
  1918. }
  1919.  
  1920. ytd-watch-flexy .sponsorChapterText,
  1921. ytd-watch-flexy .ytp-chapter-title-content {
  1922. white-space: normal;
  1923. font-weight: 500;
  1924. }
  1925.  
  1926. #movie_player .sponsorChapterText,
  1927. #movie_player .ytp-chapter-title-content {
  1928. overflow: hidden;
  1929. text-overflow: ellipsis;
  1930. text-wrap: nowrap;
  1931. line-height: 59px;
  1932. }
  1933.  
  1934. .CentAnni-chapter-title {
  1935. position: absolute;
  1936. display: flex;
  1937. flex-direction: row;
  1938. z-index: 2025;
  1939. top: 0;
  1940. right: 0;
  1941. max-width: calc(50% - 26px);
  1942. font-family: -apple-system, "Roboto", "Arial", sans-serif;
  1943. font-size: 1.4rem;
  1944. line-height: 2rem;
  1945. color: var(--yt-spec-text-primary) !important;
  1946. -webkit-user-select: none;
  1947. -moz-user-select: none;
  1948. -ms-user-select: none;
  1949. user-select: none;
  1950. cursor: default;
  1951. }
  1952.  
  1953. .CentAnni-chapter-title span {
  1954. text-wrap: nowrap;
  1955. margin-right: .5ch;
  1956. }
  1957.  
  1958. #movie_player .CentAnni-chapter-title {
  1959. position: absolute;
  1960. display: none;
  1961. flex-direction: row;
  1962. max-width: 50vw;
  1963. overflow: hidden;
  1964. z-index: 2053;
  1965. bottom: 0;
  1966. left: 50%;
  1967. font-weight: 500 !important;
  1968. font-size: 109%;
  1969. vertical-align: top;
  1970. white-space: nowrap;
  1971. line-height: 59px;
  1972. color: ghostwhite !important;
  1973. text-shadow: black 0 0 3px !important;
  1974. }
  1975.  
  1976. .ytp-autohide .ytp-chrome-bottom .CentAnni-chapter-title {
  1977. display: flex !important;
  1978. }
  1979.  
  1980. .CentAnni-chapter-title .ytp-chapter-container.sponsorblock-chapter-visible {
  1981. display: block !important;
  1982. }
  1983.  
  1984. .CentAnni-chapter-title:has(.ytp-chapter-title-content:empty):not(:has(.sponsorChapterText:not(:empty))) {
  1985. display: none;
  1986. }
  1987.  
  1988. :is(
  1989. :has(ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"]),
  1990. :has(ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"])
  1991. ) {
  1992. & ytd-watch-flexy #description > #description-inner {
  1993. width: calc(50% - 26px);
  1994. }
  1995.  
  1996. & ytd-watch-flexy #description > #description-inner #info-container {
  1997. height: 41px;
  1998. }
  1999.  
  2000. & ytd-watch-flexy #description > #description-inner #info span.CentAnni-info-date + span::after {
  2001. content: "";
  2002. display: block;
  2003. height: 0;
  2004. margin-bottom: 5px;
  2005. }
  2006.  
  2007. & ytd-watch-flexy #description > #description-inner #info a.yt-simple-endpoint.bold {
  2008. display: inline-block;
  2009. }
  2010.  
  2011. & ytd-watch-flexy #bottom-row.ytd-watch-metadata {
  2012. height: 50px;
  2013. }
  2014.  
  2015. #movie_player .CentAnni-remaining-time-container {
  2016. left: 25% !important;
  2017. }
  2018. }
  2019. }
  2020.  
  2021.  
  2022. .CentAnni-style-hide-comments-btn {
  2023. ytd-comments#comments,
  2024. .CentAnni-tabView-tab[data-tab="tab-2"] {
  2025. display: none;
  2026. }
  2027. }
  2028.  
  2029. .CentAnni-style-hide-videos-btn {
  2030. #related.style-scope.ytd-watch-flexy,
  2031. .CentAnni-tabView-tab[data-tab="tab-3"] {
  2032. display: none;
  2033. }
  2034. }
  2035.  
  2036. .CentAnni-style-hide-yt-settings .ytp-settings-menu, .CentAnni-style-hide-yt-settings .ytp-overflow-panel {
  2037. opacity: 0 !important;
  2038. pointer-events: none !important;
  2039. }
  2040.  
  2041. /* customCSS CSS */
  2042. html {
  2043. font-size: var(--fontSize) !important;
  2044. font-family: "Roboto", Arial, sans-serif;
  2045. }
  2046.  
  2047. .CentAnni-style-hide-default-sidebar {
  2048. ytd-mini-guide-renderer.ytd-app { display: none !important; }
  2049. ytd-app[mini-guide-visible] ytd-page-manager.ytd-app { margin-left: 0 !important; }
  2050. #guide-button.ytd-masthead { display: none !important; }
  2051. #contents.ytd-rich-grid-renderer { justify-content: center !important; }
  2052. ytd-browse[mini-guide-visible] ytd-playlist-header-renderer.ytd-browse, ytd-browse[mini-guide-visible] ytd-playlist-sidebar-renderer.ytd-browse, ytd-browse[mini-guide-visible] .page-header-sidebar.ytd-browse {
  2053. left: 0;
  2054. }
  2055. }
  2056.  
  2057. html #above-the-fold h1,
  2058. h1.ytd-watch-metadata,
  2059. #video-title {
  2060. text-transform: var(--textTransform) !important;
  2061. }
  2062.  
  2063. .CentAnni-style-full-title {
  2064. #video-title.ytd-rich-grid-media {
  2065. white-space: normal;
  2066. text-overflow: unset;
  2067. overflow: unset;
  2068. display: inline-block;
  2069. }
  2070.  
  2071. #video-title {
  2072. max-height: unset !important;
  2073. -webkit-line-clamp: unset !important;
  2074. }
  2075. }
  2076.  
  2077. ytd-compact-video-renderer ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer),
  2078. ytd-rich-item-renderer ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer),
  2079. ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer) {
  2080. opacity: var(--watchedOpacity);
  2081. }
  2082.  
  2083. ytd-search ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer) {
  2084. opacity: .8;
  2085. }
  2086.  
  2087. .ytd-page-manager[page-subtype="history"] {
  2088. ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer) {
  2089. opacity: 1;
  2090. }
  2091. }
  2092.  
  2093. .CentAnni-style-hide-watched-videos-global {
  2094. ytd-rich-item-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer),
  2095. ytd-grid-video-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer) {
  2096. display: none !important;
  2097. }
  2098. }
  2099.  
  2100. html.CentAnni-style-pure-bg:not([dark]) ytd-app {
  2101. background: white !important;
  2102. }
  2103.  
  2104. html.CentAnni-style-pure-bg[dark] ytd-app {
  2105. background: black !important;
  2106. }
  2107.  
  2108. .CentAnni-style-remove-scrubber {
  2109. .ytp-scrubber-container {
  2110. display: none;
  2111. pointer-events: none;
  2112. }
  2113. }
  2114.  
  2115. .CentAnni-style-play-progress-color {
  2116. .ytp-play-progress, .ytp-swatch-background-color {
  2117. background: var(--progressBarColor) !important;
  2118. }
  2119. }
  2120.  
  2121. .CentAnni-style-disable-play-on-hover {
  2122. ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail,
  2123. ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail,
  2124. ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-endorsement-renderer.ytd-thumbnail,
  2125. ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-hover-text-renderer.ytd-thumbnail,
  2126. ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail,
  2127. ytd-thumbnail[now-playing] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail,
  2128. ytd-thumbnail-overlay-loading-preview-renderer[is-preview-loading],
  2129. ytd-grid-video-renderer a#thumbnail div#mouseover-overlay,
  2130. ytd-rich-item-renderer a#thumbnail div#mouseover-overlay,
  2131. ytd-thumbnail-overlay-loading-preview-renderer,
  2132. ytd-moving-thumbnail-renderer img#thumbnail,
  2133. ytd-moving-thumbnail-renderer yt-icon,
  2134. ytd-moving-thumbnail-renderer span,
  2135. ytd-moving-thumbnail-renderer img,
  2136. ytd-moving-thumbnail-renderer,
  2137. #mouseover-overlay,
  2138. ytd-video-preview,
  2139. div#video-preview,
  2140. #video-preview,
  2141. #preview {
  2142. display: none !important;
  2143. }
  2144. }
  2145.  
  2146. .CentAnni-style-hide-end-cards {
  2147. .ytp-ce-element {
  2148. display: none !important;
  2149. }
  2150. }
  2151.  
  2152. .CentAnni-style-hide-endscreen {
  2153. .html5-video-player .html5-endscreen.videowall-endscreen {
  2154. display: none !important;
  2155. }
  2156. .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) {
  2157. display: block !important;
  2158. cursor: default !important;
  2159. }
  2160. .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) button {
  2161. display: none;
  2162. }
  2163. .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) .ytp-cued-thumbnail-overlay-image {
  2164. display: block !important;
  2165. background-image: var(--video-url);
  2166. }
  2167. }
  2168.  
  2169. .CentAnni-style-gradient-bottom {
  2170. .ytp-gradient-bottom {
  2171. padding-top: 50px !important;
  2172. height: 48px !important;
  2173. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) !important;
  2174. }
  2175. }
  2176.  
  2177. .CentAnni-style-video-row {
  2178. ytd-rich-grid-renderer {
  2179. --ytd-rich-grid-items-per-row: var(--itemsPerRow) !important;
  2180. }
  2181. }
  2182.  
  2183. .CentAnni-style-hide-voice-search {
  2184. #voice-search-button.ytd-masthead {
  2185. display: none;
  2186. }
  2187. }
  2188.  
  2189. .CentAnni-style-hide-create-btn {
  2190. ytd-button-renderer.ytd-masthead[button-renderer][button-next]:has(button[aria-label="Create"]) {
  2191. display: none !important;
  2192. }
  2193. }
  2194.  
  2195. .CentAnni-style-hide-queue-btn {
  2196. ytd-thumbnail-overlay-toggle-button-renderer[aria-label="Add to queue"] {
  2197. display: none;
  2198. }
  2199. }
  2200.  
  2201. .CentAnni-style-hide-notification-btn {
  2202. #masthead-container #end #buttons ytd-notification-topbar-button-renderer {
  2203. display: none !important;
  2204. }
  2205. }
  2206.  
  2207. .CentAnni-style-hide-notification-badge {
  2208. #masthead-container #end #buttons ytd-notification-topbar-button-renderer .yt-spec-icon-badge-shape__badge {
  2209. display: none;
  2210. }
  2211. }
  2212.  
  2213. .CentAnni-style-hide-own-avatar {
  2214. #avatar-btn {
  2215. display: none !important;
  2216. }
  2217. }
  2218.  
  2219. .CentAnni-style-hide-brand-text {
  2220. ytd-topbar-logo-renderer > #logo > ytd-yoodle-renderer > picture,
  2221. #country-code.ytd-topbar-logo-renderer,
  2222. #logo-icon [id^="youtube-paths_yt"] {
  2223. display: none;
  2224. }
  2225.  
  2226. #logo.ytd-masthead {
  2227. width: 45px;
  2228. overflow: hidden;
  2229. }
  2230.  
  2231. ytd-topbar-logo-renderer > #logo > ytd-yoodle-renderer > ytd-logo {
  2232. display: block !important;
  2233. }
  2234. }
  2235.  
  2236. .CentAnni-style-hide-fundraiser {
  2237. #donation-shelf, ytd-badge-supported-renderer:has([aria-label="Fundraiser"]) {
  2238. display: none !important;
  2239. }
  2240. }
  2241.  
  2242. .CentAnni-style-hide-miniplayer {
  2243. ytd-miniplayer {
  2244. display: none !important;
  2245. }
  2246.  
  2247. #ytd-player .ytp-miniplayer-button {
  2248. display: none !important;
  2249. }
  2250. }
  2251.  
  2252. .CentAnni-style-square-search-bar {
  2253. #center.ytd-masthead { flex: 0 1 500px; }
  2254. .YtSearchboxComponentInputBox { border: 1px solid hsl(0,0%,18.82%); border-radius: 0; }
  2255. .YtSearchboxComponentSuggestionsContainer { border-radius: 0 0 10px 10px; }
  2256. .YtSearchboxComponentSearchButton, .YtSearchboxComponentSearchButtonDark { display: none; }
  2257. .YtSearchboxComponentHost { margin: 0; }
  2258.  
  2259. .ytSearchboxComponentInputBox { border: 1px solid hsl(0,0%,18.82%); border-radius: 0; }
  2260. .ytSearchboxComponentSuggestionsContainer { border-radius: 0 0 10px 10px; }
  2261. .ytSearchboxComponentSearchButton, .ytSearchboxComponentSearchButtonDark { display: none; }
  2262. .ytSearchboxComponentHost { margin: 0; }
  2263.  
  2264. .ytSearchboxComponentDesktop .ytSearchboxComponentClearButton {
  2265. border-radius: 0;
  2266. height: 38px;
  2267. width: 38px;
  2268. margin-right: 6px;
  2269. }
  2270. }
  2271.  
  2272. .ytd-page-manager[page-subtype="home"] {
  2273. #avatar-container.ytd-rich-grid-media {
  2274. margin: 12px 12px 0 6px;
  2275. }
  2276. }
  2277.  
  2278. .CentAnni-style-square-design {
  2279. #thumbnail,
  2280. .CentAnni-tabView,
  2281. #card.ytd-miniplayer,
  2282. .smartimation__border,
  2283. .ytp-tooltip-text-wrapper,
  2284. ytd-playlist-video-renderer,
  2285. .ytOfficialCardViewModelHost,
  2286. #dismissed.ytd-rich-grid-media,
  2287. ytd-info-panel-content-renderer,
  2288. ytd-expandable-metadata-renderer,
  2289. .yt-thumbnail-view-model--medium,
  2290. .badge.ytd-badge-supported-renderer,
  2291. .yt-spec-button-shape-next--size-xs,
  2292. #related.style-scope.ytd-watch-flexy,
  2293. .animated-action__background-container,
  2294. .ytp-player-minimized .html5-main-video,
  2295. .ytProgressBarLineProgressBarLineRounded,
  2296. .ytp-tooltip.ytp-text-detail.ytp-preview,
  2297. .collections-stack-wiz__collection-stack2,
  2298. ytd-donation-shelf-renderer[modern-panels],
  2299. .ytp-player-minimized .ytp-miniplayer-scrim,
  2300. yt-interaction.circular .fill.yt-interaction,
  2301. .yt-spec-button-shape-next--icon-only-default,
  2302. yt-interaction.circular .stroke.yt-interaction,
  2303. ytd-watch-flexy[theater] .CentAnni-tabView-tab,
  2304. tp-yt-paper-toast.yt-notification-action-renderer,
  2305. .collections-stack-wiz__collection-stack1--medium,
  2306. .metadata-container.ytd-reel-player-overlay-renderer,
  2307. ytd-shorts .player-container.ytd-reel-video-renderer,
  2308. ytd-compact-link-renderer.ytd-settings-sidebar-renderer,
  2309. .ytp-tooltip.ytp-text-detail.ytp-preview .ytp-tooltip-bg,
  2310. ytd-live-chat-frame[theater-watch-while][rounded-container],
  2311. ytd-watch-flexy[rounded-player] #ytd-player.ytd-watch-flexy,
  2312. ytd-shorts[enable-anchored-panel] .anchored-panel.ytd-shorts,
  2313. ytd-live-chat-frame[rounded-container]:not([theater-watch-while]),
  2314. ytd-live-chat-frame[rounded-container] iframe.ytd-live-chat-frame,
  2315. .html5-video-player:not(.ytp-touch-mode) ::-webkit-scrollbar-thumb,
  2316. .CentAnni-tabView:has(.CentAnni-tabView-tab.active[data-tab="tab-2"]),
  2317. ytd-thumbnail[size="large"] a.ytd-thumbnail, ytd-thumbnail[size="large"]::before,
  2318. ytd-watch-flexy[rounded-player-large][default-layout] #ytd-player.ytd-watch-flexy,
  2319. ytd-thumbnail[size="medium"] a.ytd-thumbnail, ytd-thumbnail[size="medium"]::before,
  2320. ytd-engagement-panel-section-list-renderer[modern-panels]:not([live-chat-engagement-panel]),
  2321. ytd-macro-markers-list-item-renderer[rounded] #thumbnail.ytd-macro-markers-list-item-renderer,
  2322. ytd-expandable-metadata-renderer:not([is-expanded]) #header.ytd-expandable-metadata-renderer:hover,
  2323. ytd-watch-flexy[flexy][js-panel-height_]:not([fixed-panels]) #chat.ytd-watch-flexy:not([collapsed]),
  2324. ytd-playlist-panel-renderer[modern-panels]:not([within-miniplayer]) #container.ytd-playlist-panel-renderer {
  2325. border-radius: 0 !important;
  2326. }
  2327.  
  2328. .yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section {
  2329. border-radius: 1px;
  2330. }
  2331.  
  2332. .ytChipShapeChip,
  2333. yt-dropdown-menu,
  2334. .CentAnni-tabView-tab,
  2335. #menu.yt-dropdown-menu,
  2336. ytd-menu-popup-renderer,
  2337. ytd-guide-entry-renderer,
  2338. tp-yt-paper-dialog[modern],
  2339. yt-chip-cloud-chip-renderer,
  2340. ytd-multi-page-menu-renderer,
  2341. #description.ytd-watch-metadata,
  2342. .badge-shape-wiz--thumbnail-badge,
  2343. ytd-author-comment-badge-renderer,
  2344. .yt-spec-button-shape-next--size-s,
  2345. .yt-spec-button-shape-next--size-m,
  2346. ytd-rich-metadata-renderer[rounded],
  2347. .yt-sheet-view-model-wiz--contextual,
  2348. .ytVideoMetadataCarouselViewModelHost,
  2349. yt-interaction.ytd-guide-entry-renderer,
  2350. .dropdown-content.tp-yt-paper-menu-button,
  2351. .tp-yt-paper-tooltip[style-target=tooltip],
  2352. #chip-container.yt-chip-cloud-chip-renderer,
  2353. .image-wrapper.ytd-hero-playlist-thumbnail-renderer,
  2354. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer,
  2355. .immersive-header-container.ytd-playlist-header-renderer,
  2356. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:hover,
  2357. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:focus,
  2358. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:active,
  2359. ytd-engagement-panel-section-list-renderer[modern-panels]:not([live-chat-engagement-panel]) {
  2360. border-radius: 2px;
  2361. }
  2362.  
  2363. ytd-rich-item-renderer yt-interaction.circular .fill.yt-interaction,
  2364. ytd-rich-item-renderer yt-interaction.circular .stroke.yt-interaction,
  2365. #masthead-container yt-interaction.circular .fill.yt-interaction,
  2366. #masthead-container yt-interaction.circular .stroke.yt-interaction {
  2367. border-radius: 50% !important;
  2368. }
  2369.  
  2370. tp-yt-paper-item.ytd-guide-entry-renderer,
  2371. ytd-compact-link-renderer[compact-link-style="compact-link-style-type-settings-sidebar"] tp-yt-paper-item.ytd-compact-link-renderer {
  2372. --paper-item-focused-before-border-radius: 0;
  2373. }
  2374.  
  2375. .ytd-page-manager[page-subtype="home"] {
  2376. yt-chip-cloud-chip-renderer {
  2377. border-radius: 2px;
  2378. }
  2379.  
  2380. .CentAnni-style-live-video, .CentAnni-style-upcoming-video, .CentAnni-style-newly-video, .CentAnni-style-recent-video, .CentAnni-style-lately-video, .CentAnni-style-old-video { border-radius: 0; }
  2381. }
  2382.  
  2383. .ytd-page-manager[page-subtype="subscriptions"] {
  2384. .CentAnni-style-last-seen { border-radius: 0; }
  2385. }
  2386.  
  2387. .ytd-page-manager[page-subtype="channels"] {
  2388. .yt-spec-button-shape-next--size-m {
  2389. border-radius: 2px;
  2390. }
  2391.  
  2392. .yt-thumbnail-view-model--medium,
  2393. .yt-image-banner-view-model-wiz--inset,
  2394. .collections-stack-wiz__collection-stack2,
  2395. #chip-container.yt-chip-cloud-chip-renderer,
  2396. .collections-stack-wiz__collection-stack1--medium {
  2397. border-radius: 0 !important;
  2398. }
  2399. }
  2400.  
  2401. .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--segmented-start {
  2402. border-radius: 2px 0 0 3px;
  2403. }
  2404.  
  2405. .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--segmented-end {
  2406. border-radius: 0 3px 3px 0;
  2407. }
  2408.  
  2409. ytd-expandable-metadata-renderer:not([is-expanded]) {
  2410. --yt-img-border-radius: 0px;
  2411. border-radius: 0px;
  2412. }
  2413.  
  2414. .desktopShortsPlayerControlsWizHost {
  2415. left: 0;
  2416. right: 0;
  2417. }
  2418.  
  2419. ytd-search yt-official-card-view-model horizontal-shelf-view-model .ytwHorizontalShelfViewModelLeftArrow .yt-spec-button-shape-next--size-m,
  2420. ytd-search yt-official-card-view-model horizontal-shelf-view-model .ytwHorizontalShelfViewModelRightArrow .yt-spec-button-shape-next--size-m {
  2421. border-radius: 18px;
  2422. }
  2423. }
  2424.  
  2425. .CentAnni-style-square-avatars {
  2426. .yt-spec-avatar-shape__image,
  2427. #avatar.ytd-video-owner-renderer,
  2428. yt-img-shadow.ytd-video-renderer,
  2429. yt-img-shadow.ytd-channel-renderer,
  2430. .thumbnail.ytd-notification-renderer,
  2431. ytd-menu-renderer.ytd-rich-grid-media,
  2432. yt-img-shadow.ytd-guide-entry-renderer,
  2433. #avatar.ytd-active-account-header-renderer,
  2434. #avatar.ytd-watch-card-rich-header-renderer,
  2435. yt-img-shadow.ytd-topbar-menu-button-renderer,
  2436. #author-thumbnail.ytd-comment-simplebox-renderer,
  2437. ytd-rich-item-renderer yt-interaction.circular .fill.yt-interaction,
  2438. ytd-rich-item-renderer yt-interaction.circular .stroke.yt-interaction,
  2439. .yt-spec-avatar-shape--cairo-refresh.yt-spec-avatar-shape--live-ring::after,
  2440. #author-thumbnail.ytd-comment-view-model yt-img-shadow.ytd-comment-view-model,
  2441. #author-thumbnail.ytd-backstage-post-renderer yt-img-shadow.ytd-backstage-post-renderer,
  2442. ytd-comment-replies-renderer #creator-thumbnail.ytd-comment-replies-renderer yt-img-shadow.ytd-comment-replies-renderer {
  2443. border-radius: 0 !important;
  2444. }
  2445. }
  2446.  
  2447. #CentAnni-channel-btn {
  2448. display: flex;
  2449. align-items: center;
  2450. gap: 6px;
  2451. }
  2452.  
  2453. #CentAnni-playlist-direction-container {
  2454. display: inline-flex;
  2455. align-items: center;
  2456. margin-left: 8px;
  2457. }
  2458.  
  2459. #CentAnni-playlist-direction-container > span {
  2460. margin-right: 4px;
  2461. color: var(--yt-spec-text-primary);
  2462. font-family: "YouTube Sans","Roboto",sans-serif;
  2463. font-size: 1.7rem;
  2464. line-height: 1rem;
  2465. font-weight: 600;
  2466. text-rendering: optimizeLegibility !important;
  2467. -webkit-font-smoothing: antialiased !important;
  2468. -moz-osx-font-smoothing: grayscale !important;
  2469. cursor: default;
  2470. }
  2471.  
  2472. #end-actions.ytd-playlist-panel-renderer {
  2473. margin-right: 24px;
  2474. }
  2475.  
  2476. .CentAnni-playlist-direction-btn {
  2477. color: var(--yt-spec-text-secondary) !important;
  2478. }
  2479.  
  2480. .CentAnni-playlist-direction-btn.active {
  2481. color: var(--yt-spec-text-primary) !important;
  2482. }
  2483.  
  2484. .CentAnni-style-compact-layout {
  2485. ytd-rich-section-renderer:has(.grid-subheader.ytd-shelf-renderer) {
  2486. display: none;
  2487. }
  2488.  
  2489. #page-manager.ytd-app {
  2490. --ytd-toolbar-offset: 0 !important;
  2491. }
  2492.  
  2493. ytd-browse[page-subtype="hashtag-landing-page"] {
  2494. transform: translateY(0px);
  2495. }
  2496.  
  2497. .ytd-page-manager[page-subtype="home"],
  2498. .ytd-page-manager[page-subtype="channels"],
  2499. .ytd-page-manager[page-subtype="subscriptions"] {
  2500. ytd-menu-renderer .ytd-menu-renderer[style-target=button] {
  2501. height: 36px;
  2502. width: 36px;
  2503. }
  2504.  
  2505. button.yt-icon-button>yt-icon {
  2506. transform: rotate(90deg);
  2507. }
  2508.  
  2509. #contents.ytd-rich-grid-renderer {
  2510. width: 100%;
  2511. max-width: 100%;
  2512. padding-top: 0;
  2513. display: flex;
  2514. flex-wrap: wrap;
  2515. column-gap: 5px;
  2516. row-gap: 10px;
  2517. }
  2518.  
  2519. .style-scope.ytd-two-column-browse-results-renderer {
  2520. --ytd-rich-grid-item-max-width: 100vw;
  2521. --ytd-rich-grid-item-min-width: 310px;
  2522. --ytd-rich-grid-item-margin: 0px !important;
  2523. --ytd-rich-grid-content-offset-top: 56px;
  2524. }
  2525.  
  2526. ytd-rich-item-renderer[rendered-from-rich-grid] {
  2527. margin: 0 !important;
  2528. }
  2529.  
  2530. #meta.ytd-rich-grid-media {
  2531. overflow-x: hidden;
  2532. padding-right: 6px;
  2533. }
  2534.  
  2535. #avatar-container.ytd-rich-grid-media {
  2536. margin:7px 6px 50px 6px;
  2537. }
  2538.  
  2539. h3.ytd-rich-grid-media {
  2540. margin: 7px 0 4px 0;
  2541. }
  2542.  
  2543. .yt-spec-avatar-shape--cairo-refresh.yt-spec-avatar-shape--live-ring::after {
  2544. inset: -2px;
  2545. }
  2546. }
  2547.  
  2548. .ytd-page-manager[page-subtype="home"] {
  2549. ytd-menu-renderer.ytd-rich-grid-media {
  2550. position: absolute;
  2551. height: 36px;
  2552. width: 36px;
  2553. top: 50px;
  2554. right: auto;
  2555. left: 6px;
  2556. align-items: center;
  2557. background-color: rgba(255,255,255,.1);
  2558. border-radius: 50%;
  2559. }
  2560.  
  2561. .title-badge.ytd-rich-grid-media, .video-badge.ytd-rich-grid-media {
  2562. position: absolute;
  2563. bottom: 0;
  2564. right: 0;
  2565. margin: 8px;
  2566. display: flex;
  2567. flex-direction: row;
  2568. }
  2569.  
  2570. ytd-rich-item-renderer[rendered-from-rich-grid] {
  2571. margin: 0 !important;
  2572. }
  2573.  
  2574. #contents.ytd-rich-grid-renderer {
  2575. padding-top: 2px;
  2576. column-gap: 14px;
  2577. row-gap: 14px;
  2578. }
  2579.  
  2580. .style-scope.ytd-two-column-browse-results-renderer {
  2581. --ytd-rich-grid-item-margin: .5% !important;
  2582. }
  2583. }
  2584.  
  2585. .ytd-page-manager[page-subtype="channels"] {
  2586. ytd-tabbed-page-header.grid-5-columns #page-header.ytd-tabbed-page-header, ytd-tabbed-page-header.grid-5-columns[has-inset-banner] #page-header-banner.ytd-tabbed-page-header {
  2587. padding: 0 !important;
  2588. }
  2589.  
  2590. ytd-two-column-browse-results-renderer.grid-5-columns, .grid-5-columns.ytd-two-column-browse-results-renderer {
  2591. width: 100% !important;
  2592. }
  2593.  
  2594. ytd-rich-grid-renderer:not([is-default-grid]) #header.ytd-rich-grid-renderer {
  2595. transform: translateY(-40px);
  2596. z-index: 2000;
  2597. width: max-content;
  2598. margin-left: auto;
  2599. }
  2600.  
  2601. ytd-two-column-browse-results-renderer.grid-6-columns ytd-rich-grid-renderer:not([is-default-grid]) #header {
  2602. margin-right: 5px;
  2603. }
  2604.  
  2605. ytd-two-column-browse-results-renderer.grid-5-columns ytd-rich-grid-renderer:not([is-default-grid]) #header {
  2606. margin-right: 110px;
  2607. }
  2608.  
  2609. ytd-feed-filter-chip-bar-renderer[component-style="FEED_FILTER_CHIP_BAR_STYLE_TYPE_CHANNEL_PAGE_GRID"] {
  2610. margin-bottom: -32px;
  2611. margin-top: 0;
  2612. }
  2613.  
  2614. .page-header-view-model-wiz__page-header-headline-image {
  2615. margin-left: 110px;
  2616. }
  2617.  
  2618. ytd-menu-renderer.ytd-rich-grid-media {
  2619. position: absolute;
  2620. height: 36px;
  2621. width: 36px;
  2622. top: initial;
  2623. bottom: -10px;
  2624. right: 0;
  2625. align-items: center;
  2626. border-radius: 50%;
  2627. }
  2628.  
  2629. .yt-tab-group-shape-wiz__slider,.yt-tab-shape-wiz__tab-bar {
  2630. display: none;
  2631. }
  2632.  
  2633. .yt-tab-shape-wiz__tab--tab-selected,.yt-tab-shape-wiz__tab:hover {
  2634. color: white;
  2635. }
  2636.  
  2637. .style-scope.ytd-two-column-browse-results-renderer {
  2638. --ytd-rich-grid-item-margin: .5% !important;
  2639. }
  2640.  
  2641. ytd-backstage-items {
  2642. display: block;
  2643. max-width: 100%;
  2644. }
  2645.  
  2646. #contents {
  2647. margin-left: 10px;
  2648. margin-right: 10px;
  2649. }
  2650.  
  2651. #header-container {
  2652. width: 80vw;
  2653. align-self: center;
  2654. }
  2655.  
  2656. #items.ytd-grid-renderer {
  2657. justify-content: center;
  2658. }
  2659. }
  2660.  
  2661. .ytd-page-manager[page-subtype="channels"] #contentContainer {
  2662. padding-top: 0 !important;
  2663. }
  2664.  
  2665. .ytd-page-manager[page-subtype="channels"] tp-yt-app-header {
  2666. position: static !important;
  2667. transform: none !important;
  2668. transition: none !important;
  2669. }
  2670.  
  2671. .ytd-page-manager[page-subtype="channels"] tp-yt-app-header[fixed] {
  2672. position: static !important;
  2673. transform: none !important;
  2674. transition: none !important;
  2675. }
  2676.  
  2677. .ytd-page-manager[page-subtype="channels"] tp-yt-app-header #page-header {
  2678. position: static !important;
  2679. transform: none !important;
  2680. }
  2681.  
  2682. .ytd-page-manager[page-subtype="subscriptions"] {
  2683. ytd-menu-renderer.ytd-rich-grid-media {
  2684. position: absolute;
  2685. height: 36px;
  2686. width: 36px;
  2687. top: 50px;
  2688. right: auto;
  2689. left: 3px;
  2690. align-items: center;
  2691. background-color: rgba(255,255,255,.1);
  2692. border-radius: 50%;
  2693. }
  2694.  
  2695. .title-badge.ytd-rich-grid-media, .video-badge.ytd-rich-grid-media {
  2696. position: absolute;
  2697. margin: 0px 10% 0 0;
  2698. right: 0;
  2699. top: 6em;
  2700. }
  2701. }
  2702.  
  2703. .item.ytd-watch-metadata {
  2704. margin-top: 7px;
  2705. }
  2706.  
  2707. #middle-row.ytd-watch-metadata:empty {
  2708. display: none;
  2709. }
  2710.  
  2711. #subheader.ytd-engagement-panel-title-header-renderer:not(:empty) {
  2712. padding: 0 !important;
  2713. transform: translateX(110px) translateY(-44px);
  2714. background-color: transparent;
  2715. border-top: none;
  2716. }
  2717.  
  2718. #header.ytd-engagement-panel-title-header-renderer {
  2719. padding: 4px 7px 4px 7px;
  2720. }
  2721.  
  2722. #visibility-button.ytd-engagement-panel-title-header-renderer, #information-button.ytd-engagement-panel-title-header-renderer {
  2723. z-index: 1;
  2724. }
  2725.  
  2726. .ytChipShapeChip:hover {
  2727. background: rgba(255,255,255,0.2);
  2728. border-color: transparent;
  2729. }
  2730.  
  2731. .ytChipShapeActive:hover {
  2732. background-color: #f1f1f1;
  2733. color: #0f0f0f;
  2734. }
  2735.  
  2736. ytd-engagement-panel-title-header-renderer {
  2737. height: 54px;
  2738. }
  2739.  
  2740. .yt-spec-button-shape-next--icon-only-default {
  2741. width: 35px;
  2742. height: 35px;
  2743. }
  2744.  
  2745. ytd-miniplayer {
  2746. --ytd-miniplayer-attachment-padding: 0;
  2747. }
  2748.  
  2749. ytd-watch-flexy #title > ytd-badge-supported-renderer div > yt-icon {
  2750. padding: 0 2px 0px 0;
  2751. }
  2752. }
  2753.  
  2754. ytd-watch-flexy #expandable-metadata #content.ytd-expandable-metadata-renderer {
  2755. height: calc(var(--yt-macro-marker-list-item-height) - 34px);
  2756. visibility: visible;
  2757. pointer-events: auto;
  2758. }
  2759.  
  2760. ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[is-watch] #collapsed-title.ytd-expandable-metadata-renderer {
  2761. display: none;
  2762. }
  2763.  
  2764. ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[has-video-summary] #expanded-title-subtitle-group.ytd-expandable-metadata-renderer {
  2765. display: flex !important;
  2766. }
  2767.  
  2768. ytd-watch-flexy #expandable-metadata #expanded-subtitle.ytd-expandable-metadata-renderer {
  2769. display: block !important;
  2770. pointer-events: auto;
  2771. }
  2772.  
  2773. ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[is-watch] {
  2774. background: transparent;
  2775. pointer-events: none;
  2776. }
  2777.  
  2778. ytd-watch-flexy #expandable-metadata #right-section.ytd-expandable-metadata-renderer {
  2779. display: none;
  2780. }
  2781.  
  2782. ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer:not([is-expanded]) #header.ytd-expandable-metadata-renderer:hover {
  2783. background-color: transparent;
  2784. }
  2785.  
  2786. .ytd-page-manager[page-subtype="home"] {
  2787. .CentAnni-style-live-video, .CentAnni-style-upcoming-video, .CentAnni-style-newly-video, .CentAnni-style-recent-video, .CentAnni-style-lately-video { outline: 2px solid; border-radius: 12px; }
  2788. .CentAnni-style-old-video { outline: none;}
  2789.  
  2790. .CentAnni-style-live-video { outline-color: var(--liveVideo); }
  2791. .CentAnni-style-streamed-text { color: var(--streamedText); }
  2792. .CentAnni-style-upcoming-video { outline-color: var(--upComingVideo); }
  2793. .CentAnni-style-newly-video { outline-color: var(--newlyVideo); }
  2794. .CentAnni-style-recent-video { outline-color: var(--recentVideo); }
  2795. .CentAnni-style-lately-video { outline-color: var(--latelyVideo); }
  2796. .CentAnni-style-old-video { opacity: var(--oldVideo); }
  2797. ytd-rich-item-renderer:has(a#video-title-link[aria-label*="From your Watch later playlist"]) { background-color: var(--WatchLater); }
  2798. }
  2799.  
  2800. .ytd-page-manager[page-subtype="subscriptions"] {
  2801. .CentAnni-style-last-seen {
  2802. border: 2px solid var(--lastSeenVideoColor);
  2803. border-radius: 12px;
  2804. }
  2805. }
  2806.  
  2807. .ytd-page-manager[page-subtype="playlist"] {
  2808. .CentAnni-style-playlist-remove-btn {
  2809. display: flex;
  2810. align-items: center;
  2811. border: 1px dashed red;
  2812. background: transparent;
  2813. cursor: pointer;
  2814. margin: 10px 20px 0px 10px;
  2815. padding: 15px;
  2816. transition: background 0.2s, filter 0.2s, transform 0.15s;
  2817. font-size: 2rem;
  2818. position: relative;
  2819. z-index: 1000;
  2820. border-radius: 2px;
  2821. }
  2822.  
  2823. .CentAnni-style-playlist-remove-btn:hover {
  2824. background: darkred;
  2825. }
  2826.  
  2827. .CentAnni-style-playlist-remove-btn:active {
  2828. background: darkred;
  2829. transform: scale(0.9);
  2830. }
  2831. }
  2832.  
  2833. .CentAnni-style-playlist-hide-menu {
  2834. display: none !important;
  2835. }
  2836.  
  2837. .CentAnni-style-hide-watched-videos {
  2838. .ytd-page-manager[page-subtype="home"] {
  2839. ytd-rich-item-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer) {
  2840. display: none;
  2841. }
  2842. }
  2843. }
  2844.  
  2845. .CentAnni-close-live-chat {
  2846. #chat-container {
  2847. z-index: -1 !important;
  2848. opacity: 0 !important;
  2849. visibility: hidden;
  2850. pointer-events: none !important;
  2851. }
  2852.  
  2853. ytd-watch-flexy[fixed-panels] #panels-full-bleed-container.ytd-watch-flexy {
  2854. width: var(--ytd-watch-flexy-sidebar-width);
  2855. display: none;
  2856. }
  2857.  
  2858. .video-stream.html5-main-video {
  2859. width: 100%;
  2860. }
  2861.  
  2862. ytd-watch-flexy[fixed-panels] #columns.ytd-watch-flexy {
  2863. padding-right: 0;
  2864. }
  2865. }
  2866.  
  2867. .CentAnni-style-hide-join-btn {
  2868. button[aria-label="Join this channel"],
  2869. #sponsor-button.ytd-video-owner-renderer:not(:empty),
  2870. ytd-browse[page-subtype="channels"] ytd-recognition-shelf-renderer,
  2871. ytd-browse[page-subtype="channels"] yt-page-header-view-model yt-flexible-actions-view-model button-view-model {
  2872. display: none !important;
  2873. }
  2874. }
  2875.  
  2876. :root {
  2877. --next-button-visibility: none;
  2878. }
  2879.  
  2880. html:has(.CentAnni-tabView-tab[data-tab="tab-6"]) {
  2881. --next-button-visibility: inline-block;
  2882. }
  2883.  
  2884. .CentAnni-style-hide-playnext-btn {
  2885. a.ytp-next-button {
  2886. display: var(--next-button-visibility);
  2887. }
  2888. }
  2889.  
  2890. .CentAnni-style-hide-airplay-btn {
  2891. #ytd-player .ytp-airplay-button {
  2892. display: none;
  2893. }
  2894. }
  2895.  
  2896. .CentAnni-style-small-subscribe-btn {
  2897. .ytd-page-manager:not([page-subtype="channels"]) .yt-spec-button-shape-next.yt-spec-button-shape-next--tonal.yt-spec-button-shape-next--mono.yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-leading-trailing {
  2898. display: flex;
  2899. align-items: center;
  2900. justify-content: flex-start;
  2901. overflow: hidden;
  2902. width: 36px;
  2903. padding: 0 12px;
  2904. }
  2905. }
  2906.  
  2907. .CentAnni-style-hide-share-btn {
  2908. yt-button-view-model.ytd-menu-renderer:has(button.yt-spec-button-shape-next[aria-label="Share"]) {
  2909. display: none;
  2910. }
  2911. }
  2912.  
  2913. .CentAnni-style-hide-hashtags {
  2914. ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a {
  2915. display: none !important;
  2916. }
  2917.  
  2918. ytd-watch-flexy #description > #description-inner #info-container {
  2919. height: 18px !important;
  2920. }
  2921.  
  2922. .CentAnni-chapter-title {
  2923. max-width: 60% !important;
  2924. }
  2925.  
  2926. & ytd-watch-flexy #bottom-row.ytd-watch-metadata {
  2927. height: fit-content !important;
  2928. }
  2929. }
  2930.  
  2931. .CentAnni-style-hide-info-panel {
  2932. #middle-row,
  2933. ytd-info-panel-container-renderer {
  2934. display: none;
  2935. }
  2936. }
  2937.  
  2938. .CentAnni-style-hide-add-comment {
  2939. ytd-shorts #header.ytd-item-section-renderer,
  2940. ytd-comments ytd-comments-header-renderer #simple-box {
  2941. display: none;
  2942. }
  2943.  
  2944. #title.ytd-comments-header-renderer {
  2945. margin-bottom: 0;
  2946. }
  2947. }
  2948.  
  2949. .CentAnni-style-hide-news-home {
  2950. ytd-browse[page-subtype="home"] ytd-rich-grid-renderer ytd-rich-section-renderer:has(yt-icon:empty) {
  2951. display: none;
  2952. }
  2953. }
  2954.  
  2955. .CentAnni-style-hide-playlists-home {
  2956. ytd-browse[page-subtype="home"] ytd-rich-grid-renderer > #contents > ytd-rich-item-renderer:has(a[href*="list="]) {
  2957. display: none;
  2958. }
  2959. }
  2960.  
  2961. .CentAnni-style-hide-reply-btn {
  2962. ytd-comments ytd-comment-engagement-bar #reply-button-end {
  2963. display: none;
  2964. }
  2965. }
  2966.  
  2967. .CentAnni-style-search-hide-right-sidebar {
  2968. #container.ytd-search ytd-secondary-search-container-renderer {
  2969. display: none;
  2970. }
  2971. }
  2972.  
  2973. .CentAnni-style-hide-shorts {
  2974. a[title="Shorts"],
  2975. #container.ytd-search ytd-reel-shelf-renderer,
  2976. ytd-rich-item-renderer:has(a[href^="/shorts/"]),
  2977. ytd-watch-metadata #description ytd-reel-shelf-renderer,
  2978. ytd-browse[page-subtype="channels"] ytd-reel-shelf-renderer,
  2979. ytd-video-renderer:has(a.yt-simple-endpoint[href*="shorts"]),
  2980. yt-chip-cloud-chip-renderer[chip-shape-data*='"text":"Shorts"'],
  2981. ytd-reel-shelf-renderer.ytd-structured-description-content-renderer,
  2982. ytd-rich-section-renderer:has(div ytd-rich-shelf-renderer[is-shorts]),
  2983. #container.ytd-search ytd-video-renderer:has(a.yt-simple-endpoint[href*="shorts"]),
  2984. ytd-item-section-renderer[page-subtype="subscriptions"]:has(ytd-reel-shelf-renderer),
  2985. ytd-browse[page-subtype="hashtag-landing-page"] tp-yt-app-toolbar.ytd-tabbed-page-header,
  2986. #tabsContent > yt-tab-group-shape > div.yt-tab-group-shape-wiz__tabs > yt-tab-shape[tab-title="Shorts"] {
  2987. display: none !important;
  2988. }
  2989. }
  2990.  
  2991. .CentAnni-style-hide-ad-slots {
  2992. #player-ads,
  2993. .yt-consent,
  2994. #masthead-ad,
  2995. #promotion-shelf,
  2996. .yt-consent-banner,
  2997. #top_advertisement,
  2998. .ytp-subscribe-card,
  2999. .ytp-featured-product,
  3000. ytd-search-pyv-renderer,
  3001. #yt-lang-alert-container,
  3002. .ytd-merch-shelf-renderer,
  3003. .ytd-primetime-promo-renderer,
  3004. ytd-brand-video-singleton-renderer,
  3005. #related ytd-in-feed-ad-layout-renderer,
  3006. ytd-rich-section-renderer:has(ytd-statement-banner-renderer),
  3007. ytd-rich-item-renderer:has(> #content > ytd-ad-slot-renderer),
  3008. ytd-rich-item-renderer:has(.badge-style-type-simple[aria-label="YouTube featured"])
  3009. ytd-compact-video-renderer:has(.badge-style-type-simple[aria-label="YouTube featured"]) {
  3010. display: none!important;
  3011. }
  3012. }
  3013.  
  3014. .CentAnni-style-hide-members-only {
  3015. ytd-compact-video-renderer:has(.badge-style-type-members-only),
  3016. ytd-rich-item-renderer:has(.badge-style-type-members-only) {
  3017. display: none;
  3018. }
  3019. }
  3020.  
  3021. .CentAnni-style-hide-pay-to-watch {
  3022. ytd-compact-video-renderer:has(.badge[aria-label="Pay to watch"]),
  3023. ytd-rich-item-renderer:has(.badge[aria-label="Pay to watch"]) {
  3024. display: none;
  3025. }
  3026. }
  3027.  
  3028. .CentAnni-style-hide-free-with-ads {
  3029. ytd-compact-video-renderer:has(.badge[aria-label="Free with ads"]),
  3030. ytd-rich-item-renderer:has(.badge[aria-label="Free with ads"]) {
  3031. display: none;
  3032. }
  3033. }
  3034.  
  3035. .CentAnni-style-hide-latest-posts {
  3036. #container.ytd-search ytd-shelf-renderer:has(ytd-post-renderer) {
  3037. display: none;
  3038. }
  3039. }
  3040.  
  3041. /* left navigation bar */
  3042. .CentAnni-style-lnb-hide-home-btn {
  3043. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Home"]) {
  3044. display: none;
  3045. }
  3046. }
  3047.  
  3048. .CentAnni-style-lnb-hide-subscriptions-btn {
  3049. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Subscriptions"]) {
  3050. display: none;
  3051. }
  3052. }
  3053.  
  3054. .CentAnni-style-lnb-hide-history-btn {
  3055. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="History"]) {
  3056. display: none;
  3057. }
  3058. }
  3059.  
  3060. .CentAnni-style-lnb-hide-playlists-btn {
  3061. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Playlists"]) {
  3062. display: none;
  3063. }
  3064. }
  3065.  
  3066. .CentAnni-style-lnb-hide-videos-btn {
  3067. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Your videos"]) {
  3068. display: none;
  3069. }
  3070. }
  3071.  
  3072. .CentAnni-style-lnb-hide-courses-btn {
  3073. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Your courses"]) {
  3074. display: none;
  3075. }
  3076. }
  3077.  
  3078. .CentAnni-style-lnb-hide-your-podcasts-btn {
  3079. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Your podcasts"]) {
  3080. display: none;
  3081. }
  3082. }
  3083.  
  3084. .CentAnni-style-lnb-hide-wl-btn {
  3085. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Watch later"]) {
  3086. display: none;
  3087. }
  3088. }
  3089.  
  3090. .CentAnni-style-lnb-hide-liked-videos-btn {
  3091. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Liked videos"]) {
  3092. display: none;
  3093. }
  3094. }
  3095.  
  3096. .CentAnni-style-lnb-hide-you-btn {
  3097. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="You"]) {
  3098. display: none;
  3099. }
  3100. }
  3101.  
  3102. .CentAnni-style-lnb-hide-subscriptions-section {
  3103. #sections ytd-guide-section-renderer:has(#expander-item) {
  3104. display: none;
  3105. }
  3106. }
  3107.  
  3108. .CentAnni-style-lnb-hide-subscriptions-title {
  3109. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="/@"]) #guide-section-title {
  3110. display: none;
  3111. }
  3112. }
  3113.  
  3114. .CentAnni-style-lnb-hide-more-btn {
  3115. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="/@"]) #expander-item {
  3116. display: none;
  3117. }
  3118. }
  3119.  
  3120. .CentAnni-style-lnb-hide-explore-section {
  3121. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="feed/trending"]) {
  3122. display: none;
  3123. }
  3124. }
  3125.  
  3126. .CentAnni-style-lnb-hide-explore-title {
  3127. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="feed/trending"]) #guide-section-title {
  3128. display: none;
  3129. }
  3130. }
  3131.  
  3132. .CentAnni-style-lnb-hide-trending-btn {
  3133. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Trending"]) {
  3134. display: none;
  3135. }
  3136. }
  3137.  
  3138. .CentAnni-style-lnb-hide-music-btn {
  3139. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Music"]) {
  3140. display: none;
  3141. }
  3142. }
  3143.  
  3144. .CentAnni-style-lnb-hide-movies-btn {
  3145. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Movies & TV"]) {
  3146. display: none;
  3147. }
  3148. }
  3149.  
  3150. .CentAnni-style-lnb-hide-live-btn {
  3151. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Live"]) {
  3152. display: none;
  3153. }
  3154. }
  3155.  
  3156. .CentAnni-style-lnb-hide-gaming-btn {
  3157. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Gaming"]) {
  3158. display: none;
  3159. }
  3160. }
  3161.  
  3162. .CentAnni-style-lnb-hide-news-btn {
  3163. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="News"]) {
  3164. display: none;
  3165. }
  3166. }
  3167.  
  3168. .CentAnni-style-lnb-hide-sports-btn {
  3169. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Sports"]) {
  3170. display: none;
  3171. }
  3172. }
  3173.  
  3174. .CentAnni-style-lnb-hide-learning-btn {
  3175. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Learning"]) {
  3176. display: none;
  3177. }
  3178. }
  3179.  
  3180. .CentAnni-style-lnb-hide-fashion-btn {
  3181. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Fashion & Beauty"]) {
  3182. display: none;
  3183. }
  3184. }
  3185.  
  3186. .CentAnni-style-lnb-hide-podcasts-btn {
  3187. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Podcasts"]) {
  3188. display: none;
  3189. }
  3190. }
  3191.  
  3192. .CentAnni-style-lnb-hide-more-section {
  3193. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="/premium"]) {
  3194. display: none;
  3195. }
  3196. }
  3197.  
  3198. .CentAnni-style-lnb-hide-more-title {
  3199. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="youtubekids"]) #guide-section-title {
  3200. display: none;
  3201. }
  3202. }
  3203.  
  3204. .CentAnni-style-lnb-hide-yt-premium-btn {
  3205. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="YouTube Premium"]) {
  3206. display: none;
  3207. }
  3208. }
  3209.  
  3210. .CentAnni-style-lnb-hide-yt-studio-btn {
  3211. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="YouTube Studio"]) {
  3212. display: none;
  3213. }
  3214. }
  3215.  
  3216. .CentAnni-style-lnb-hide-yt-music-btn {
  3217. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="YouTube Music"]) {
  3218. display: none;
  3219. }
  3220. }
  3221.  
  3222. .CentAnni-style-lnb-hide-yt-kids-btn {
  3223. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="YouTube Kids"]) {
  3224. display: none;
  3225. }
  3226. }
  3227.  
  3228. .CentAnni-style-lnb-hide-penultimate-section {
  3229. tp-yt-app-drawer#guide[role="navigation"] #sections ytd-guide-section-renderer:has(a[href*="/account"]) {
  3230. display: none;
  3231. }
  3232. }
  3233.  
  3234. .CentAnni-style-lnb-hide-settings-btn {
  3235. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Settings"]) {
  3236. display: none;
  3237. }
  3238. }
  3239.  
  3240. .CentAnni-style-lnb-hide-report-history-btn {
  3241. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Report history"]) {
  3242. display: none;
  3243. }
  3244. }
  3245.  
  3246. .CentAnni-style-lnb-hide-help-btn {
  3247. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Help"]) {
  3248. display: none;
  3249. }
  3250. }
  3251.  
  3252. .CentAnni-style-lnb-hide-feedback-btn {
  3253. #sections ytd-guide-section-renderer ytd-guide-entry-renderer:has(a[title="Send feedback"]) {
  3254. display: none;
  3255. }
  3256. }
  3257.  
  3258. .CentAnni-style-lnb-hide-footer {
  3259. tp-yt-app-drawer#guide[role="navigation"] #footer {
  3260. display: none;
  3261. }
  3262. }
  3263.  
  3264. /* hide main scrollbar in safari */
  3265. html {
  3266. scrollbar-width: none;
  3267. -ms-overflow-style: none;
  3268. }
  3269.  
  3270. html::-webkit-scrollbar {
  3271. display: none;
  3272. }
  3273.  
  3274. .scrollable-div {
  3275. scrollbar-width: auto;
  3276. -ms-overflow-style: auto;
  3277. }
  3278.  
  3279. .scrollable-div::-webkit-scrollbar {
  3280. display: block;
  3281. }
  3282.  
  3283. /* adjustments for light mode */
  3284. ytd-masthead:not([dark]):not([page-dark-theme]) .buttons-left {
  3285. color: black;
  3286. }
  3287.  
  3288. ytd-masthead:not([dark]):not([page-dark-theme]) .button-style-settings {
  3289. color: slategray !important;
  3290. }
  3291.  
  3292. ytd-masthead:not([dark]):not([page-dark-theme]) .button-style-settings:hover {
  3293. color: black !important;
  3294. }
  3295.  
  3296. ytd-masthead:not([dark]):not([page-dark-theme]) .button-style {
  3297. color: black;
  3298. }
  3299.  
  3300. ytd-masthead:not([dark]):not([page-dark-theme]) .button-wrapper:not(:has(.button-style-settings)):hover {
  3301. background-color: rgba(0, 0, 0, 0.1); border-radius: 24px;
  3302. }
  3303.  
  3304. ytd-masthead:not([dark]):not([page-dark-theme]) .button-wrapper:not(:has(.button-style-settings)):active {
  3305. background-color: rgba(0, 0, 0, 0.2); border-radius: 24px;
  3306. }
  3307.  
  3308.  
  3309. ytd-masthead:not([dark]):not([page-dark-theme]) .notification-error {
  3310. background-color: white;
  3311. border: 1px solid black;
  3312. color: #030303;
  3313. }
  3314.  
  3315. html:not([dark]) .CentAnni-playback-speed-button:active {
  3316. background: rgb(205,205,205) !important;
  3317. }
  3318.  
  3319. html:not([dark]) .CentAnni-tabView-tab,
  3320. html:not([dark]) .CentAnni-playback-speed-display {
  3321. background-color: rgba(0,0,0,0.05);
  3322. color: #0f0f0f;
  3323. }
  3324.  
  3325. html:not([dark]) .CentAnni-tabView-tab:hover {
  3326. background: rgba(0,0,0,0.1);
  3327. border-color: transparent;
  3328. }
  3329.  
  3330. html:not([dark]) .CentAnni-tabView-tab.active {
  3331. background-color: #0f0f0f;
  3332. color: white;
  3333. }
  3334.  
  3335. html:not([dark]) .CentAnni-tabView {
  3336. border: 1px solid var(--yt-spec-10-percent-layer);
  3337. }
  3338.  
  3339. html:not([dark]) ytd-watch-flexy[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-structured-description"] {
  3340. background-color: var(--yt-spec-badge-chip-background);
  3341. }
  3342.  
  3343. html:not([dark]) #CentAnni-playback-speed-control > div > svg > path {
  3344. fill: black;
  3345. }
  3346.  
  3347. html:not([dark]) ytd-watch-flexy[flexy][js-panel-height_] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy,
  3348. html:not([dark]) #related.style-scope.ytd-watch-flexy {
  3349. border: 1px solid var(--yt-spec-10-percent-layer);
  3350. border-top: none;
  3351. }
  3352.  
  3353. html:not([dark]) #tab-2 {
  3354. border-top: 1px solid var(--yt-spec-10-percent-layer);
  3355. }
  3356.  
  3357. html:not([dark]) .yt-tab-shape-wiz__tab--tab-selected,
  3358. html:not([dark]) .yt-tab-shape-wiz__tab:hover {
  3359. color: black !important;
  3360. }
  3361.  
  3362. .CentAnni-style-selection-color {
  3363. --selection-color: var(--darkSelectionColor);
  3364. --selection-text-color: white;
  3365. }
  3366.  
  3367. html:not([dark]) .CentAnni-style-selection-color {
  3368. --selection-color: var(--lightSelectionColor);
  3369. --selection-text-color: var(--light-theme-background-color);
  3370. }
  3371.  
  3372. .CentAnni-style-selection-color ::selection {
  3373. background: var(--selection-color);
  3374. color: var(--selection-text-color);
  3375. }
  3376. `;
  3377. // (document.documentElement || document).appendChild(styleSheet);
  3378. if (document.head) document.head.appendChild(styleSheet);
  3379. else {
  3380. document.addEventListener('DOMContentLoaded', () => {
  3381. document.head.appendChild(styleSheet);
  3382. });
  3383. }
  3384.  
  3385. // default configuration
  3386. const DEFAULT_CONFIG = {
  3387. YouTubeTranscriptExporter: true,
  3388. targetChatGPTUrl: 'https://ChatGPT.com/',
  3389. targetNotebookLMUrl: 'https://NotebookLM.Google.com/',
  3390. fileNamingFormat: 'title-channel',
  3391. includeTimestamps: true,
  3392. includeChapterHeaders: true,
  3393. openSameTab:true,
  3394. transcriptTimestamps: false,
  3395. preventBackgroundExecution: true,
  3396. ChatGPTPrompt: `You are an expert at summarizing YouTube video transcripts and are capable of analyzing and understanding a YouTuber's unique tone of voice and style from a transcript alone to mimic their communication style perfectly. Respond only in English while being mindful of American English spelling, vocabulary, and a casual, conversational tone. You prefer to use clauses instead of complete sentences while avoiding self-referential discourse signals like "I explain" or "I will show." Ignore advertisement, promotional, and sponsorship segments. Respond only in chat. Do not open a canvas. In your initial response, do not answer any question from the transcript, do not use the web tool, and avoid using colons. Do not hallucinate. Do not make up factual information. Do not speculate. Before you write your initial answer, take a moment to think about how you have to adopt your own writing to capture the YouTuber's specific word choices and communication stylestudy the provided transcript and utilize it as a style guide. Write as if you are the YouTuber speaking directly to your audience. Avoid any narrator-like phrases such as "the transcript" or "this video." Summarize the provided YouTube transcript into two distinct sections. The first section is a quick three-line bullet point overview, with each point fewer than 30 words, in a section called "### Key Takeaways:" and highlight important words by **bolding** themonly for this first section maintain a neutral tone. Then write the second section, a one-paragraph summary of at least 100 words while focusing on the main points and key takeaways into a section called "### One-Paragraph Summary:" and **bold** multiple phrases within the paragraph that together form an encapsulated, abridged version, that allows for quick identification and understanding of the core message.`,
  3397. buttonIcons: {
  3398. settings: '⋮',
  3399. download: '↓',
  3400. ChatGPT: '💬',
  3401. NotebookLM: '🎧'
  3402. },
  3403. buttonLeft1Text: 'ABC News',
  3404. buttonLeft1Url: 'https://www.youtube.com/@ABCNews/videos',
  3405. buttonLeft2Text: 'CNN',
  3406. buttonLeft2Url: 'https://www.youtube.com/@CNN/videos',
  3407. buttonLeft3Text: '',
  3408. buttonLeft3Url: 'https://www.youtube.com/@BBCNews/videos',
  3409. buttonLeft4Text: '',
  3410. buttonLeft4Url: 'https://www.youtube.com/@FoxNews/videos',
  3411. buttonLeft5Text: '',
  3412. buttonLeft5Url: 'https://www.youtube.com/@NBCNews/videos',
  3413. buttonLeft6Text: '',
  3414. buttonLeft6Url: 'https://www.youtube.com/@kcalnews/videos',
  3415. buttonLeft7Text: '',
  3416. buttonLeft7Url: 'https://www.youtube.com/foxla/videos',
  3417. buttonLeft8Text: '',
  3418. buttonLeft8Url: 'https://www.youtube.com/@earthcam/streams',
  3419. buttonLeft9Text: '',
  3420. buttonLeft9Url: 'https://www.youtube.com/@Formula1/videos',
  3421. buttonLeft10Text: '',
  3422. buttonLeft10Url: 'https://www.youtube.com/@OpenAI/videos',
  3423. mButtonText: '☰',
  3424. mButtonDisplay: false,
  3425. colorCodeVideosEnabled: true,
  3426. videosHideWatchedGlobal: false,
  3427. videosHideWatched: false,
  3428. videosOldOpacity: 0.5,
  3429. videosAgeColorPickerNewly: '#FFFF00',
  3430. videosAgeColorPickerRecent: '#FF9B00',
  3431. videosAgeColorPickerLately: '#006DFF',
  3432. videosAgeColorPickerLive: '#FF0000',
  3433. videosAgeColorPickerStreamed: '#FF0000',
  3434. videosAgeColorPickerUpcoming: '#32CD32',
  3435. WatchLaterColor: '#313131',
  3436. progressbarColorPicker: '#FF0033',
  3437. lightModeSelectionColor: '#000000',
  3438. darkModeSelectionColor: '#007CC3',
  3439. textTransform: 'normal-case',
  3440. defaultFontSize: 10,
  3441. videosWatchedOpacity: 0.5,
  3442. videosPerRow: 0,
  3443. playProgressColor: false,
  3444. videoTabView: true,
  3445. tabViewChapters: true,
  3446. progressBar: true,
  3447. playbackSpeed: true,
  3448. playbackSpeedValue: 1,
  3449. VerifiedArtist: false,
  3450. defaultQuality: 'auto',
  3451. defaultQualityPremium: false,
  3452. lastSeenVideo: true,
  3453. lastSeenVideoScroll: false,
  3454. lastSeenVideoColor: '#9400D3',
  3455. playlistLinks: false,
  3456. playlistTrashCan: false,
  3457. commentsNewFirst: false,
  3458. defaultTranscriptLanguage: 'auto',
  3459. autoOpenChapters: true,
  3460. autoOpenTranscript: false,
  3461. displayRemainingTime: true,
  3462. preventAutoplay: false,
  3463. hideVoiceSearch: false,
  3464. selectionColor: true,
  3465. hideCreateButton: false,
  3466. hideNotificationBtn: false,
  3467. hideNotificationBadge: false,
  3468. hideOwnAvatar: false,
  3469. hideBrandText: false,
  3470. hideJoinButton: false,
  3471. hidePlayNextButton: false,
  3472. hideAirplayButton: false,
  3473. hideShorts: false,
  3474. hideCommentsSection: false,
  3475. hideVideosSection: false,
  3476. redirectShorts: false,
  3477. hideAdSlots: false,
  3478. hidePayToWatch: false,
  3479. hideFreeWithAds: false,
  3480. hideMembersOnly: false,
  3481. hideLatestPosts: false,
  3482. hideShareButton: false,
  3483. hideHashtags: false,
  3484. hideInfoPanel: false,
  3485. hideRightSidebarSearch: false,
  3486. hideAddComment: false,
  3487. hideReplyButton: false,
  3488. hidePlaylistsHome: false,
  3489. hideNewsHome: false,
  3490. hideEndCards: false,
  3491. hideEndscreen: false,
  3492. gradientBottom: true,
  3493. smallSubscribeButton: false,
  3494. pureBWBackground: true,
  3495. removeScrubber: false,
  3496. disablePlayOnHover: false,
  3497. hideFundraiser: false,
  3498. hideMiniPlayer: false,
  3499. hideQueueBtn: false,
  3500. closeChatWindow: false,
  3501. displayFullTitle: true,
  3502. autoTheaterMode: false,
  3503. channelReindirizzare: false,
  3504. channelRSSBtn: false,
  3505. channelPlaylistBtn: true,
  3506. playlistDirectionBtns: true,
  3507. lnbHideHomeBtn: false,
  3508. lnbHideSubscriptionsBtn: false,
  3509. lnbHideHistoryBtn: false,
  3510. lnbHidePlaylistsBtn: false,
  3511. lnbHideVideosBtn: false,
  3512. lnbHideCoursesBtn: false,
  3513. lnbHideYPodcastsBtn: false,
  3514. lnbHideWlBtn: false,
  3515. lnbHideLikedVideosBtn: false,
  3516. lnbHideYouBtn: false,
  3517. lnbHideSubscriptionsSection: false,
  3518. lnbHideSubscriptionsTitle: false,
  3519. lnbHideMoreBtn: false,
  3520. lnbHideExploreSection: false,
  3521. lnbHideExploreTitle: false,
  3522. lnbHideTrendingBtn: false,
  3523. lnbHideMusicBtn: false,
  3524. lnbHideMoviesBtn: false,
  3525. lnbHideLiveBtn: false,
  3526. lnbHideGamingBtn: false,
  3527. lnbHideNewsBtn: false,
  3528. lnbHideSportsBtn: false,
  3529. lnbHideLearningBtn: false,
  3530. lnbHideFashionBtn: false,
  3531. lnbHidePodcastsBtn: false,
  3532. lnbHideMoreSection: false,
  3533. lnbHideMoreTitle: false,
  3534. lnbHideYtPremiumBtn: false,
  3535. lnbHideYtStudioBtn: false,
  3536. lnbHideYtMusicBtn: false,
  3537. lnbHideYtKidsBtn: false,
  3538. lnbHidePenultimateSection: false,
  3539. lnbHideSettingsBtn: false,
  3540. lnbHideReportHistoryBtn: false,
  3541. lnbHideHelpBtn: false,
  3542. lnbHideFeedbackBtn: false,
  3543. lnbHideFooter: false,
  3544. squareSearchBar: false,
  3545. squareDesign: false,
  3546. squareAvatars: false,
  3547. compactLayout: false
  3548. };
  3549.  
  3550. // load user configuration or use defaults
  3551. let storedConfig = {};
  3552. try {
  3553. storedConfig = await GM.getValue('USER_CONFIG', {});
  3554. } catch (error) {
  3555. showNotification('Error loading user save!');
  3556. console.error("YouTubeAlchemy: Error loading user configuration:", error);
  3557. }
  3558.  
  3559. let USER_CONFIG = {
  3560. ...DEFAULT_CONFIG,
  3561. ...storedConfig,
  3562. buttonIcons: {
  3563. ...DEFAULT_CONFIG.buttonIcons,
  3564. ...storedConfig.buttonIcons
  3565. }
  3566. };
  3567.  
  3568. // ensure CSS settings load immediately
  3569. let cssSettingsApplied = false;
  3570. function loadCSSsettings() {
  3571. const html = document.documentElement;
  3572. if (!html) return;
  3573.  
  3574. // features css
  3575. if (USER_CONFIG.progressBar) { html.classList.add('CentAnni-progress-bar'); } else { html.classList.remove('CentAnni-progress-bar'); }
  3576. if (USER_CONFIG.videoTabView) { html.classList.add('CentAnni-video-tabView'); } else { html.classList.remove('CentAnni-video-tabView'); }
  3577. if (USER_CONFIG.playbackSpeed) { html.classList.add('CentAnni-playback-speed'); } else { html.classList.remove('CentAnni-playback-speed'); }
  3578. if (USER_CONFIG.mButtonDisplay) { html.classList.add('CentAnni-style-hide-default-sidebar'); } else { html.classList.remove('CentAnni-style-hide-default-sidebar'); }
  3579. if (USER_CONFIG.videoTabView && USER_CONFIG.tabViewChapters) { html.classList.add('CentAnni-tabView-chapters'); } else { html.classList.remove('CentAnni-tabView-chapters'); }
  3580.  
  3581. // custom css
  3582. document.documentElement.style.setProperty('--itemsPerRow', USER_CONFIG.videosPerRow);
  3583. document.documentElement.style.setProperty('--textTransform', USER_CONFIG.textTransform);
  3584. document.documentElement.style.setProperty('--fontSize', `${USER_CONFIG.defaultFontSize}px`);
  3585. document.documentElement.style.setProperty('--watchedOpacity', USER_CONFIG.videosWatchedOpacity);
  3586. document.documentElement.style.setProperty('--progressBarColor', USER_CONFIG.progressbarColorPicker);
  3587. document.documentElement.style.setProperty('--lightSelectionColor', USER_CONFIG.lightModeSelectionColor);
  3588. document.documentElement.style.setProperty('--darkSelectionColor', USER_CONFIG.darkModeSelectionColor);
  3589.  
  3590. if (USER_CONFIG.pureBWBackground) { html.classList.add('CentAnni-style-pure-bg'); } else { html.classList.remove('CentAnni-style-pure-bg'); }
  3591. if (USER_CONFIG.hideShorts) { html.classList.add('CentAnni-style-hide-shorts'); } else { html.classList.remove('CentAnni-style-hide-shorts'); }
  3592. if (USER_CONFIG.closeChatWindow) { html.classList.add('CentAnni-close-live-chat'); } else { html.classList.remove('CentAnni-close-live-chat'); }
  3593. if (USER_CONFIG.videosPerRow !== 0) { html.classList.add('CentAnni-style-video-row'); } else { html.classList.remove('CentAnni-style-video-row'); }
  3594. if (USER_CONFIG.displayFullTitle) { html.classList.add('CentAnni-style-full-title'); } else { html.classList.remove('CentAnni-style-full-title'); }
  3595. if (USER_CONFIG.hideAdSlots) { html.classList.add('CentAnni-style-hide-ad-slots'); } else { html.classList.remove('CentAnni-style-hide-ad-slots'); }
  3596. if (USER_CONFIG.hideHashtags) { html.classList.add('CentAnni-style-hide-hashtags'); } else { html.classList.remove('CentAnni-style-hide-hashtags'); }
  3597. if (USER_CONFIG.squareDesign) { html.classList.add('CentAnni-style-square-design'); } else { html.classList.remove('CentAnni-style-square-design'); }
  3598. if (USER_CONFIG.hideQueueBtn) { html.classList.add('CentAnni-style-hide-queue-btn'); } else { html.classList.remove('CentAnni-style-hide-queue-btn'); }
  3599. if (USER_CONFIG.hideJoinButton) { html.classList.add('CentAnni-style-hide-join-btn'); } else { html.classList.remove('CentAnni-style-hide-join-btn'); }
  3600. if (USER_CONFIG.hideNewsHome) { html.classList.add('CentAnni-style-hide-news-home'); } else { html.classList.remove('CentAnni-style-hide-news-home'); }
  3601. if (USER_CONFIG.hideEndCards) { html.classList.add('CentAnni-style-hide-end-cards'); } else { html.classList.remove('CentAnni-style-hide-end-cards'); }
  3602. if (USER_CONFIG.squareAvatars) { html.classList.add('CentAnni-style-square-avatars'); } else { html.classList.remove('CentAnni-style-square-avatars'); }
  3603. if (USER_CONFIG.compactLayout) { html.classList.add('CentAnni-style-compact-layout'); } else { html.classList.remove('CentAnni-style-compact-layout'); }
  3604. if (USER_CONFIG.hideEndscreen) { html.classList.add('CentAnni-style-hide-endscreen'); } else { html.classList.remove('CentAnni-style-hide-endscreen'); }
  3605. if (USER_CONFIG.lnbHideWlBtn) { html.classList.add('CentAnni-style-lnb-hide-wl-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-wl-btn'); }
  3606. if (USER_CONFIG.hideOwnAvatar) { html.classList.add('CentAnni-style-hide-own-avatar'); } else { html.classList.remove('CentAnni-style-hide-own-avatar'); }
  3607. if (USER_CONFIG.hideShareButton) { html.classList.add('CentAnni-style-hide-share-btn'); } else { html.classList.remove('CentAnni-style-hide-share-btn'); }
  3608. if (USER_CONFIG.hideReplyButton) { html.classList.add('CentAnni-style-hide-reply-btn'); } else { html.classList.remove('CentAnni-style-hide-reply-btn'); }
  3609. if (USER_CONFIG.lnbHideFooter) { html.classList.add('CentAnni-style-lnb-hide-footer'); } else { html.classList.remove('CentAnni-style-lnb-hide-footer'); }
  3610. if (USER_CONFIG.hideInfoPanel) { html.classList.add('CentAnni-style-hide-info-panel'); } else { html.classList.remove('CentAnni-style-hide-info-panel'); }
  3611. if (USER_CONFIG.hideBrandText) { html.classList.add('CentAnni-style-hide-brand-text'); } else { html.classList.remove('CentAnni-style-hide-brand-text'); }
  3612. if (USER_CONFIG.selectionColor) { html.classList.add('CentAnni-style-selection-color'); } else { html.classList.remove('CentAnni-style-selection-color'); }
  3613. if (USER_CONFIG.removeScrubber) { html.classList.add('CentAnni-style-remove-scrubber'); } else { html.classList.remove('CentAnni-style-remove-scrubber'); }
  3614. if (USER_CONFIG.hideFundraiser) { html.classList.add('CentAnni-style-hide-fundraiser'); } else { html.classList.remove('CentAnni-style-hide-fundraiser'); }
  3615. if (USER_CONFIG.hideMiniPlayer) { html.classList.add('CentAnni-style-hide-miniplayer'); } else { html.classList.remove('CentAnni-style-hide-miniplayer'); }
  3616. if (USER_CONFIG.lnbHideYouBtn) { html.classList.add('CentAnni-style-lnb-hide-you-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-you-btn'); }
  3617. if (USER_CONFIG.hideAddComment) { html.classList.add('CentAnni-style-hide-add-comment'); } else { html.classList.remove('CentAnni-style-hide-add-comment'); }
  3618. if (USER_CONFIG.hideCreateButton) { html.classList.add('CentAnni-style-hide-create-btn'); } else { html.classList.remove('CentAnni-style-hide-create-btn'); }
  3619. if (USER_CONFIG.hideVideosSection) { html.classList.add('CentAnni-style-hide-videos-btn'); } else { html.classList.remove('CentAnni-style-hide-videos-btn'); }
  3620. if (USER_CONFIG.gradientBottom) { html.classList.add('CentAnni-style-gradient-bottom'); } else { html.classList.remove('CentAnni-style-gradient-bottom'); }
  3621. if (USER_CONFIG.hidePayToWatch) { html.classList.add('CentAnni-style-hide-pay-to-watch'); } else { html.classList.remove('CentAnni-style-hide-pay-to-watch'); }
  3622. if (USER_CONFIG.lnbHideLiveBtn) { html.classList.add('CentAnni-style-lnb-hide-live-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-live-btn'); }
  3623. if (USER_CONFIG.lnbHideNewsBtn) { html.classList.add('CentAnni-style-lnb-hide-news-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-news-btn'); }
  3624. if (USER_CONFIG.lnbHideMoreBtn) { html.classList.add('CentAnni-style-lnb-hide-more-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-more-btn'); }
  3625. if (USER_CONFIG.lnbHideHomeBtn) { html.classList.add('CentAnni-style-lnb-hide-home-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-home-btn'); }
  3626. if (USER_CONFIG.lnbHideHelpBtn) { html.classList.add('CentAnni-style-lnb-hide-help-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-help-btn'); }
  3627. if (USER_CONFIG.hideAirplayButton) { html.classList.add('CentAnni-style-hide-airplay-btn'); } else { html.classList.remove('CentAnni-style-hide-airplay-btn'); }
  3628. if (USER_CONFIG.hideMembersOnly) { html.classList.add('CentAnni-style-hide-members-only'); } else { html.classList.remove('CentAnni-style-hide-members-only'); }
  3629. if (USER_CONFIG.hideLatestPosts) { html.classList.add('CentAnni-style-hide-latest-posts'); } else { html.classList.remove('CentAnni-style-hide-latest-posts'); }
  3630. if (USER_CONFIG.hideVoiceSearch) { html.classList.add('CentAnni-style-hide-voice-search'); } else { html.classList.remove('CentAnni-style-hide-voice-search'); }
  3631. if (USER_CONFIG.squareSearchBar) { html.classList.add('CentAnni-style-square-search-bar'); } else { html.classList.remove('CentAnni-style-square-search-bar'); }
  3632. if (USER_CONFIG.hideFreeWithAds) { html.classList.add('CentAnni-style-hide-free-with-ads'); } else { html.classList.remove('CentAnni-style-hide-free-with-ads'); }
  3633. if (USER_CONFIG.lnbHideMusicBtn) { html.classList.add('CentAnni-style-lnb-hide-music-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-music-btn'); }
  3634. if (USER_CONFIG.hidePlayNextButton) { html.classList.add('CentAnni-style-hide-playnext-btn'); } else { html.classList.remove('CentAnni-style-hide-playnext-btn'); }
  3635. if (USER_CONFIG.hideCommentsSection) { html.classList.add('CentAnni-style-hide-comments-btn'); } else { html.classList.remove('CentAnni-style-hide-comments-btn'); }
  3636. if (USER_CONFIG.lnbHideMoviesBtn) { html.classList.add('CentAnni-style-lnb-hide-movies-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-movies-btn'); }
  3637. if (USER_CONFIG.lnbHideGamingBtn) { html.classList.add('CentAnni-style-lnb-hide-gaming-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-gaming-btn'); }
  3638. if (USER_CONFIG.lnbHideSportsBtn) { html.classList.add('CentAnni-style-lnb-hide-sports-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-sports-btn'); }
  3639. if (USER_CONFIG.lnbHideMoreTitle) { html.classList.add('CentAnni-style-lnb-hide-more-title'); } else { html.classList.remove('CentAnni-style-lnb-hide-more-title'); }
  3640. if (USER_CONFIG.lnbHideVideosBtn) { html.classList.add('CentAnni-style-lnb-hide-videos-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-videos-btn'); }
  3641. if (USER_CONFIG.playProgressColor) { html.classList.add('CentAnni-style-play-progress-color'); } else { html.classList.remove('CentAnni-style-play-progress-color'); }
  3642. if (USER_CONFIG.hidePlaylistsHome) { html.classList.add('CentAnni-style-hide-playlists-home'); } else { html.classList.remove('CentAnni-style-hide-playlists-home'); }
  3643. if (USER_CONFIG.lnbHideYtKidsBtn) { html.classList.add('CentAnni-style-lnb-hide-yt-kids-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-yt-kids-btn'); }
  3644. if (USER_CONFIG.lnbHideFashionBtn) { html.classList.add('CentAnni-style-lnb-hide-fashion-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-fashion-btn'); }
  3645. if (USER_CONFIG.lnbHideCoursesBtn) { html.classList.add('CentAnni-style-lnb-hide-courses-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-courses-btn'); }
  3646. if (USER_CONFIG.lnbHideHistoryBtn) { html.classList.add('CentAnni-style-lnb-hide-history-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-history-btn'); }
  3647. if (USER_CONFIG.smallSubscribeButton) { html.classList.add('CentAnni-style-small-subscribe-btn'); } else { html.classList.remove('CentAnni-style-small-subscribe-btn'); }
  3648. if (USER_CONFIG.lnbHideYtMusicBtn) { html.classList.add('CentAnni-style-lnb-hide-yt-music-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-yt-music-btn'); }
  3649. if (USER_CONFIG.lnbHideTrendingBtn) { html.classList.add('CentAnni-style-lnb-hide-trending-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-trending-btn'); }
  3650. if (USER_CONFIG.disablePlayOnHover) { html.classList.add('CentAnni-style-disable-play-on-hover'); } else { html.classList.remove('CentAnni-style-disable-play-on-hover'); }
  3651. if (USER_CONFIG.lnbHideLearningBtn) { html.classList.add('CentAnni-style-lnb-hide-learning-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-learning-btn'); }
  3652. if (USER_CONFIG.lnbHidePodcastsBtn) { html.classList.add('CentAnni-style-lnb-hide-podcasts-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-podcasts-btn'); }
  3653. if (USER_CONFIG.lnbHideMoreSection) { html.classList.add('CentAnni-style-lnb-hide-more-section'); } else { html.classList.remove('CentAnni-style-lnb-hide-more-section'); }
  3654. if (USER_CONFIG.lnbHideSettingsBtn) { html.classList.add('CentAnni-style-lnb-hide-settings-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-settings-btn'); }
  3655. if (USER_CONFIG.lnbHideFeedbackBtn) { html.classList.add('CentAnni-style-lnb-hide-feedback-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-feedback-btn'); }
  3656. if (USER_CONFIG.hideNotificationBtn) { html.classList.add('CentAnni-style-hide-notification-btn'); } else { html.classList.remove('CentAnni-style-hide-notification-btn'); }
  3657. if (USER_CONFIG.lnbHideYtStudioBtn) { html.classList.add('CentAnni-style-lnb-hide-yt-studio-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-yt-studio-btn'); }
  3658. if (USER_CONFIG.lnbHidePlaylistsBtn) { html.classList.add('CentAnni-style-lnb-hide-playlists-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-playlists-btn'); }
  3659. if (USER_CONFIG.lnbHideExploreTitle) { html.classList.add('CentAnni-style-lnb-hide-explore-title'); } else { html.classList.remove('CentAnni-style-lnb-hide-explore-title'); }
  3660. if (USER_CONFIG.lnbHideYtPremiumBtn) { html.classList.add('CentAnni-style-lnb-hide-yt-premium-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-yt-premium-btn'); }
  3661. if (USER_CONFIG.hideNotificationBadge) { html.classList.add('CentAnni-style-hide-notification-badge'); } else { html.classList.remove('CentAnni-style-hide-notification-badge'); }
  3662. if (USER_CONFIG.lnbHideExploreSection) { html.classList.add('CentAnni-style-lnb-hide-explore-section'); } else { html.classList.remove('CentAnni-style-lnb-hide-explore-section'); }
  3663. if (USER_CONFIG.lnbHideLikedVideosBtn) { html.classList.add('CentAnni-style-lnb-hide-liked-videos-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-liked-videos-btn'); }
  3664. if (USER_CONFIG.lnbHideYPodcastsBtn) { html.classList.add('CentAnni-style-lnb-hide-your-podcasts-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-your-podcasts-btn'); }
  3665. if (USER_CONFIG.hideRightSidebarSearch) { html.classList.add('CentAnni-style-search-hide-right-sidebar'); } else { html.classList.remove('CentAnni-style-search-hide-right-sidebar'); }
  3666. if (USER_CONFIG.videosHideWatchedGlobal) { html.classList.add('CentAnni-style-hide-watched-videos-global'); } else { html.classList.remove('CentAnni-style-hide-watched-videos-global'); }
  3667. if (USER_CONFIG.lnbHideSubscriptionsBtn) { html.classList.add('CentAnni-style-lnb-hide-subscriptions-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-subscriptions-btn'); }
  3668. if (USER_CONFIG.lnbHideReportHistoryBtn) { html.classList.add('CentAnni-style-lnb-hide-report-history-btn'); } else { html.classList.remove('CentAnni-style-lnb-hide-report-history-btn'); }
  3669. if (USER_CONFIG.lnbHideSubscriptionsTitle) { html.classList.add('CentAnni-style-lnb-hide-subscriptions-title'); } else { html.classList.remove('CentAnni-style-lnb-hide-subscriptions-title'); }
  3670. if (USER_CONFIG.lnbHidePenultimateSection) { html.classList.add('CentAnni-style-lnb-hide-penultimate-section'); } else { html.classList.remove('CentAnni-style-lnb-hide-penultimate-section'); }
  3671. if (USER_CONFIG.lnbHideSubscriptionsSection) { html.classList.add('CentAnni-style-lnb-hide-subscriptions-section'); } else { html.classList.remove('CentAnni-style-lnb-hide-subscriptions-section'); }
  3672.  
  3673. // color code videos
  3674. if (USER_CONFIG.videosHideWatched) { html.classList.add('CentAnni-style-hide-watched-videos'); } else { html.classList.remove('CentAnni-style-hide-watched-videos'); }
  3675. document.documentElement.style.setProperty('--liveVideo', USER_CONFIG.videosAgeColorPickerLive);
  3676. document.documentElement.style.setProperty('--streamedText', USER_CONFIG.videosAgeColorPickerStreamed);
  3677. document.documentElement.style.setProperty('--upComingVideo', USER_CONFIG.videosAgeColorPickerUpcoming);
  3678. document.documentElement.style.setProperty('--newlyVideo', USER_CONFIG.videosAgeColorPickerNewly);
  3679. document.documentElement.style.setProperty('--recentVideo', USER_CONFIG.videosAgeColorPickerRecent);
  3680. document.documentElement.style.setProperty('--latelyVideo', USER_CONFIG.videosAgeColorPickerLately);
  3681. document.documentElement.style.setProperty('--oldVideo', USER_CONFIG.videosOldOpacity);
  3682. if (USER_CONFIG.colorCodeVideosEnabled) document.documentElement.style.setProperty('--WatchLater', USER_CONFIG.WatchLaterColor);
  3683. document.documentElement.style.setProperty('--lastSeenVideoColor', USER_CONFIG.lastSeenVideoColor);
  3684.  
  3685. cssSettingsApplied = true;
  3686. } loadCSSsettings();
  3687.  
  3688. // create and show the settings modal
  3689. function showSettingsModal() {
  3690. const existingModal = document.getElementById('yt-transcript-settings-modal');
  3691. if (existingModal) {
  3692. existingModal.style.display = 'flex';
  3693. document.body.style.overflow = 'hidden';
  3694. return;
  3695. }
  3696.  
  3697. // create modal elements
  3698. const modal = document.createElement('div');
  3699. modal.id = 'yt-transcript-settings-modal';
  3700. modal.classList.add('CentAnni-overlay');
  3701.  
  3702. const modalContent = document.createElement('div');
  3703. modalContent.classList.add('modal-content');
  3704.  
  3705. // create header container
  3706. const headerWrapper = document.createElement('div');
  3707. headerWrapper.classList.add('header-wrapper');
  3708.  
  3709. // header
  3710. const header = document.createElement('a');
  3711. header.href = 'https://github.com/TimMacy/YouTubeAlchemy';
  3712. header.target = '_blank';
  3713. header.innerText = 'YouTube Alchemy';
  3714. header.title = 'GitHub Repository for YouTube Alchemy';
  3715. header.classList.add('header');
  3716. headerWrapper.appendChild(header);
  3717.  
  3718. // version
  3719. const versionSpan = document.createElement('span');
  3720. const scriptVersion = GM.info.script.version;
  3721. versionSpan.innerText = `v${scriptVersion}`;
  3722. versionSpan.classList.add('version-label');
  3723. headerWrapper.appendChild(versionSpan);
  3724.  
  3725. modalContent.appendChild(headerWrapper);
  3726.  
  3727. // create form elements for each setting
  3728. const form = document.createElement('form');
  3729. form.id = 'yt-transcript-settings-form';
  3730.  
  3731. // Button Icons
  3732. const iconsHeader = document.createElement('label');
  3733. iconsHeader.innerText = 'Button Icons:';
  3734. iconsHeader.classList.add('button-icons');
  3735. form.appendChild(iconsHeader);
  3736.  
  3737. const iconsContainer = document.createElement('div');
  3738. iconsContainer.classList.add('icons-container');
  3739.  
  3740. function createIconInputField(labelText, settingKey, settingValue, labelClass) {
  3741. const container = document.createElement('div');
  3742. container.classList.add('container-button');
  3743.  
  3744. const input = document.createElement('input');
  3745. const iconInputClass = `${settingKey}-input-field`;
  3746. input.type = 'text';
  3747. input.name = settingKey;
  3748. input.value = settingValue;
  3749. input.classList.add('container-button-input');
  3750. input.classList.add(iconInputClass);
  3751.  
  3752. const label = document.createElement('label');
  3753. label.innerText = labelText;
  3754. label.className = labelClass;
  3755. label.classList.add('container-button-label');
  3756.  
  3757. container.appendChild(input);
  3758. container.appendChild(label);
  3759.  
  3760. return container;
  3761. }
  3762.  
  3763. iconsContainer.appendChild(createIconInputField('NotebookLM', 'buttonIconNotebookLM', USER_CONFIG.buttonIcons.NotebookLM, 'label-NotebookLM'));
  3764. iconsContainer.appendChild(createIconInputField('ChatGPT', 'buttonIconChatGPT', USER_CONFIG.buttonIcons.ChatGPT, 'label-ChatGPT'));
  3765. iconsContainer.appendChild(createIconInputField('Download', 'buttonIconDownload', USER_CONFIG.buttonIcons.download, 'label-download'));
  3766. iconsContainer.appendChild(createIconInputField('Settings', 'buttonIconSettings', USER_CONFIG.buttonIcons.settings, 'label-settings'));
  3767.  
  3768. form.appendChild(iconsContainer);
  3769.  
  3770. // NotebookLM URL
  3771. form.appendChild(createInputField('NotebookLM URL (Copy transcript, then open the website):', 'targetNotebookLMUrl', USER_CONFIG.targetNotebookLMUrl, 'label-NotebookLM'));
  3772.  
  3773. // ChatGPT URL
  3774. form.appendChild(createInputField('ChatGPT URL (Copy transcript with the prompt, then open the website):', 'targetChatGPTUrl', USER_CONFIG.targetChatGPTUrl, 'label-ChatGPT'));
  3775.  
  3776. // SpacerTop10
  3777. const SpacerTop10 = document.createElement('div');
  3778. SpacerTop10.classList.add('spacer-10');
  3779. form.appendChild(SpacerTop10);
  3780.  
  3781. // File Naming Format
  3782. form.appendChild(createSelectField('Text File Naming Format:', 'label-download', 'fileNamingFormat', USER_CONFIG.fileNamingFormat, {
  3783. 'title-channel': 'Title - Channel.txt (default)',
  3784. 'channel-title': 'Channel - Title.txt',
  3785. 'date-title-channel': 'uploadDate - Title - Channel.txt',
  3786. 'date-channel-title': 'uploadDate - Channel - Title.txt',
  3787. }));
  3788.  
  3789. // transcript exporter
  3790. form.appendChild(createCheckboxField('Enable Transcript Exporter (default: yes)', 'YouTubeTranscriptExporter', USER_CONFIG.YouTubeTranscriptExporter));
  3791.  
  3792. // include Timestamps
  3793. form.appendChild(createCheckboxField('Include Timestamps in the Transcript (default: yes)', 'includeTimestamps', USER_CONFIG.includeTimestamps));
  3794.  
  3795. // include Chapter Headers
  3796. form.appendChild(createCheckboxField('Include Chapter Headers in the Transcript (default: yes)', 'includeChapterHeaders', USER_CONFIG.includeChapterHeaders));
  3797.  
  3798. // open in Same Tab
  3799. form.appendChild(createCheckboxField('Open Links in the Same Tab (default: yes)', 'openSameTab', USER_CONFIG.openSameTab));
  3800.  
  3801. // prevent execution in background tabs
  3802. form.appendChild(createCheckboxField('Important for Chrome! (default: yes)', 'preventBackgroundExecution', USER_CONFIG.preventBackgroundExecution));
  3803.  
  3804. // info for Chrome
  3805. const description = document.createElement('small');
  3806. description.innerText = 'Ensures compatibility and prevents early script execution in background tabs.\nWhile this feature is superfluous in Safari, it is essential for Chrome.';
  3807. description.classList.add('CentAnni-info-text');
  3808. form.appendChild(description);
  3809.  
  3810. // extra settings buttons
  3811. const extraSettings = document.createElement('div');
  3812. extraSettings.classList.add('extra-button-container');
  3813.  
  3814. const buttonsLeft = document.createElement('button');
  3815. buttonsLeft.type = 'button';
  3816. buttonsLeft.innerText = 'Links in Header';
  3817. buttonsLeft.classList.add('btn-style-settings');
  3818. buttonsLeft.onclick = () => showSubPanel(createLinksInHeaderContent(), 'linksInHeader');
  3819.  
  3820. const customCSSButton = document.createElement('button');
  3821. customCSSButton.type = 'button';
  3822. customCSSButton.innerText = 'Features & CSS';
  3823. customCSSButton.classList.add('btn-style-settings');
  3824. customCSSButton.onclick = () => showSubPanel(createCustomCSSContent(), 'createcustomCSS');
  3825.  
  3826. const colorCodeVideos = document.createElement('button');
  3827. colorCodeVideos.type = 'button';
  3828. colorCodeVideos.innerText = 'Color Code Videos';
  3829. colorCodeVideos.classList.add('btn-style-settings');
  3830. colorCodeVideos.onclick = () => showSubPanel(createColorCodeVideosContent(), 'colorCodeVideos');
  3831.  
  3832. extraSettings.appendChild(buttonsLeft)
  3833. extraSettings.appendChild(customCSSButton);
  3834. extraSettings.appendChild(colorCodeVideos);
  3835.  
  3836. form.appendChild(extraSettings);
  3837.  
  3838. // ChatGPT prompt
  3839. const promptContainer = createTextareaField('ChatGPT Prompt:', 'ChatGPTPrompt', USER_CONFIG.ChatGPTPrompt, 'label-ChatGPT');
  3840.  
  3841. // reset ChatGPT prompt
  3842. const resetText = document.createElement('span');
  3843. resetText.innerText = 'Reset Prompt';
  3844. resetText.className = 'reset-prompt-text';
  3845. resetText.addEventListener('click', function() {
  3846. const textarea = promptContainer.querySelector('textarea[name="ChatGPTPrompt"]');
  3847. if (textarea) textarea.value = DEFAULT_CONFIG.ChatGPTPrompt;
  3848. });
  3849.  
  3850. const label = promptContainer.querySelector('label');
  3851. promptContainer.insertBefore(resetText, label);
  3852.  
  3853. form.appendChild(promptContainer);
  3854.  
  3855. // action buttons container
  3856. const buttonContainer = document.createElement('div');
  3857. buttonContainer.classList.add('button-container-end');
  3858.  
  3859. // export and import button container
  3860. const exportImportContainer = document.createElement('div');
  3861. exportImportContainer.classList.add('button-container-backup');
  3862.  
  3863. const exportButton = document.createElement('button');
  3864. exportButton.type = 'button';
  3865. exportButton.innerText = 'Export Settings';
  3866. exportButton.classList.add('btn-style-settings');
  3867. exportButton.onclick = exportSettings;
  3868.  
  3869. const importButton = document.createElement('button');
  3870. importButton.type = 'button';
  3871. importButton.innerText = 'Import Settings';
  3872. importButton.classList.add('btn-style-settings');
  3873. importButton.onclick = importSettings;
  3874.  
  3875. // Copyright
  3876. const copyright = document.createElement('a');
  3877. copyright.href = 'https://github.com/TimMacy';
  3878. copyright.target = '_blank';
  3879. copyright.innerText = '© 2024 Tim Macy';
  3880. copyright.title = 'Copyright by Tim Macy';
  3881. copyright.classList.add('copyright');
  3882.  
  3883. const spacer = document.createElement('div');
  3884. spacer.style = 'flex: 1;';
  3885.  
  3886. // Save, Reset, and Cancel Buttons
  3887. const buttonContainerSettings = document.createElement('div');
  3888. buttonContainerSettings.classList.add('button-container-settings');
  3889.  
  3890. const saveButton = document.createElement('button');
  3891. saveButton.type = 'button';
  3892. saveButton.innerText = 'Save';
  3893. saveButton.classList.add('btn-style-settings');
  3894. saveButton.onclick = saveSettings;
  3895.  
  3896. const resetButton = document.createElement('button');
  3897. resetButton.type = 'button';
  3898. resetButton.innerText = 'Reset to Default';
  3899. resetButton.classList.add('btn-style-settings');
  3900. resetButton.onclick = async () => {
  3901. const userConfirmed = window.confirm("All settings will be reset to their default values.");
  3902. if (!userConfirmed) { return; }
  3903.  
  3904. try {
  3905. USER_CONFIG = { ...DEFAULT_CONFIG };
  3906. await GM.setValue('USER_CONFIG', USER_CONFIG);
  3907. showNotification('Settings have been reset to default!');
  3908. document.getElementById('yt-transcript-settings-modal').style.display = 'none';
  3909. setTimeout(() => { location.reload(); }, 1000);
  3910. } catch (error) {
  3911. showNotification('Error resetting settings to default!');
  3912. console.error("YouTubeAlchemy: Error resetting settings to default:", error);
  3913. }
  3914. };
  3915.  
  3916. const cancelButton = document.createElement('button');
  3917. cancelButton.type = 'button';
  3918. cancelButton.innerText = 'Cancel';
  3919. cancelButton.classList.add('btn-style-settings');
  3920. cancelButton.onclick = () => { modal.style.display = 'none'; document.body.style.overflow = ''; };
  3921.  
  3922. exportImportContainer.appendChild(exportButton);
  3923. exportImportContainer.appendChild(importButton);
  3924.  
  3925. buttonContainerSettings.appendChild(copyright);
  3926. buttonContainerSettings.appendChild(spacer);
  3927. buttonContainerSettings.appendChild(saveButton);
  3928. buttonContainerSettings.appendChild(resetButton);
  3929. buttonContainerSettings.appendChild(cancelButton);
  3930.  
  3931. buttonContainer.appendChild(exportImportContainer);
  3932. buttonContainer.appendChild(buttonContainerSettings);
  3933.  
  3934. form.appendChild(buttonContainer);
  3935. modalContent.appendChild(form);
  3936. modal.appendChild(modalContent);
  3937. document.body.appendChild(modal);
  3938. document.body.style.overflow = 'hidden';
  3939.  
  3940. // text area scroll on click
  3941. let animationTriggered = false;
  3942.  
  3943. document.querySelector('.chatgpt-prompt-textarea').addEventListener('click', function () {
  3944. if (animationTriggered) return;
  3945. animationTriggered = true;
  3946.  
  3947. const modalContent = this.closest('#yt-transcript-settings-form');
  3948. if (!modalContent) { animationTriggered = false; return; }
  3949.  
  3950. const textArea = this;
  3951. const buttons = modalContent.querySelector('.button-container-end');
  3952. const startHeight = 65;
  3953. const endHeight = 569;
  3954. const duration = 700;
  3955.  
  3956. const modalContentRect = modalContent.getBoundingClientRect();
  3957. const textAreaRect = textArea.getBoundingClientRect();
  3958. const buttonsRect = buttons.getBoundingClientRect();
  3959.  
  3960. const textAreaTop = textAreaRect.top - modalContentRect.top + modalContent.scrollTop;
  3961. const buttonsBottom = buttonsRect.bottom - modalContentRect.top + modalContent.scrollTop;
  3962. const contentHeightAfterExpansion = buttonsBottom + (endHeight - startHeight);
  3963. const modalVisibleHeight = modalContent.clientHeight;
  3964. const contentWillFit = contentHeightAfterExpansion <= modalVisibleHeight;
  3965. const maxScrollTop = textAreaTop;
  3966.  
  3967. let desiredScrollTop;
  3968.  
  3969. if (contentWillFit) { desiredScrollTop = contentHeightAfterExpansion - modalVisibleHeight;
  3970. } else { desiredScrollTop = buttonsBottom + (endHeight - startHeight) - modalVisibleHeight; }
  3971.  
  3972. const newScrollTop = Math.min(desiredScrollTop, maxScrollTop);
  3973. const startScrollTop = modalContent.scrollTop;
  3974. const scrollDistance = newScrollTop - startScrollTop;
  3975. const startTime = performance.now();
  3976.  
  3977. function animateScroll(currentTime) {
  3978. const elapsedTime = currentTime - startTime;
  3979. const progress = Math.min(elapsedTime / duration, 1);
  3980.  
  3981. const easeProgress = progress < 0.5
  3982. ? 2 * progress * progress
  3983. : -1 + (4 - 2 * progress) * progress;
  3984.  
  3985. const currentScrollTop = startScrollTop + scrollDistance * easeProgress;
  3986. modalContent.scrollTop = currentScrollTop;
  3987.  
  3988. if (progress < 1) { requestAnimationFrame(animateScroll);
  3989. } else { animationTriggered = false; }
  3990. }
  3991.  
  3992. requestAnimationFrame(animateScroll);
  3993. });
  3994.  
  3995. // close modal on overlay click
  3996. function closeModalOverlayClickHandler(event) {
  3997. const mainModal = document.getElementById('yt-transcript-settings-modal');
  3998. const openSubPanel = document.querySelector('.sub-panel-overlay.active');
  3999.  
  4000. if (openSubPanel && event.target === openSubPanel) {
  4001. openSubPanel.classList.remove('active');
  4002. return;
  4003. }
  4004.  
  4005. if (mainModal && event.target === mainModal) {
  4006. mainModal.style.display = 'none';
  4007. document.body.style.overflow = '';
  4008. return;
  4009. }
  4010. }
  4011. document.addEventListener('click', closeModalOverlayClickHandler);
  4012.  
  4013. // close modal with ESC key
  4014. const escKeyListener = function(event) {
  4015. if (event.key === 'Escape' && event.type === 'keydown') {
  4016. const openSubPanel = document.querySelector('.sub-panel-overlay.active');
  4017.  
  4018. if (openSubPanel) {
  4019. openSubPanel.classList.remove('active');
  4020. } else {
  4021. const modal = document.getElementById('yt-transcript-settings-modal');
  4022. if (modal) {
  4023. modal.style.display = 'none';
  4024. document.body.style.overflow = '';
  4025. }
  4026. }
  4027. }
  4028. };
  4029.  
  4030. window.addEventListener('keydown', escKeyListener);
  4031.  
  4032. document.addEventListener('yt-navigate-start', () => {
  4033. window.removeEventListener('keydown', escKeyListener);
  4034. document.removeEventListener('click', closeModalOverlayClickHandler);
  4035. });
  4036.  
  4037. // sub-panels
  4038. function showSubPanel(panelContent, panelId) {
  4039. let subPanelOverlay = document.querySelector(`.sub-panel-overlay[data-panel-id="${panelId}"]`);
  4040.  
  4041. if (!subPanelOverlay){
  4042. subPanelOverlay = document.createElement('div');
  4043. subPanelOverlay.classList.add('sub-panel-overlay');
  4044. subPanelOverlay.setAttribute('data-panel-id', panelId);
  4045.  
  4046. const subPanel = document.createElement('div');
  4047. subPanel.classList.add('sub-panel');
  4048.  
  4049. const closeButton = document.createElement('button');
  4050. closeButton.type = 'button';
  4051. closeButton.innerText = 'Close';
  4052. closeButton.classList.add('btn-style-settings');
  4053. closeButton.onclick = () => { subPanelOverlay.classList.remove('active'); };
  4054. subPanel.appendChild(closeButton);
  4055.  
  4056. if (panelContent) { subPanel.appendChild(panelContent); }
  4057.  
  4058. subPanelOverlay.appendChild(subPanel);
  4059. document.body.appendChild(subPanelOverlay);
  4060. }
  4061. subPanelOverlay.classList.add('active');
  4062. }
  4063.  
  4064. // links in header
  4065. function createLinksInHeaderContent() {
  4066. const form = document.createElement('form');
  4067. form.id = 'links-in-header-form';
  4068.  
  4069. const subPanelHeader = document.createElement('div');
  4070. subPanelHeader.classList.add('sub-panel-header');
  4071. subPanelHeader.textContent = 'Configure Links in Header';
  4072. form.appendChild(subPanelHeader);
  4073.  
  4074. const infoLinksHeader = document.createElement('small');
  4075. infoLinksHeader.innerText = "Up to ten links can be added next to the YouTube logo. An empty 'Link Text' field won't insert the link into the header.\nIf the navigation bar is hidden, a replacement icon will prepend the links, while retaining the default functionality of opening and closing the sidebar.";
  4076. infoLinksHeader.classList.add('CentAnni-info-text');
  4077. form.appendChild(infoLinksHeader);
  4078.  
  4079. const sidebarContainer = document.createElement('div');
  4080. sidebarContainer.classList.add('sidebar-container');
  4081.  
  4082. // hide left navigation bar and replacement icon
  4083. const checkboxField = createCheckboxField('Hide Navigation Bar', 'mButtonDisplay', USER_CONFIG.mButtonDisplay);
  4084. sidebarContainer.appendChild(checkboxField);
  4085.  
  4086. const inputField = createInputField('Navigation Bar Replacement Icon', 'mButtonText', USER_CONFIG.mButtonText, 'label-mButtonText');
  4087. sidebarContainer.appendChild(inputField);
  4088.  
  4089. form.appendChild(sidebarContainer);
  4090.  
  4091. // function to create a link input group
  4092. function createButtonInputGroup(linkNumber) {
  4093. const container = document.createElement('div');
  4094. container.classList.add('links-header-container');
  4095.  
  4096. // link text
  4097. const textField = createInputField(`Link ${linkNumber} Text`, `buttonLeft${linkNumber}Text`, USER_CONFIG[`buttonLeft${linkNumber}Text`], `label-buttonLeft${linkNumber}Text`);
  4098. container.appendChild(textField);
  4099.  
  4100. // link URL
  4101. const urlField = createInputField(`Link ${linkNumber} URL`, `buttonLeft${linkNumber}Url`, USER_CONFIG[`buttonLeft${linkNumber}Url`], `label-buttonLeft${linkNumber}Url`);
  4102. container.appendChild(urlField);
  4103.  
  4104. return container;
  4105. }
  4106.  
  4107. // create input groups for links 1 through 6
  4108. for (let i = 1; i <= 10; i++) {
  4109. form.appendChild(createButtonInputGroup(i));
  4110. }
  4111.  
  4112. return form;
  4113. }
  4114.  
  4115. // custom css
  4116. function createCustomCSSContent() {
  4117. const form = document.createElement('form');
  4118. form.id = 'custom-css-form';
  4119.  
  4120. const subPanelHeader = document.createElement('div');
  4121. subPanelHeader.classList.add('sub-panel-header');
  4122. subPanelHeader.textContent = 'Customize YouTube Appearance and Manage Features';
  4123. form.appendChild(subPanelHeader);
  4124.  
  4125. // general
  4126. const general = document.createElement('label');
  4127. general.innerText = 'General';
  4128. general.classList.add('button-icons', 'features-text');
  4129. form.appendChild(general);
  4130.  
  4131. // dim watched videos
  4132. const videosWatchedContainer = createSliderInputField( 'Change Opacity of Watched Videos (default 0.5):', 'videosWatchedOpacity', USER_CONFIG.videosWatchedOpacity, '0', '1', '0.1' );
  4133. form.appendChild(videosWatchedContainer);
  4134.  
  4135. // title text transform
  4136. form.appendChild(createSelectField('Title Case:', 'label-Text-Transform', 'textTransform', USER_CONFIG.textTransform, {
  4137. 'uppercase': 'uppercase - THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.',
  4138. 'lowercase': 'lowercase - the quick brown fox jumps over the lazy dog.',
  4139. 'capitalize': 'capitalize - The Quick Brown Fox Jumps Over The Lazy Dog.',
  4140. 'normal-case': 'normal-case (default) - The quick brown fox jumps over the lazy dog.',
  4141. }));
  4142.  
  4143. // default video quality
  4144. form.appendChild(createSelectField( 'Video Quality:', 'label-Video-Quality', 'defaultQuality', USER_CONFIG.defaultQuality, {
  4145. 'auto': 'Auto (default)',
  4146. 'highest': 'Highest Available',
  4147. 'highres': '4320p - 8K',
  4148. 'hd2160': '2160p - 4K',
  4149. 'hd1440': '1440p - QHD',
  4150. 'hd1080': '1080p - FHD',
  4151. 'hd720': '720p - HD',
  4152. 'large': '480p',
  4153. 'medium': '360p',
  4154. 'small': '240p',
  4155. 'tiny': '144p',
  4156. 'lowest': 'Lowest Available'
  4157. }));
  4158.  
  4159. // default transcript language
  4160. form.appendChild(createSelectField( 'Transcript Language:', 'label-Transcript-Language', 'defaultTranscriptLanguage', USER_CONFIG.defaultTranscriptLanguage, {
  4161. 'auto': 'Auto (default)',
  4162. 'english': 'English',
  4163. 'spanish': 'Spanish - Español',
  4164. 'hindi': 'Hindi - हिन्दी',
  4165. 'portuguese': 'Portuguese - Português',
  4166. 'german': 'German - Deutsch',
  4167. 'french': 'French - Français',
  4168. 'italian': 'Italian - Italiano',
  4169. 'dutch': 'Dutch - Nederlands',
  4170. 'polish': 'Polish - Polski',
  4171. 'hebrew': 'Hebrew - עברית'
  4172. }));
  4173.  
  4174. // font size
  4175. const defaultFontSizeField = createNumberInputField('Set Font Size (default: 10)', 'defaultFontSize', USER_CONFIG.defaultFontSize);
  4176. form.appendChild(defaultFontSizeField);
  4177.  
  4178. // videos per row
  4179. const videosPerRow = createNumberInputField("Number of Videos per Row (default: 0 | dynamic based on available space)", 'videosPerRow', USER_CONFIG.videosPerRow);
  4180. form.appendChild(videosPerRow);
  4181.  
  4182. // playback speed
  4183. const playbackSpeedContainer = document.createElement('div');
  4184. playbackSpeedContainer.className = 'playback-speed-container';
  4185.  
  4186. const playbackSpeed = createCheckboxField('Enabled (default: yes)\nkey toggles: A: -0.25x | S: toggle 1x/set speed | D: +0.25x', 'playbackSpeed', USER_CONFIG.playbackSpeed);
  4187. const playbackSpeedValue = createNumberInputField('Set Playback Speed for VODs\n(defaults to 1x for live videos)', 'playbackSpeedValue', USER_CONFIG.playbackSpeedValue);
  4188.  
  4189. playbackSpeedContainer.appendChild(playbackSpeedValue);
  4190. playbackSpeedContainer.appendChild(playbackSpeed);
  4191. form.appendChild(playbackSpeedContainer);
  4192.  
  4193. // features
  4194. const features = document.createElement('label');
  4195. features.innerText = 'Features';
  4196. features.classList.add('button-icons', 'features-text');
  4197. form.appendChild(features);
  4198.  
  4199. // auto theater mode
  4200. const autoTheaterMode = createCheckboxField('Auto Theater Mode (default: no)', 'autoTheaterMode', USER_CONFIG.autoTheaterMode);
  4201. form.appendChild(autoTheaterMode);
  4202.  
  4203. // prevent autoplay
  4204. const preventAutoplay = createCheckboxField('Prevent Autoplay (default: no)', 'preventAutoplay', USER_CONFIG.preventAutoplay);
  4205. form.appendChild(preventAutoplay);
  4206.  
  4207. // disable play on hover
  4208. const disablePlayOnHover = createCheckboxField('Disable Play on Hover (default: no)', 'disablePlayOnHover', USER_CONFIG.disablePlayOnHover);
  4209. form.appendChild(disablePlayOnHover);
  4210.  
  4211. // close chat window
  4212. const closeChatWindow = createCheckboxField('Auto Close Initial Chat Windows (default: no)', 'closeChatWindow', USER_CONFIG.closeChatWindow);
  4213. form.appendChild(closeChatWindow);
  4214.  
  4215. // channel default videos page
  4216. const channelReindirizzare = createCheckboxField('"Videos" Tab as Default on Channel Page (default: no)', 'channelReindirizzare', USER_CONFIG.channelReindirizzare);
  4217. form.appendChild(channelReindirizzare);
  4218.  
  4219. // rss feed button on channel page
  4220. const channelRSSBtn = createCheckboxField('Add RSS Feed Button to Channel Pages (default: no)', 'channelRSSBtn', USER_CONFIG.channelRSSBtn);
  4221. form.appendChild(channelRSSBtn);
  4222.  
  4223. // playlist button on channel page
  4224. const channelPlaylistBtn = createCheckboxField('Add Playlist Buttons to Channel Pages (default: yes)', 'channelPlaylistBtn', USER_CONFIG.channelPlaylistBtn);
  4225. form.appendChild(channelPlaylistBtn);
  4226.  
  4227. // playlist direction buttons in playlist panel
  4228. const playlistDirectionBtns = createCheckboxField('Add Direction Buttons to Playlist Panels (default: yes)', 'playlistDirectionBtns', USER_CONFIG.playlistDirectionBtns);
  4229. form.appendChild(playlistDirectionBtns);
  4230.  
  4231. // open playlist videos without being in a playlist
  4232. const playlistLinks = createCheckboxField('Open Playlist Videos Without Being in a Playlist When Clicking the Thumbnail or Title (default: no)', 'playlistLinks', USER_CONFIG.playlistLinks);
  4233. form.appendChild(playlistLinks);
  4234.  
  4235. // channel default videos page
  4236. const playlistTrashCan = createCheckboxField('Show Trash Can Icon on Owned Playlists to Quickly Remove Videos (default: no)', 'playlistTrashCan', USER_CONFIG.playlistTrashCan);
  4237. form.appendChild(playlistTrashCan);
  4238.  
  4239. // sort comments new first
  4240. const commentsNewFirst = createCheckboxField('Sort Comments to "Newest First" (default: no)', 'commentsNewFirst', USER_CONFIG.commentsNewFirst);
  4241. form.appendChild(commentsNewFirst);
  4242.  
  4243. // auto open chapter panel
  4244. const autoOpenChapters = createCheckboxField('Automatically Open Chapter Panels (default: yes)', 'autoOpenChapters', USER_CONFIG.autoOpenChapters);
  4245. form.appendChild(autoOpenChapters);
  4246.  
  4247. // auto open transcript panel
  4248. const autoOpenTranscript = createCheckboxField('Automatically Open Transcript Panels (default: no)', 'autoOpenTranscript', USER_CONFIG.autoOpenTranscript);
  4249. form.appendChild(autoOpenTranscript);
  4250.  
  4251. // enable transcript timestamps
  4252. const transcriptTimestamps = createCheckboxField('Automatically Enable Timestamps in Transcript Panels (default: no)', 'transcriptTimestamps', USER_CONFIG.transcriptTimestamps);
  4253. form.appendChild(transcriptTimestamps);
  4254.  
  4255. // 1x playback speed for music videos
  4256. const VerifiedArtist = createCheckboxField('Maintain 1x Playback Speed for Verified Artist Music Videos (default: no)', 'VerifiedArtist', USER_CONFIG.VerifiedArtist);
  4257. form.appendChild(VerifiedArtist);
  4258.  
  4259. // 1080p enhanced bitrate
  4260. const defaultQualityPremium = createCheckboxField('Use Enhanced Bitrate for 1080p Videos | Premium Required (default: no)', 'defaultQualityPremium', USER_CONFIG.defaultQualityPremium);
  4261. form.appendChild(defaultQualityPremium);
  4262.  
  4263. // persistent progress bar
  4264. const progressBar = createCheckboxField('Persistent Progress Bar with Chapter Markers and SponsorBlock Support (default: yes)', 'progressBar', USER_CONFIG.progressBar);
  4265. form.appendChild(progressBar);
  4266.  
  4267. // display remaining time minus SponsorBlock segments
  4268. const displayRemainingTime = createCheckboxField('Display Remaining Time Under Videos Adjusted for Playback Speed (default: yes)', 'displayRemainingTime', USER_CONFIG.displayRemainingTime);
  4269. form.appendChild(displayRemainingTime);
  4270.  
  4271. // info for remaining time minus segments beta
  4272. const descriptionRemainingTimeBeta = document.createElement('small');
  4273. descriptionRemainingTimeBeta.innerText = 'To also include Skipped SponsorBlock Segments, ensure "Show time with skips removed" is enabled in SponsorBlock Settings under "Interface."';
  4274. descriptionRemainingTimeBeta.classList.add('CentAnni-info-text');
  4275. form.appendChild(descriptionRemainingTimeBeta);
  4276.  
  4277. // layout changes
  4278. const layoutChanges = document.createElement('label');
  4279. layoutChanges.innerText = 'Layout Changes';
  4280. layoutChanges.classList.add('button-icons', 'features-text');
  4281. form.appendChild(layoutChanges);
  4282.  
  4283. // tab view on video page
  4284. const videoTabView = createCheckboxField('Tab View on Video Page (default: yes)', 'videoTabView', USER_CONFIG.videoTabView);
  4285. form.appendChild(videoTabView);
  4286.  
  4287. // show chapters - only in tab view
  4288. const tabViewChapters = createCheckboxField('Show Chapters Under Videos | Only Works with Tab View Enabled! (default: yes)', 'tabViewChapters', USER_CONFIG.tabViewChapters);
  4289. form.appendChild(tabViewChapters);
  4290.  
  4291. // hide comment section
  4292. const hideCommentsSection = createCheckboxField('Hide Comments Section (default: no)', 'hideCommentsSection', USER_CONFIG.hideCommentsSection);
  4293. form.appendChild(hideCommentsSection);
  4294.  
  4295. // hide related video section
  4296. const hideVideosSection = createCheckboxField('Hide Suggested Videos (default: no)', 'hideVideosSection', USER_CONFIG.hideVideosSection);
  4297. form.appendChild(hideVideosSection);
  4298.  
  4299. // square and compact search bar
  4300. const squareSearchBar = createCheckboxField('Square and Compact Search Bar (default: no)', 'squareSearchBar', USER_CONFIG.squareSearchBar);
  4301. form.appendChild(squareSearchBar);
  4302.  
  4303. // square design
  4304. const squareDesign = createCheckboxField('Square Design (default: no)', 'squareDesign', USER_CONFIG.squareDesign);
  4305. form.appendChild(squareDesign);
  4306.  
  4307. // square avatars
  4308. const squareAvatars = createCheckboxField('Square Avatars (default: no)', 'squareAvatars', USER_CONFIG.squareAvatars);
  4309. form.appendChild(squareAvatars);
  4310.  
  4311. // compact layout
  4312. const compactLayout = createCheckboxField('Compact Layout (default: no)', 'compactLayout', USER_CONFIG.compactLayout);
  4313. form.appendChild(compactLayout);
  4314.  
  4315. // hide shorts
  4316. const hideShorts = createCheckboxField('Hide Shorts (default: no)', 'hideShorts', USER_CONFIG.hideShorts);
  4317. form.appendChild(hideShorts);
  4318.  
  4319. // redirect shorts
  4320. const redirectShorts = createCheckboxField('Redirect Shorts to the Standard Video Page (default: no)', 'redirectShorts', USER_CONFIG.redirectShorts);
  4321. form.appendChild(redirectShorts);
  4322.  
  4323. // hide ad slot
  4324. const hideAdSlots = createCheckboxField('Hide Ad Slots on the Home Page (default: no)', 'hideAdSlots', USER_CONFIG.hideAdSlots);
  4325. form.appendChild(hideAdSlots);
  4326.  
  4327. // hide pay to watch
  4328. const hidePayToWatch = createCheckboxField('Hide "Pay to Watch" Featured Videos on the Home Page (default: no)', 'hidePayToWatch', USER_CONFIG.hidePayToWatch);
  4329. form.appendChild(hidePayToWatch);
  4330.  
  4331. // hide free with ads
  4332. const hideFreeWithAds = createCheckboxField('Hide "Free with ads" Videos on the Home Page (default: no)', 'hideFreeWithAds', USER_CONFIG.hideFreeWithAds);
  4333. form.appendChild(hideFreeWithAds);
  4334.  
  4335. // hide members only
  4336. const hideMembersOnly = createCheckboxField('Hide Members Only Featured Videos on the Home Page (default: no)', 'hideMembersOnly', USER_CONFIG.hideMembersOnly);
  4337. form.appendChild(hideMembersOnly);
  4338.  
  4339. // hide latest post from . . .
  4340. const hideLatestPosts = createCheckboxField('Hide "Latest posts from . . ." on Search Page (default: no)', 'hideLatestPosts', USER_CONFIG.hideLatestPosts);
  4341. form.appendChild(hideLatestPosts);
  4342.  
  4343. // modify or hide ui elements
  4344. const uielements = document.createElement('label');
  4345. uielements.innerText = 'Modify or Hide UI Elements';
  4346. uielements.classList.add('button-icons', 'features-text');
  4347. form.appendChild(uielements);
  4348.  
  4349. // hide voice search button
  4350. const hideVoiceSearch = createCheckboxField('Hide "Voice Search" Button (default: no)', 'hideVoiceSearch', USER_CONFIG.hideVoiceSearch);
  4351. form.appendChild(hideVoiceSearch);
  4352.  
  4353. // hide create button
  4354. const hideCreateButton = createCheckboxField('Hide "Create" Button (default: no)', 'hideCreateButton', USER_CONFIG.hideCreateButton);
  4355. form.appendChild(hideCreateButton);
  4356.  
  4357. // hide notification button
  4358. const hideNotificationBtn = createCheckboxField('Hide "Notification" Button (default: no)', 'hideNotificationBtn', USER_CONFIG.hideNotificationBtn);
  4359. form.appendChild(hideNotificationBtn);
  4360.  
  4361. // hide notification count
  4362. const hideNotificationBadge = createCheckboxField('Hide Notification Badge (default: no)', 'hideNotificationBadge', USER_CONFIG.hideNotificationBadge);
  4363. form.appendChild(hideNotificationBadge);
  4364.  
  4365. // hide avatar
  4366. const hideOwnAvatar = createCheckboxField('Hide Own Avatar in the Header (default: no)', 'hideOwnAvatar', USER_CONFIG.hideOwnAvatar);
  4367. form.appendChild(hideOwnAvatar);
  4368.  
  4369. // hide YouTube brand text within the header
  4370. const hideBrandText = createCheckboxField('Hide YouTube Brand Text in the Header (default: no)', 'hideBrandText', USER_CONFIG.hideBrandText);
  4371. form.appendChild(hideBrandText);
  4372.  
  4373. // small subscribed button
  4374. const smallSubscribeButton = createCheckboxField('Small Subscribed Button Under a Video—Displays Only the Notification Icon (default: no)', 'smallSubscribeButton', USER_CONFIG.smallSubscribeButton);
  4375. form.appendChild(smallSubscribeButton);
  4376.  
  4377. // hide join button
  4378. const hideJoinButton = createCheckboxField('Hide Join Button under a Videos and on Channel Pages (default: no)', 'hideJoinButton', USER_CONFIG.hideJoinButton);
  4379. form.appendChild(hideJoinButton);
  4380.  
  4381. // display full title
  4382. const displayFullTitle = createCheckboxField('Display Full Titles (default: yes)', 'displayFullTitle', USER_CONFIG.displayFullTitle);
  4383. form.appendChild(displayFullTitle);
  4384.  
  4385. // custom selection color - toggle | light mode | dark mode
  4386. const selectionColorContainer = document.createElement('div');
  4387. selectionColorContainer.classList.add('videos-colorpicker-container', 'selection-color-container');
  4388.  
  4389. const selectionColor = createCheckboxField('Custom Selection Color (default: yes)', 'selectionColor', USER_CONFIG.selectionColor);
  4390. selectionColorContainer.appendChild(selectionColor);
  4391.  
  4392. const lightModeColorPicker = createColorPicker('Light Mode', 'lightModeSelectionColor');
  4393. selectionColorContainer.appendChild(lightModeColorPicker);
  4394.  
  4395. const darkModeColorPicker = createColorPicker('Dark Mode', 'darkModeSelectionColor');
  4396. selectionColorContainer.appendChild(darkModeColorPicker);
  4397.  
  4398. form.appendChild(selectionColorContainer);
  4399.  
  4400. // color picker progress bar - toggle | color picker
  4401. const progressbarColorPicker = document.createElement('div');
  4402. progressbarColorPicker.classList.add('videos-colorpicker-container', 'selection-color-container');
  4403.  
  4404. const playProgressColor = createCheckboxField('Custom Color for on Hover Progress Bar (default: no)', 'playProgressColor', USER_CONFIG.playProgressColor);
  4405. progressbarColorPicker.appendChild(playProgressColor);
  4406.  
  4407. const progressbarColorPickerColor = createColorPicker('Progress Bar Color', 'progressbarColorPicker');
  4408. progressbarColorPicker.appendChild(progressbarColorPickerColor);
  4409.  
  4410. form.appendChild(progressbarColorPicker);
  4411.  
  4412. // pure b/w bg
  4413. const pureBWBackground = createCheckboxField('Pure Black-and-White Background (default: yes)', 'pureBWBackground', USER_CONFIG.pureBWBackground);
  4414. form.appendChild(pureBWBackground);
  4415.  
  4416. // hide video scrubber
  4417. const removeScrubber = createCheckboxField('Hide Video Scrubber (default: no)', 'removeScrubber', USER_CONFIG.removeScrubber);
  4418. form.appendChild(removeScrubber);
  4419.  
  4420. // hide video end cards
  4421. const hideEndCards = createCheckboxField('Hide Video End Cards (default: no)', 'hideEndCards', USER_CONFIG.hideEndCards);
  4422. form.appendChild(hideEndCards);
  4423.  
  4424. // hide end screens
  4425. const hideEndscreen = createCheckboxField('Hide End Screens (default: no)', 'hideEndscreen', USER_CONFIG.hideEndscreen);
  4426. form.appendChild(hideEndscreen);
  4427.  
  4428. // bottom gradient lower height and different bg image
  4429. const gradientBottom = createCheckboxField('Less Intrusive Bottom Gradient (default: yes)', 'gradientBottom', USER_CONFIG.gradientBottom);
  4430. form.appendChild(gradientBottom);
  4431.  
  4432. // hide play next button
  4433. const hidePlayNextButton = createCheckboxField('Hide "Play Next" Button (default: no)', 'hidePlayNextButton', USER_CONFIG.hidePlayNextButton);
  4434. form.appendChild(hidePlayNextButton);
  4435.  
  4436. // hide airplay button
  4437. const hideAirplayButton = createCheckboxField('Hide "Airplay" Button (default: no)', 'hideAirplayButton', USER_CONFIG.hideAirplayButton);
  4438. form.appendChild(hideAirplayButton);
  4439.  
  4440. // hide share button
  4441. const hideShareButton = createCheckboxField('Hide Share Button Under a Videos (default: no)', 'hideShareButton', USER_CONFIG.hideShareButton);
  4442. form.appendChild(hideShareButton);
  4443.  
  4444. // hide hashtags under video
  4445. const hideHashtags = createCheckboxField('Hide Hashtags Under Videos (default: no)', 'hideHashtags', USER_CONFIG.hideHashtags);
  4446. form.appendChild(hideHashtags);
  4447.  
  4448. // hide blue info panel under video
  4449. const hideInfoPanel = createCheckboxField('Hide Blue Info Panels (default: no)', 'hideInfoPanel', USER_CONFIG.hideInfoPanel);
  4450. form.appendChild(hideInfoPanel);
  4451.  
  4452. // hide add comment
  4453. const hideAddComment = createCheckboxField('Hide "Add Comment" Textfield (default: no)', 'hideAddComment', USER_CONFIG.hideAddComment);
  4454. form.appendChild(hideAddComment);
  4455.  
  4456. // hide reply comment button
  4457. const hideReplyButton = createCheckboxField('Hide Comment "Reply" Button (default: no)', 'hideReplyButton', USER_CONFIG.hideReplyButton);
  4458. form.appendChild(hideReplyButton);
  4459.  
  4460. // hide news on home
  4461. const hideNewsHome = createCheckboxField('Hide Breaking News on Home (default: no)', 'hideNewsHome', USER_CONFIG.hideNewsHome);
  4462. form.appendChild(hideNewsHome);
  4463.  
  4464. // hide playlists on home
  4465. const hidePlaylistsHome = createCheckboxField('Hide Playlists on Home (default: no)', 'hidePlaylistsHome', USER_CONFIG.hidePlaylistsHome);
  4466. form.appendChild(hidePlaylistsHome);
  4467.  
  4468. // hide fundraiser
  4469. const hideFundraiser = createCheckboxField('Hide Fundraiser Icons and Panels (default: no)', 'hideFundraiser', USER_CONFIG.hideFundraiser);
  4470. form.appendChild(hideFundraiser);
  4471.  
  4472. // hide mini player
  4473. const hideMiniPlayer = createCheckboxField('Hide Mini Player (default: no)', 'hideMiniPlayer', USER_CONFIG.hideMiniPlayer);
  4474. form.appendChild(hideMiniPlayer);
  4475.  
  4476. // hide queue button
  4477. const hideQueueBtn = createCheckboxField('Hide "Add to queue" Button on Hover (default: no)', 'hideQueueBtn', USER_CONFIG.hideQueueBtn);
  4478. form.appendChild(hideQueueBtn);
  4479.  
  4480. // hide right sidebar search
  4481. const hideRightSidebarSearch = createCheckboxField('Hide Right Sidebar on Search Page (default: no)', 'hideRightSidebarSearch', USER_CONFIG.hideRightSidebarSearch);
  4482. form.appendChild(hideRightSidebarSearch);
  4483.  
  4484. // hide watched videos globally
  4485. const videosHideWatchedGlobal = createCheckboxField('Hide Watched Videos Globally (default: no)', 'videosHideWatchedGlobal', USER_CONFIG.videosHideWatchedGlobal);
  4486. form.appendChild(videosHideWatchedGlobal);
  4487.  
  4488. // left navigation bar
  4489. const leftnavbar = document.createElement('label');
  4490. leftnavbar.innerText = 'Hide UI Elements in the Left Navigation Bar';
  4491. leftnavbar.classList.add('button-icons', 'features-text');
  4492. form.appendChild(leftnavbar);
  4493.  
  4494. // hide home button
  4495. const lnbHideHomeBtn = createCheckboxField('Hide "Home" Button (default: no)', 'lnbHideHomeBtn', USER_CONFIG.lnbHideHomeBtn);
  4496. form.appendChild(lnbHideHomeBtn);
  4497.  
  4498. // hide subscriptions button
  4499. const lnbHideSubscriptionsBtn = createCheckboxField('Hide "Subscriptions" Button (default: no)', 'lnbHideSubscriptionsBtn', USER_CONFIG.lnbHideSubscriptionsBtn);
  4500. form.appendChild(lnbHideSubscriptionsBtn);
  4501.  
  4502. // Spacer-5
  4503. const spacer5Home = document.createElement('div');
  4504. spacer5Home.classList.add('spacer-5');
  4505. form.appendChild(spacer5Home);
  4506.  
  4507. // hide you button
  4508. const lnbHideYouBtn = createCheckboxField('Hide "You" Button (default: no)', 'lnbHideYouBtn', USER_CONFIG.lnbHideYouBtn);
  4509. form.appendChild(lnbHideYouBtn);
  4510.  
  4511. // hide history button
  4512. const lnbHideHistoryBtn = createCheckboxField('Hide "History" Button (default: no)', 'lnbHideHistoryBtn', USER_CONFIG.lnbHideHistoryBtn);
  4513. form.appendChild(lnbHideHistoryBtn);
  4514.  
  4515. // hide playlists button
  4516. const lnbHidePlaylistsBtn = createCheckboxField('Hide "Playlists" Button (default: no)', 'lnbHidePlaylistsBtn', USER_CONFIG.lnbHidePlaylistsBtn);
  4517. form.appendChild(lnbHidePlaylistsBtn);
  4518.  
  4519. // hide videos button
  4520. const lnbHideVideosBtn = createCheckboxField('Hide "Your Videos" Button (default: no)', 'lnbHideVideosBtn', USER_CONFIG.lnbHideVideosBtn);
  4521. form.appendChild(lnbHideVideosBtn);
  4522.  
  4523. // hide courses button
  4524. const lnbHideCoursesBtn = createCheckboxField('Hide "Your Courses" Button (default: no)', 'lnbHideCoursesBtn', USER_CONFIG.lnbHideCoursesBtn);
  4525. form.appendChild(lnbHideCoursesBtn);
  4526.  
  4527. // hide your podcasts button
  4528. const lnbHideYPodcastsBtn = createCheckboxField('Hide "Your Podcasts" Button (default: no)', 'lnbHideYPodcastsBtn', USER_CONFIG.lnbHideYPodcastsBtn);
  4529. form.appendChild(lnbHideYPodcastsBtn);
  4530.  
  4531. // hide watch later button
  4532. const lnbHideWlBtn = createCheckboxField('Hide "Watch Later" Button (default: no)', 'lnbHideWlBtn', USER_CONFIG.lnbHideWlBtn);
  4533. form.appendChild(lnbHideWlBtn);
  4534.  
  4535. // hide liked videos button
  4536. const lnbHideLikedVideosBtn = createCheckboxField('Hide "Liked Videos" Button (default: no)', 'lnbHideLikedVideosBtn', USER_CONFIG.lnbHideLikedVideosBtn);
  4537. form.appendChild(lnbHideLikedVideosBtn);
  4538.  
  4539. // Spacer-5
  4540. const spacer5Subscriptions = document.createElement('div');
  4541. spacer5Subscriptions.classList.add('spacer-5');
  4542. form.appendChild(spacer5Subscriptions);
  4543.  
  4544. // hide subscriptions section
  4545. const lnbHideSubscriptionsSection = createCheckboxField('Hide "Subscriptions" Section (default: no)', 'lnbHideSubscriptionsSection', USER_CONFIG.lnbHideSubscriptionsSection);
  4546. form.appendChild(lnbHideSubscriptionsSection);
  4547.  
  4548. // hide subscriptions title
  4549. const lnbHideSubscriptionsTitle = createCheckboxField('Hide "Subscriptions" Title (default: no)', 'lnbHideSubscriptionsTitle', USER_CONFIG.lnbHideSubscriptionsTitle);
  4550. form.appendChild(lnbHideSubscriptionsTitle);
  4551.  
  4552. // hide more button
  4553. const lnbHideMoreBtn = createCheckboxField('Hide "Show More" Button (default: no)', 'lnbHideMoreBtn', USER_CONFIG.lnbHideMoreBtn);
  4554. form.appendChild(lnbHideMoreBtn);
  4555.  
  4556. // Spacer-5
  4557. const spacer5Explore = document.createElement('div');
  4558. spacer5Explore.classList.add('spacer-5');
  4559. form.appendChild(spacer5Explore);
  4560.  
  4561. // hide explore section
  4562. const lnbHideExploreSection = createCheckboxField('Hide "Explore" Section (default: no)', 'lnbHideExploreSection', USER_CONFIG.lnbHideExploreSection);
  4563. form.appendChild(lnbHideExploreSection);
  4564.  
  4565. // hide explore title
  4566. const lnbHideExploreTitle = createCheckboxField('Hide "Explore" Title (default: no)', 'lnbHideExploreTitle', USER_CONFIG.lnbHideExploreTitle);
  4567. form.appendChild(lnbHideExploreTitle);
  4568.  
  4569. // hide trending button
  4570. const lnbHideTrendingBtn = createCheckboxField('Hide "Trending" Button (default: no)', 'lnbHideTrendingBtn', USER_CONFIG.lnbHideTrendingBtn);
  4571. form.appendChild(lnbHideTrendingBtn);
  4572.  
  4573. // hide music button
  4574. const lnbHideMusicBtn = createCheckboxField('Hide "Music" Button (default: no)', 'lnbHideMusicBtn', USER_CONFIG.lnbHideMusicBtn);
  4575. form.appendChild(lnbHideMusicBtn);
  4576.  
  4577. // hide movies button
  4578. const lnbHideMoviesBtn = createCheckboxField('Hide "Movies & TV" Button (default: no)', 'lnbHideMoviesBtn', USER_CONFIG.lnbHideMoviesBtn);
  4579. form.appendChild(lnbHideMoviesBtn);
  4580.  
  4581. // hide live button
  4582. const lnbHideLiveBtn = createCheckboxField('Hide "Live" Button (default: no)', 'lnbHideLiveBtn', USER_CONFIG.lnbHideLiveBtn);
  4583. form.appendChild(lnbHideLiveBtn);
  4584.  
  4585. // hide gaming button
  4586. const lnbHideGamingBtn = createCheckboxField('Hide "Gaming" Button (default: no)', 'lnbHideGamingBtn', USER_CONFIG.lnbHideGamingBtn);
  4587. form.appendChild(lnbHideGamingBtn);
  4588.  
  4589. // hide news button
  4590. const lnbHideNewsBtn = createCheckboxField('Hide "News" Button (default: no)', 'lnbHideNewsBtn', USER_CONFIG.lnbHideNewsBtn);
  4591. form.appendChild(lnbHideNewsBtn);
  4592.  
  4593. // hide sports button
  4594. const lnbHideSportsBtn = createCheckboxField('Hide "Sports" Button (default: no)', 'lnbHideSportsBtn', USER_CONFIG.lnbHideSportsBtn);
  4595. form.appendChild(lnbHideSportsBtn);
  4596.  
  4597. // hide learning button
  4598. const lnbHideLearningBtn = createCheckboxField('Hide "Learning" Button (default: no)', 'lnbHideLearningBtn', USER_CONFIG.lnbHideLearningBtn);
  4599. form.appendChild(lnbHideLearningBtn);
  4600.  
  4601. // hide fashion & beauty button
  4602. const lnbHideFashionBtn = createCheckboxField('Hide "Fashion & Beauty" Button (default: no)', 'lnbHideFashionBtn', USER_CONFIG.lnbHideFashionBtn);
  4603. form.appendChild(lnbHideFashionBtn);
  4604.  
  4605. // hide podcasts button
  4606. const lnbHidePodcastsBtn = createCheckboxField('Hide "Podcasts" Button (default: no)', 'lnbHidePodcastsBtn', USER_CONFIG.lnbHidePodcastsBtn);
  4607. form.appendChild(lnbHidePodcastsBtn);
  4608.  
  4609. // Spacer-5
  4610. const spacer5More = document.createElement('div');
  4611. spacer5More.classList.add('spacer-5');
  4612. form.appendChild(spacer5More);
  4613.  
  4614. // hide more section
  4615. const lnbHideMoreSection = createCheckboxField('Hide "More from YouTube" Section (default: no)', 'lnbHideMoreSection', USER_CONFIG.lnbHideMoreSection);
  4616. form.appendChild(lnbHideMoreSection);
  4617.  
  4618. // hide more title
  4619. const lnbHideMoreTitle = createCheckboxField('Hide "More from YouTube" Title (default: no)', 'lnbHideMoreTitle', USER_CONFIG.lnbHideMoreTitle);
  4620. form.appendChild(lnbHideMoreTitle);
  4621.  
  4622. // hide youtube premium button
  4623. const lnbHideYtPremiumBtn = createCheckboxField('Hide "YouTube Premium" Button (default: no)', 'lnbHideYtPremiumBtn', USER_CONFIG.lnbHideYtPremiumBtn);
  4624. form.appendChild(lnbHideYtPremiumBtn);
  4625.  
  4626. // hide youtube studio button
  4627. const lnbHideYtStudioBtn = createCheckboxField('Hide "YouTube Studio" Button (default: no)', 'lnbHideYtStudioBtn', USER_CONFIG.lnbHideYtStudioBtn);
  4628. form.appendChild(lnbHideYtStudioBtn);
  4629.  
  4630. // hide youtube music button
  4631. const lnbHideYtMusicBtn = createCheckboxField('Hide "YouTube Music" Button (default: no)', 'lnbHideYtMusicBtn', USER_CONFIG.lnbHideYtMusicBtn);
  4632. form.appendChild(lnbHideYtMusicBtn);
  4633.  
  4634. // hide youtube kids button
  4635. const lnbHideYtKidsBtn = createCheckboxField('Hide "YouTube Kids" Button (default: no)', 'lnbHideYtKidsBtn', USER_CONFIG.lnbHideYtKidsBtn);
  4636. form.appendChild(lnbHideYtKidsBtn);
  4637.  
  4638. // Spacer-5
  4639. const spacer5Penultimate = document.createElement('div');
  4640. spacer5Penultimate.classList.add('spacer-5');
  4641. form.appendChild(spacer5Penultimate);
  4642.  
  4643. // hide penultimate section
  4644. const lnbHidePenultimateSection = createCheckboxField('Hide Penultimate Section (default: no)', 'lnbHidePenultimateSection', USER_CONFIG.lnbHidePenultimateSection);
  4645. form.appendChild(lnbHidePenultimateSection);
  4646.  
  4647. // hide settings button
  4648. const lnbHideSettingsBtn = createCheckboxField('Hide "Settings" Button (default: no)', 'lnbHideSettingsBtn', USER_CONFIG.lnbHideSettingsBtn);
  4649. form.appendChild(lnbHideSettingsBtn);
  4650.  
  4651. // hide report history button
  4652. const lnbHideReportHistoryBtn = createCheckboxField('Hide "Report History" Button (default: no)', 'lnbHideReportHistoryBtn', USER_CONFIG.lnbHideReportHistoryBtn);
  4653. form.appendChild(lnbHideReportHistoryBtn);
  4654.  
  4655. // hide help button
  4656. const lnbHideHelpBtn = createCheckboxField('Hide "Help" Button (default: no)', 'lnbHideHelpBtn', USER_CONFIG.lnbHideHelpBtn);
  4657. form.appendChild(lnbHideHelpBtn);
  4658.  
  4659. // hide feedback button
  4660. const lnbHideFeedbackBtn = createCheckboxField('Hide "Send Feedback" Button (default: no)', 'lnbHideFeedbackBtn', USER_CONFIG.lnbHideFeedbackBtn);
  4661. form.appendChild(lnbHideFeedbackBtn);
  4662.  
  4663. // Spacer-5
  4664. const spacer5Footer = document.createElement('div');
  4665. spacer5Footer.classList.add('spacer-5');
  4666. form.appendChild(spacer5Footer);
  4667.  
  4668. // hide footer
  4669. const lnbHideFooter = createCheckboxField('Hide Footer (default: no)', 'lnbHideFooter', USER_CONFIG.lnbHideFooter);
  4670. form.appendChild(lnbHideFooter);
  4671.  
  4672. return form;
  4673. }
  4674.  
  4675. // color code videos
  4676. function createColorCodeVideosContent() {
  4677. const form = document.createElement('form');
  4678. form.id = 'color-code-videos-form';
  4679.  
  4680. const subPanelHeader = document.createElement('div');
  4681. subPanelHeader.classList.add('sub-panel-header');
  4682. subPanelHeader.textContent = 'Configure Color Codes for Videos';
  4683. form.appendChild(subPanelHeader);
  4684.  
  4685. // on home page
  4686. const colorCodeVideosOnHome = document.createElement('label');
  4687. colorCodeVideosOnHome.innerText = 'Home Page';
  4688. colorCodeVideosOnHome.classList.add('button-icons', 'features-text');
  4689. form.appendChild(colorCodeVideosOnHome);
  4690.  
  4691. const infoColorCodeVideosHome = document.createElement('small');
  4692. infoColorCodeVideosHome.innerText = "These settings apply only to the Home page.";
  4693. infoColorCodeVideosHome.classList.add('CentAnni-info-text');
  4694. form.appendChild(infoColorCodeVideosHome);
  4695.  
  4696. // activate color code videos on home
  4697. const checkboxField = createCheckboxField('Color Code Videos Based on Age and Status (default: yes)', 'colorCodeVideosEnabled', USER_CONFIG.colorCodeVideosEnabled );
  4698. form.appendChild(checkboxField);
  4699.  
  4700. const checkboxFieldWatched = createCheckboxField('Hide Watched Videos—Only on Home (default: no)', 'videosHideWatched', USER_CONFIG.videosHideWatched );
  4701. form.appendChild(checkboxFieldWatched);
  4702.  
  4703. // opacity picker for old videos
  4704. const videosOldContainer = createSliderInputField( 'Change Opacity of Videos Uploaded More than 6 Months Ago:', 'videosOldOpacity', USER_CONFIG.videosOldOpacity, '0', '1', '0.1' );
  4705. form.appendChild(videosOldContainer);
  4706.  
  4707. // color pickers for different video ages
  4708. const videosAgeContainer = document.createElement('div');
  4709. videosAgeContainer.classList.add('videos-colorpicker-container');
  4710.  
  4711. function createLabelColorPair(labelText, configKey) {
  4712. const row = document.createElement('div');
  4713. row.classList.add('videos-colorpicker-row');
  4714.  
  4715. const label = document.createElement('span');
  4716. label.classList.add('label-style-settings');
  4717. label.innerText = labelText;
  4718. row.appendChild(label);
  4719.  
  4720. const colorPicker = document.createElement('input');
  4721. colorPicker.type = 'color';
  4722. colorPicker.value = USER_CONFIG[configKey];
  4723. colorPicker.name = configKey;
  4724. row.appendChild(colorPicker);
  4725.  
  4726. videosAgeContainer.appendChild(row);
  4727. }
  4728.  
  4729. createLabelColorPair('Videos Uploaded Within the Last 24 Hours:', 'videosAgeColorPickerNewly');
  4730. createLabelColorPair('Videos Uploaded Within the Past Week:', 'videosAgeColorPickerRecent');
  4731. createLabelColorPair('Videos Uploaded Within the Past Month:', 'videosAgeColorPickerLately');
  4732. createLabelColorPair('Videos Currently Live:', 'videosAgeColorPickerLive');
  4733. createLabelColorPair('The Word “Streamed” from Videos That Were Live:', 'videosAgeColorPickerStreamed');
  4734. createLabelColorPair('Scheduled Videos and Upcoming Live Streams:', 'videosAgeColorPickerUpcoming');
  4735. createLabelColorPair('Details Sections of Watch Later Videos:', 'WatchLaterColor');
  4736.  
  4737. form.appendChild(videosAgeContainer);
  4738.  
  4739. // on subscriptions page
  4740. const colorCodeVideosOnSubscriptions = document.createElement('label');
  4741. colorCodeVideosOnSubscriptions.innerText = 'Subscriptions Page';
  4742. colorCodeVideosOnSubscriptions.classList.add('button-icons', 'features-text');
  4743. form.appendChild(colorCodeVideosOnSubscriptions);
  4744.  
  4745. const infoColorCodeVideosSubscriptions = document.createElement('small');
  4746. infoColorCodeVideosSubscriptions.innerText = "These settings apply only to the Subscriptions page.\nOn each visit, the newest uploaded video is saved, allowing subsequent visits to highlight and optionally auto-scroll to that video. On first page load, YouTube loads about 84 videos, so highlighting and scrolling apply within this limit.";
  4747. infoColorCodeVideosSubscriptions.classList.add('CentAnni-info-text');
  4748. form.appendChild(infoColorCodeVideosSubscriptions);
  4749.  
  4750. // color picker last seen video
  4751. const lastSeenVideoColor = document.createElement('div');
  4752. lastSeenVideoColor.classList.add('videos-colorpicker-container');
  4753.  
  4754. function createLabelLVColor(labelText, configKey) {
  4755. const row = document.createElement('div');
  4756. row.classList.add('videos-colorpicker-row');
  4757.  
  4758. const label = document.createElement('span');
  4759. label.classList.add('label-style-settings');
  4760. label.innerText = labelText;
  4761. row.appendChild(label);
  4762.  
  4763. const colorPicker = document.createElement('input');
  4764. colorPicker.type = 'color';
  4765. colorPicker.value = USER_CONFIG[configKey];
  4766. colorPicker.name = configKey;
  4767. row.appendChild(colorPicker);
  4768.  
  4769. lastSeenVideoColor.appendChild(row);
  4770. }
  4771.  
  4772. createLabelLVColor('Last Uploaded Video:', 'lastSeenVideoColor');
  4773.  
  4774. form.appendChild(lastSeenVideoColor);
  4775.  
  4776. // last seen video
  4777. const lastSeenVideo = createCheckboxField('Color Code Last Uploaded Video (default: yes)', 'lastSeenVideo', USER_CONFIG.lastSeenVideo);
  4778. form.appendChild(lastSeenVideo);
  4779.  
  4780. // scroll to last seen video
  4781. const lastSeenVideoScroll = createCheckboxField('Auto-Scroll to Last Uploaded Video (default: no)', 'lastSeenVideoScroll', USER_CONFIG.lastSeenVideoScroll);
  4782. form.appendChild(lastSeenVideoScroll);
  4783.  
  4784. return form;
  4785. }
  4786. }
  4787.  
  4788. // helper function to create input fields
  4789. function createInputField(labelText, settingKey, settingValue, labelClass) {
  4790. const container = document.createElement('div');
  4791. container.classList.add('url-container');
  4792.  
  4793. const label = document.createElement('label');
  4794. label.innerText = labelText;
  4795. label.className = labelClass;
  4796. label.classList.add('label-style-settings');
  4797. container.appendChild(label);
  4798.  
  4799. const input = document.createElement('input');
  4800. const fieldInputClass = `input-field-${settingKey}`;
  4801. input.type = 'text';
  4802. input.name = settingKey;
  4803. input.value = settingValue;
  4804. input.classList.add('input-field-url');
  4805. input.classList.add(fieldInputClass);
  4806. container.appendChild(input);
  4807.  
  4808. return container;
  4809. }
  4810.  
  4811. // helper function to create select fields
  4812. function createSelectField(labelText, labelClass, settingKey, settingValue, options) {
  4813. const container = document.createElement('div');
  4814. container.classList.add('file-naming-container');
  4815.  
  4816. const label = document.createElement('label');
  4817. label.innerText = labelText;
  4818. label.className = labelClass;
  4819. label.classList.add('label-style-settings');
  4820. container.appendChild(label);
  4821.  
  4822. const select = document.createElement('div');
  4823. select.classList.add('select-file-naming');
  4824. select.innerText = options[settingValue];
  4825. select.setAttribute('tabindex', '0');
  4826. container.appendChild(select);
  4827.  
  4828. const hiddenSelect = document.createElement('select');
  4829. hiddenSelect.name = settingKey;
  4830. hiddenSelect.classList.add('hidden-select');
  4831. for (const [value, text] of Object.entries(options)) {
  4832. const option = document.createElement('option');
  4833. option.value = value;
  4834. option.text = text;
  4835. if (value === settingValue) {
  4836. option.selected = true;
  4837. }
  4838. hiddenSelect.appendChild(option);
  4839. }
  4840. container.appendChild(hiddenSelect);
  4841.  
  4842. const dropdownList = document.createElement('div');
  4843. dropdownList.classList.add('dropdown-list');
  4844. container.appendChild(dropdownList);
  4845.  
  4846. for (const [value, text] of Object.entries(options)) {
  4847. const item = document.createElement('div');
  4848. item.classList.add('dropdown-item');
  4849. item.innerText = text;
  4850. item.dataset.value = value;
  4851.  
  4852. if (value === settingValue) { item.classList.add('dropdown-item-selected'); }
  4853.  
  4854. item.addEventListener('click', () => {
  4855. const previouslySelected = dropdownList.querySelector('.dropdown-item-selected');
  4856. if (previouslySelected) {
  4857. previouslySelected.classList.remove('dropdown-item-selected');
  4858. }
  4859.  
  4860. item.classList.add('dropdown-item-selected');
  4861. select.innerText = text;
  4862. hiddenSelect.value = value;
  4863. dropdownList.classList.remove('show');
  4864. });
  4865.  
  4866. dropdownList.appendChild(item);
  4867. }
  4868.  
  4869. // open dropdown
  4870. select.addEventListener('click', (event) => {
  4871. event.stopPropagation();
  4872. document.querySelectorAll('.dropdown-list.show').forEach(list => { if (list !== dropdownList) list.classList.remove('show'); });
  4873. dropdownList.classList.toggle('show');
  4874. });
  4875.  
  4876. // close dropdown
  4877. document.addEventListener('click', () => {
  4878. dropdownList.classList.remove('show');
  4879. });
  4880.  
  4881. return container;
  4882. }
  4883.  
  4884. // helper function to create checkbox fields
  4885. function createCheckboxField(labelText, settingKey, settingValue) {
  4886. const container = document.createElement('div');
  4887. container.classList.add('checkbox-container');
  4888.  
  4889. const label = document.createElement('label');
  4890. label.classList.add('checkbox-label');
  4891.  
  4892. const checkbox = document.createElement('input');
  4893. checkbox.type = 'checkbox';
  4894. checkbox.name = settingKey;
  4895. checkbox.checked = settingValue;
  4896. checkbox.classList.add('checkbox-field');
  4897. label.appendChild(checkbox);
  4898.  
  4899. const span = document.createElement('span');
  4900. span.innerText = labelText;
  4901. label.appendChild(span);
  4902.  
  4903. container.appendChild(label);
  4904. return container;
  4905. }
  4906.  
  4907. // helper function to create a number input fields
  4908. function createNumberInputField(labelText, settingKey, settingValue) {
  4909. const container = document.createElement('div');
  4910. container.classList.add('number-input-container');
  4911.  
  4912. const label = document.createElement('label');
  4913. label.classList.add('number-input-label');
  4914.  
  4915. const numberInput = document.createElement('input');
  4916. numberInput.type = 'number';
  4917. numberInput.name = settingKey;
  4918. numberInput.value = settingValue;
  4919. numberInput.min = 1;
  4920. numberInput.max = 20;
  4921. numberInput.step = 1;
  4922. numberInput.classList.add('number-input-field');
  4923. label.appendChild(numberInput);
  4924.  
  4925. const span = document.createElement('span');
  4926. span.innerText = labelText;
  4927. label.appendChild(span);
  4928.  
  4929. container.appendChild(label);
  4930. return container;
  4931. }
  4932.  
  4933. // helper function to create a slider fields
  4934. function createSliderInputField(labelText, settingKey, settingValue, min, max, step) {
  4935. const container = document.createElement('div');
  4936. container.classList.add('videos-old-container');
  4937.  
  4938. const label = document.createElement('span');
  4939. label.classList.add('label-style-settings');
  4940. label.innerText = labelText;
  4941. container.appendChild(label);
  4942.  
  4943. const sliderContainer = document.createElement('div');
  4944. sliderContainer.classList.add('slider-container');
  4945.  
  4946. const leftLabel = document.createElement('span');
  4947. leftLabel.innerText = min;
  4948. sliderContainer.appendChild(leftLabel);
  4949.  
  4950. const slider = document.createElement('input');
  4951. slider.type = 'range';
  4952. slider.min = min;
  4953. slider.max = max;
  4954. slider.step = step;
  4955. slider.value = settingValue;
  4956. slider.name = settingKey;
  4957. sliderContainer.appendChild(slider);
  4958.  
  4959. const rightLabel = document.createElement('span');
  4960. rightLabel.innerText = max;
  4961. sliderContainer.appendChild(rightLabel);
  4962.  
  4963. const currentValue = document.createElement('span');
  4964. currentValue.innerText = `(${parseFloat(slider.value).toFixed(1)})`;
  4965. sliderContainer.appendChild(currentValue);
  4966.  
  4967. container.appendChild(sliderContainer);
  4968.  
  4969. slider.addEventListener('input', (e) => {
  4970. const value = parseFloat(e.target.value).toFixed(1);
  4971. currentValue.innerText = `(${value})`;
  4972. });
  4973.  
  4974. return container;
  4975. }
  4976.  
  4977. // helper function to create a textarea fields
  4978. function createTextareaField(labelText, settingKey, settingValue, labelClass) {
  4979. const container = document.createElement('chatgpt-prompt');
  4980.  
  4981. const label = document.createElement('label');
  4982. label.innerText = labelText;
  4983. label.className = labelClass;
  4984. label.classList.add('label-style-settings');
  4985. container.appendChild(label);
  4986.  
  4987. const textarea = document.createElement('textarea');
  4988. textarea.name = settingKey;
  4989. textarea.value = settingValue;
  4990. textarea.classList.add('chatgpt-prompt-textarea');
  4991. container.appendChild(textarea);
  4992.  
  4993. return container;
  4994. }
  4995.  
  4996. // helper function to create color pickers
  4997. function createColorPicker(labelText, configKey) {
  4998. const row = document.createElement('div');
  4999. row.classList.add('videos-colorpicker-row');
  5000.  
  5001. const colorPicker = document.createElement('input');
  5002. colorPicker.type = 'color';
  5003. colorPicker.value = USER_CONFIG[configKey];
  5004. colorPicker.name = configKey;
  5005. row.appendChild(colorPicker);
  5006.  
  5007. const label = document.createElement('span');
  5008. label.classList.add('label-style-settings');
  5009. label.innerText = labelText;
  5010. row.appendChild(label);
  5011.  
  5012. return row;
  5013. }
  5014.  
  5015. // function to save settings
  5016. async function saveSettings() {
  5017. const form = document.getElementById('yt-transcript-settings-form');
  5018. const subPanelLinks = document.getElementById('links-in-header-form');
  5019. const subPanelCustomCSS = document.getElementById('custom-css-form');
  5020. const subPanelColor = document.getElementById('color-code-videos-form');
  5021.  
  5022. // function to ensure secure URLs
  5023. function normalizeUrl(url) {
  5024. url = url.trim();
  5025. if (/^https?:\/\//i.test(url)) {
  5026. url = url.replace(/^http:\/\//i, 'https://');
  5027. } else { url = 'https://' + url; }
  5028. return url;
  5029. }
  5030.  
  5031. // validate ChatGPT URL
  5032. let targetChatGPTUrl = form.elements.targetChatGPTUrl.value.trim();
  5033. if (targetChatGPTUrl !== '') {
  5034. USER_CONFIG.targetChatGPTUrl = normalizeUrl(targetChatGPTUrl);
  5035. } else { delete USER_CONFIG.targetChatGPTUrl; }
  5036.  
  5037. // validate NotebookLM URL
  5038. let targetNotebookLMUrl = form.elements.targetNotebookLMUrl.value.trim();
  5039. if (targetNotebookLMUrl !== '') {
  5040. USER_CONFIG.targetNotebookLMUrl = normalizeUrl(targetNotebookLMUrl);
  5041. } else { delete USER_CONFIG.targetNotebookLMUrl; }
  5042.  
  5043. // save other settings
  5044. USER_CONFIG.YouTubeTranscriptExporter = form.elements.YouTubeTranscriptExporter.checked;
  5045. USER_CONFIG.fileNamingFormat = form.elements.fileNamingFormat.value;
  5046. USER_CONFIG.includeTimestamps = form.elements.includeTimestamps.checked;
  5047. USER_CONFIG.includeChapterHeaders = form.elements.includeChapterHeaders.checked;
  5048. USER_CONFIG.openSameTab = form.elements.openSameTab.checked;
  5049. USER_CONFIG.preventBackgroundExecution = form.elements.preventBackgroundExecution.checked;
  5050. USER_CONFIG.ChatGPTPrompt = form.elements.ChatGPTPrompt.value;
  5051.  
  5052. // initialize buttonIcons if not already
  5053. USER_CONFIG.buttonIcons = USER_CONFIG.buttonIcons || {};
  5054.  
  5055. // save button icons, removing empty values to use defaults
  5056. const buttonIconDownload = form.elements.buttonIconDownload.value.trim();
  5057. const buttonIconChatGPT = form.elements.buttonIconChatGPT.value.trim();
  5058. const buttonIconNotebookLM = form.elements.buttonIconNotebookLM.value.trim();
  5059. const buttonIconSettings = form.elements.buttonIconSettings.value.trim();
  5060.  
  5061. USER_CONFIG.buttonIcons.download = buttonIconDownload;
  5062. USER_CONFIG.buttonIcons.ChatGPT = buttonIconChatGPT;
  5063. USER_CONFIG.buttonIcons.NotebookLM = buttonIconNotebookLM;
  5064. if (buttonIconSettings !== '') { USER_CONFIG.buttonIcons.settings = buttonIconSettings; } else { delete USER_CONFIG.buttonIcons.settings; }
  5065.  
  5066. // save sub panels - links in header
  5067. if (subPanelLinks) {
  5068. USER_CONFIG.buttonLeft1Text = subPanelLinks.elements.buttonLeft1Text.value;
  5069. USER_CONFIG.buttonLeft1Url = subPanelLinks.elements.buttonLeft1Url.value;
  5070. USER_CONFIG.buttonLeft2Text = subPanelLinks.elements.buttonLeft2Text.value;
  5071. USER_CONFIG.buttonLeft2Url = subPanelLinks.elements.buttonLeft2Url.value;
  5072. USER_CONFIG.buttonLeft3Text = subPanelLinks.elements.buttonLeft3Text.value;
  5073. USER_CONFIG.buttonLeft3Url = subPanelLinks.elements.buttonLeft3Url.value;
  5074. USER_CONFIG.buttonLeft4Text = subPanelLinks.elements.buttonLeft4Text.value;
  5075. USER_CONFIG.buttonLeft4Url = subPanelLinks.elements.buttonLeft4Url.value;
  5076. USER_CONFIG.buttonLeft5Text = subPanelLinks.elements.buttonLeft5Text.value;
  5077. USER_CONFIG.buttonLeft5Url = subPanelLinks.elements.buttonLeft5Url.value;
  5078. USER_CONFIG.buttonLeft6Text = subPanelLinks.elements.buttonLeft6Text.value;
  5079. USER_CONFIG.buttonLeft6Url = subPanelLinks.elements.buttonLeft6Url.value;
  5080. USER_CONFIG.buttonLeft7Text = subPanelLinks.elements.buttonLeft7Text.value;
  5081. USER_CONFIG.buttonLeft7Url = subPanelLinks.elements.buttonLeft7Url.value;
  5082. USER_CONFIG.buttonLeft8Text = subPanelLinks.elements.buttonLeft8Text.value;
  5083. USER_CONFIG.buttonLeft8Url = subPanelLinks.elements.buttonLeft8Url.value;
  5084. USER_CONFIG.buttonLeft9Text = subPanelLinks.elements.buttonLeft9Text.value;
  5085. USER_CONFIG.buttonLeft9Url = subPanelLinks.elements.buttonLeft9Url.value;
  5086. USER_CONFIG.buttonLeft10Text = subPanelLinks.elements.buttonLeft10Text.value;
  5087. USER_CONFIG.buttonLeft10Url = subPanelLinks.elements.buttonLeft10Url.value;
  5088. USER_CONFIG.mButtonText = subPanelLinks.elements.mButtonText.value;
  5089. USER_CONFIG.mButtonDisplay = subPanelLinks.elements.mButtonDisplay.checked;
  5090. }
  5091.  
  5092. // save sub panels - custom css
  5093. if (subPanelCustomCSS) {
  5094. USER_CONFIG.textTransform = subPanelCustomCSS.elements.textTransform.value;
  5095. USER_CONFIG.defaultFontSize = parseFloat(subPanelCustomCSS.elements.defaultFontSize.value);
  5096. USER_CONFIG.videosWatchedOpacity = parseFloat(subPanelCustomCSS.elements.videosWatchedOpacity.value);
  5097. USER_CONFIG.videosHideWatchedGlobal = subPanelCustomCSS.elements.videosHideWatchedGlobal.checked;
  5098. USER_CONFIG.videosPerRow = parseInt(subPanelCustomCSS.elements.videosPerRow.value);
  5099. USER_CONFIG.autoOpenChapters = subPanelCustomCSS.elements.autoOpenChapters.checked;
  5100. USER_CONFIG.autoOpenTranscript = subPanelCustomCSS.elements.autoOpenTranscript.checked;
  5101. USER_CONFIG.transcriptTimestamps = subPanelCustomCSS.elements.transcriptTimestamps.checked;
  5102. USER_CONFIG.displayRemainingTime = subPanelCustomCSS.elements.displayRemainingTime.checked;
  5103. USER_CONFIG.progressBar = subPanelCustomCSS.elements.progressBar.checked;
  5104. USER_CONFIG.hideShorts = subPanelCustomCSS.elements.hideShorts.checked;
  5105. USER_CONFIG.redirectShorts = subPanelCustomCSS.elements.redirectShorts.checked;
  5106. USER_CONFIG.hideAdSlots = subPanelCustomCSS.elements.hideAdSlots.checked;
  5107. USER_CONFIG.hidePayToWatch = subPanelCustomCSS.elements.hidePayToWatch.checked;
  5108. USER_CONFIG.hideFreeWithAds = subPanelCustomCSS.elements.hideFreeWithAds.checked;
  5109. USER_CONFIG.hideMembersOnly = subPanelCustomCSS.elements.hideMembersOnly.checked;
  5110. USER_CONFIG.hideLatestPosts = subPanelCustomCSS.elements.hideLatestPosts.checked;
  5111. USER_CONFIG.videoTabView = subPanelCustomCSS.elements.videoTabView.checked;
  5112. USER_CONFIG.tabViewChapters = subPanelCustomCSS.elements.tabViewChapters.checked;
  5113. USER_CONFIG.hideCommentsSection = subPanelCustomCSS.elements.hideCommentsSection.checked;
  5114. USER_CONFIG.hideVideosSection = subPanelCustomCSS.elements.hideVideosSection.checked;
  5115. USER_CONFIG.playbackSpeed = subPanelCustomCSS.elements.playbackSpeed.checked;
  5116. USER_CONFIG.playbackSpeedValue = parseInt(subPanelCustomCSS.elements.playbackSpeedValue.value);
  5117. USER_CONFIG.defaultQuality = subPanelCustomCSS.elements.defaultQuality.value;
  5118. USER_CONFIG.defaultTranscriptLanguage = subPanelCustomCSS.elements.defaultTranscriptLanguage.value;
  5119. USER_CONFIG.hideVoiceSearch = subPanelCustomCSS.elements.hideVoiceSearch.checked;
  5120. USER_CONFIG.selectionColor = subPanelCustomCSS.elements.selectionColor.checked;
  5121. USER_CONFIG.hideCreateButton = subPanelCustomCSS.elements.hideCreateButton.checked;
  5122. USER_CONFIG.hideNotificationBtn = subPanelCustomCSS.elements.hideNotificationBtn.checked;
  5123. USER_CONFIG.hideNotificationBadge = subPanelCustomCSS.elements.hideNotificationBadge.checked;
  5124. USER_CONFIG.hideOwnAvatar = subPanelCustomCSS.elements.hideOwnAvatar.checked;
  5125. USER_CONFIG.hideRightSidebarSearch = subPanelCustomCSS.elements.hideRightSidebarSearch.checked;
  5126. USER_CONFIG.hideBrandText = subPanelCustomCSS.elements.hideBrandText.checked;
  5127. USER_CONFIG.disablePlayOnHover = subPanelCustomCSS.elements.disablePlayOnHover.checked;
  5128. USER_CONFIG.preventAutoplay = subPanelCustomCSS.elements.preventAutoplay.checked;
  5129. USER_CONFIG.VerifiedArtist = subPanelCustomCSS.elements.VerifiedArtist.checked;
  5130. USER_CONFIG.defaultQualityPremium = subPanelCustomCSS.elements.defaultQualityPremium.checked;
  5131. USER_CONFIG.hideEndCards = subPanelCustomCSS.elements.hideEndCards.checked;
  5132. USER_CONFIG.hideEndscreen = subPanelCustomCSS.elements.hideEndscreen.checked;
  5133. USER_CONFIG.gradientBottom = subPanelCustomCSS.elements.gradientBottom.checked;
  5134. USER_CONFIG.hideJoinButton = subPanelCustomCSS.elements.hideJoinButton.checked;
  5135. USER_CONFIG.hidePlayNextButton = subPanelCustomCSS.elements.hidePlayNextButton.checked;
  5136. USER_CONFIG.hideAirplayButton = subPanelCustomCSS.elements.hideAirplayButton.checked;
  5137. USER_CONFIG.smallSubscribeButton = subPanelCustomCSS.elements.smallSubscribeButton.checked;
  5138. USER_CONFIG.hideShareButton = subPanelCustomCSS.elements.hideShareButton.checked;
  5139. USER_CONFIG.hideHashtags = subPanelCustomCSS.elements.hideHashtags.checked;
  5140. USER_CONFIG.hideInfoPanel = subPanelCustomCSS.elements.hideInfoPanel.checked;
  5141. USER_CONFIG.hideAddComment = subPanelCustomCSS.elements.hideAddComment.checked;
  5142. USER_CONFIG.hideReplyButton = subPanelCustomCSS.elements.hideReplyButton.checked;
  5143. USER_CONFIG.hidePlaylistsHome = subPanelCustomCSS.elements.hidePlaylistsHome.checked;
  5144. USER_CONFIG.hideNewsHome = subPanelCustomCSS.elements.hideNewsHome.checked;
  5145. USER_CONFIG.playProgressColor = subPanelCustomCSS.elements.playProgressColor.checked;
  5146. USER_CONFIG.progressbarColorPicker = subPanelCustomCSS.elements.progressbarColorPicker.value;
  5147. USER_CONFIG.lightModeSelectionColor = subPanelCustomCSS.elements.lightModeSelectionColor.value;
  5148. USER_CONFIG.darkModeSelectionColor = subPanelCustomCSS.elements.darkModeSelectionColor.value;
  5149. USER_CONFIG.pureBWBackground = subPanelCustomCSS.elements.pureBWBackground.checked;
  5150. USER_CONFIG.removeScrubber = subPanelCustomCSS.elements.removeScrubber.checked;
  5151. USER_CONFIG.autoTheaterMode = subPanelCustomCSS.elements.autoTheaterMode.checked;
  5152. USER_CONFIG.channelReindirizzare = subPanelCustomCSS.elements.channelReindirizzare.checked;
  5153. USER_CONFIG.channelRSSBtn = subPanelCustomCSS.elements.channelRSSBtn.checked;
  5154. USER_CONFIG.channelPlaylistBtn = subPanelCustomCSS.elements.channelPlaylistBtn.checked;
  5155. USER_CONFIG.playlistDirectionBtns = subPanelCustomCSS.elements.playlistDirectionBtns.checked;
  5156. USER_CONFIG.playlistLinks = subPanelCustomCSS.elements.playlistLinks.checked;
  5157. USER_CONFIG.playlistTrashCan = subPanelCustomCSS.elements.playlistTrashCan.checked;
  5158. USER_CONFIG.commentsNewFirst = subPanelCustomCSS.elements.commentsNewFirst.checked;
  5159. USER_CONFIG.hideFundraiser = subPanelCustomCSS.elements.hideFundraiser.checked;
  5160. USER_CONFIG.hideMiniPlayer = subPanelCustomCSS.elements.hideMiniPlayer.checked;
  5161. USER_CONFIG.hideQueueBtn = subPanelCustomCSS.elements.hideQueueBtn.checked;
  5162. USER_CONFIG.closeChatWindow = subPanelCustomCSS.elements.closeChatWindow.checked;
  5163. USER_CONFIG.lnbHideHomeBtn = subPanelCustomCSS.elements.lnbHideHomeBtn.checked;
  5164. USER_CONFIG.lnbHideSubscriptionsBtn = subPanelCustomCSS.elements.lnbHideSubscriptionsBtn.checked;
  5165. USER_CONFIG.lnbHideHistoryBtn = subPanelCustomCSS.elements.lnbHideHistoryBtn.checked;
  5166. USER_CONFIG.lnbHidePlaylistsBtn = subPanelCustomCSS.elements.lnbHidePlaylistsBtn.checked;
  5167. USER_CONFIG.lnbHideVideosBtn = subPanelCustomCSS.elements.lnbHideVideosBtn.checked;
  5168. USER_CONFIG.lnbHideCoursesBtn = subPanelCustomCSS.elements.lnbHideCoursesBtn.checked;
  5169. USER_CONFIG.lnbHideYPodcastsBtn = subPanelCustomCSS.elements.lnbHideYPodcastsBtn.checked;
  5170. USER_CONFIG.lnbHideWlBtn = subPanelCustomCSS.elements.lnbHideWlBtn.checked;
  5171. USER_CONFIG.lnbHideLikedVideosBtn = subPanelCustomCSS.elements.lnbHideLikedVideosBtn.checked;
  5172. USER_CONFIG.lnbHideYouBtn = subPanelCustomCSS.elements.lnbHideYouBtn.checked;
  5173. USER_CONFIG.lnbHideSubscriptionsSection = subPanelCustomCSS.elements.lnbHideSubscriptionsSection.checked;
  5174. USER_CONFIG.lnbHideSubscriptionsTitle = subPanelCustomCSS.elements.lnbHideSubscriptionsTitle.checked;
  5175. USER_CONFIG.lnbHideMoreBtn = subPanelCustomCSS.elements.lnbHideMoreBtn.checked;
  5176. USER_CONFIG.lnbHideExploreSection = subPanelCustomCSS.elements.lnbHideExploreSection.checked;
  5177. USER_CONFIG.lnbHideExploreTitle = subPanelCustomCSS.elements.lnbHideExploreTitle.checked;
  5178. USER_CONFIG.lnbHideTrendingBtn = subPanelCustomCSS.elements.lnbHideTrendingBtn.checked;
  5179. USER_CONFIG.lnbHideMusicBtn = subPanelCustomCSS.elements.lnbHideMusicBtn.checked;
  5180. USER_CONFIG.lnbHideMoviesBtn = subPanelCustomCSS.elements.lnbHideMoviesBtn.checked;
  5181. USER_CONFIG.lnbHideLiveBtn = subPanelCustomCSS.elements.lnbHideLiveBtn.checked;
  5182. USER_CONFIG.lnbHideGamingBtn = subPanelCustomCSS.elements.lnbHideGamingBtn.checked;
  5183. USER_CONFIG.lnbHideNewsBtn = subPanelCustomCSS.elements.lnbHideNewsBtn.checked;
  5184. USER_CONFIG.lnbHideSportsBtn = subPanelCustomCSS.elements.lnbHideSportsBtn.checked;
  5185. USER_CONFIG.lnbHideLearningBtn = subPanelCustomCSS.elements.lnbHideLearningBtn.checked;
  5186. USER_CONFIG.lnbHideFashionBtn = subPanelCustomCSS.elements.lnbHideFashionBtn.checked;
  5187. USER_CONFIG.lnbHidePodcastsBtn = subPanelCustomCSS.elements.lnbHidePodcastsBtn.checked;
  5188. USER_CONFIG.lnbHideMoreSection = subPanelCustomCSS.elements.lnbHideMoreSection.checked;
  5189. USER_CONFIG.lnbHideMoreTitle = subPanelCustomCSS.elements.lnbHideMoreTitle.checked;
  5190. USER_CONFIG.lnbHideYtPremiumBtn = subPanelCustomCSS.elements.lnbHideYtPremiumBtn.checked;
  5191. USER_CONFIG.lnbHideYtStudioBtn = subPanelCustomCSS.elements.lnbHideYtStudioBtn.checked;
  5192. USER_CONFIG.lnbHideYtMusicBtn = subPanelCustomCSS.elements.lnbHideYtMusicBtn.checked;
  5193. USER_CONFIG.lnbHideYtKidsBtn = subPanelCustomCSS.elements.lnbHideYtKidsBtn.checked;
  5194. USER_CONFIG.lnbHidePenultimateSection = subPanelCustomCSS.elements.lnbHidePenultimateSection.checked;
  5195. USER_CONFIG.lnbHideSettingsBtn = subPanelCustomCSS.elements.lnbHideSettingsBtn.checked;
  5196. USER_CONFIG.lnbHideReportHistoryBtn = subPanelCustomCSS.elements.lnbHideReportHistoryBtn.checked;
  5197. USER_CONFIG.lnbHideHelpBtn = subPanelCustomCSS.elements.lnbHideHelpBtn.checked;
  5198. USER_CONFIG.lnbHideFeedbackBtn = subPanelCustomCSS.elements.lnbHideFeedbackBtn.checked;
  5199. USER_CONFIG.lnbHideFooter = subPanelCustomCSS.elements.lnbHideFooter.checked;
  5200. USER_CONFIG.displayFullTitle = subPanelCustomCSS.elements.displayFullTitle.checked;
  5201. USER_CONFIG.squareSearchBar = subPanelCustomCSS.elements.squareSearchBar.checked;
  5202. USER_CONFIG.squareDesign = subPanelCustomCSS.elements.squareDesign.checked;
  5203. USER_CONFIG.squareAvatars = subPanelCustomCSS.elements.squareAvatars.checked;
  5204. USER_CONFIG.compactLayout = subPanelCustomCSS.elements.compactLayout.checked;
  5205. }
  5206.  
  5207. // save sub panels - color code videos
  5208. if (subPanelColor) {
  5209. USER_CONFIG.colorCodeVideosEnabled = subPanelColor.elements.colorCodeVideosEnabled.checked;
  5210. USER_CONFIG.videosHideWatched = subPanelColor.elements.videosHideWatched.checked;
  5211. USER_CONFIG.videosOldOpacity = parseFloat(subPanelColor.elements.videosOldOpacity.value);
  5212. USER_CONFIG.videosAgeColorPickerNewly = subPanelColor.elements.videosAgeColorPickerNewly.value;
  5213. USER_CONFIG.videosAgeColorPickerRecent = subPanelColor.elements.videosAgeColorPickerRecent.value;
  5214. USER_CONFIG.videosAgeColorPickerLately = subPanelColor.elements.videosAgeColorPickerLately.value;
  5215. USER_CONFIG.videosAgeColorPickerLive = subPanelColor.elements.videosAgeColorPickerLive.value;
  5216. USER_CONFIG.videosAgeColorPickerStreamed = subPanelColor.elements.videosAgeColorPickerStreamed.value;
  5217. USER_CONFIG.videosAgeColorPickerUpcoming = subPanelColor.elements.videosAgeColorPickerUpcoming.value;
  5218. USER_CONFIG.WatchLaterColor = subPanelColor.elements.WatchLaterColor.value;
  5219. USER_CONFIG.lastSeenVideo = subPanelColor.elements.lastSeenVideo.checked;
  5220. USER_CONFIG.lastSeenVideoScroll = subPanelColor.elements.lastSeenVideoScroll.checked;
  5221. USER_CONFIG.lastSeenVideoColor = subPanelColor.elements.lastSeenVideoColor.value;
  5222. }
  5223.  
  5224. // save updated config
  5225. try {
  5226. await GM.setValue('USER_CONFIG', USER_CONFIG);
  5227.  
  5228. // close modal
  5229. document.getElementById('yt-transcript-settings-modal').style.display = 'none';
  5230. showNotification('Settings have been updated!');
  5231. setTimeout(() => { location.reload(); }, 1000);
  5232. } catch (error) {
  5233. showNotification('Error saving new user config!');
  5234. console.error("YouTubeAlchemy: Error saving user configuration:", error);
  5235. }
  5236. }
  5237.  
  5238. // export and import settings
  5239. async function exportSettings() {
  5240. try {
  5241. const scriptVersion = GM.info.script.version;
  5242. const settingsString = JSON.stringify(USER_CONFIG, null, 2);
  5243. const blob = new Blob([settingsString], { type: 'application/json' });
  5244. const url = URL.createObjectURL(blob);
  5245.  
  5246. const a = document.createElement('a');
  5247. a.href = url;
  5248. a.download = `YouTube-Alchemy_v${scriptVersion}_Backup_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
  5249. document.body.appendChild(a);
  5250. a.click();
  5251. document.body.removeChild(a);
  5252. URL.revokeObjectURL(url);
  5253.  
  5254. showNotification('Settings have been exported.');
  5255. } catch (error) {
  5256. showNotification("Error exporting settings!");
  5257. console.error("YouTubeAlchemy: Error exporting user settings:", error);
  5258. }
  5259. }
  5260.  
  5261. let fileInputSettings;
  5262. async function importSettings() {
  5263. const handleFile = (e) => {
  5264. const file = e.target.files[0];
  5265. if (!file) return;
  5266.  
  5267. const reader = new FileReader();
  5268. reader.onload = (event) => {
  5269. const fileContent = event.target.result;
  5270. try {
  5271. const importedConfig = JSON.parse(fileContent);
  5272. if (typeof importedConfig === 'object' && importedConfig !== null) {
  5273. USER_CONFIG = { ...DEFAULT_CONFIG, ...importedConfig };
  5274. GM.setValue('USER_CONFIG', USER_CONFIG);
  5275. showNotification('Settings have been imported.');
  5276. setTimeout(() => {
  5277. location.reload();
  5278. }, 1000);
  5279. } else {
  5280. showNotification('Invalid JSON format!');
  5281. }
  5282. } catch (error) {
  5283. showNotification('Invalid JSON format!');
  5284. }
  5285. };
  5286. reader.readAsText(file);
  5287. };
  5288.  
  5289. const createOrResetFileInput = () => {
  5290. if (!fileInputSettings) {
  5291. fileInputSettings = document.createElement('input');
  5292. fileInputSettings.type = 'file';
  5293. fileInputSettings.accept = 'application/json';
  5294. fileInputSettings.id = 'fileInputSettings';
  5295. fileInputSettings.style.display = 'none';
  5296. fileInputSettings.addEventListener('change', handleFile);
  5297. document.body.appendChild(fileInputSettings);
  5298. } else {
  5299. fileInputSettings.value = '';
  5300. }
  5301. };
  5302.  
  5303. createOrResetFileInput();
  5304. fileInputSettings.click();
  5305. }
  5306.  
  5307. // function to display a notification for settings change or reset
  5308. function showNotification(message) {
  5309. const overlay = document.createElement('div');
  5310. overlay.classList.add('CentAnni-overlay');
  5311.  
  5312. const modal = document.createElement('div');
  5313. modal.classList.add('notification');
  5314. modal.innerText = message;
  5315.  
  5316. overlay.appendChild(modal);
  5317. document.body.appendChild(overlay);
  5318.  
  5319. setTimeout(() => { overlay.remove(); }, 1000);
  5320. }
  5321.  
  5322. // function to add the transcript exporter buttons
  5323. function buttonLocation(buttons, callback) {
  5324. const masthead = document.querySelector('#end');
  5325. if (masthead) {
  5326. buttons.forEach(({ id, text, clickHandler, tooltip }) => {
  5327.  
  5328. // button wrapper
  5329. const buttonWrapper = document.createElement('div');
  5330. buttonWrapper.classList.add('button-wrapper');
  5331.  
  5332. // buttons
  5333. const button = document.createElement('button');
  5334. button.id = id;
  5335. button.innerText = text;
  5336. button.classList.add('button-style');
  5337. if (id === 'transcript-settings-button') {
  5338. button.classList.add('button-style-settings'); }
  5339.  
  5340. button.addEventListener('click', clickHandler);
  5341.  
  5342. // tooltip div
  5343. const tooltipDiv = document.createElement('div');
  5344. tooltipDiv.innerText = tooltip;
  5345. tooltipDiv.classList.add('button-tooltip');
  5346.  
  5347. // tooltip arrow
  5348. const arrowDiv = document.createElement('div');
  5349. arrowDiv.classList.add('button-tooltip-arrow');
  5350. tooltipDiv.appendChild(arrowDiv);
  5351.  
  5352. // show and hide tooltip on hover
  5353. let tooltipTimeout;
  5354. button.addEventListener('mouseenter', () => {
  5355. tooltipTimeout = setTimeout(() => {
  5356. tooltipDiv.style.visibility = 'visible';
  5357. tooltipDiv.style.opacity = '1';
  5358. }, 700);
  5359. });
  5360.  
  5361. button.addEventListener('mouseleave', () => {
  5362. clearTimeout(tooltipTimeout);
  5363. tooltipDiv.style.visibility = 'hidden';
  5364. tooltipDiv.style.opacity = '0';
  5365. });
  5366.  
  5367. // append button elements
  5368. buttonWrapper.appendChild(button);
  5369. buttonWrapper.appendChild(tooltipDiv);
  5370. masthead.prepend(buttonWrapper);
  5371. });
  5372. } else {
  5373. const observer = new MutationObserver((mutations, obs) => {
  5374. const masthead = document.querySelector('#end');
  5375. if (masthead) {
  5376. obs.disconnect();
  5377. if (callback) callback();
  5378. }
  5379. });
  5380. observer.observe(document.body, {
  5381. childList: true,
  5382. subtree: true
  5383. });
  5384. }
  5385. }
  5386.  
  5387. function addButton() {
  5388. document.querySelectorAll('.button-wrapper').forEach(el => el.remove());
  5389.  
  5390. const buttons = [
  5391. { id: 'transcript-settings-button', text: USER_CONFIG.buttonIcons.settings, clickHandler: showSettingsModal, tooltip: 'YouTube Alchemy Settings', ariaLabel: 'YouTube Alchemy Settings.' },
  5392. { id: 'transcript-download-button', text: USER_CONFIG.buttonIcons.download, clickHandler: handleDownloadClick, tooltip: 'Download Transcript as a Text File', ariaLabel: 'Download Transcript as a Text File.' },
  5393. { id: 'transcript-ChatGPT-button', text: USER_CONFIG.buttonIcons.ChatGPT, clickHandler: handleChatGPTClick, tooltip: 'Copy Transcript with a Prompt and Open ChatGPT', ariaLabel: 'Copy Transcript to Clipboard with a Prompt and Open ChatGPT.' },
  5394. { id: 'transcript-NotebookLM-button', text: USER_CONFIG.buttonIcons.NotebookLM, clickHandler: handleNotebookLMClick, tooltip: 'Copy Transcript and Open NotebookLM', ariaLabel: 'Copy Transcript to Clipboard and Open NotebookLM.' }
  5395. ];
  5396.  
  5397. const buttonsToAdd = buttons.filter(button => button.id === 'transcript-settings-button' || (button.text && button.text.trim() !== ''));
  5398. buttonLocation(buttonsToAdd, addButton);
  5399. }
  5400.  
  5401. function addSettingsButton() {
  5402. document.querySelectorAll('.button-wrapper').forEach(el => el.remove());
  5403. const buttons = [ { id: 'transcript-settings-button', text: USER_CONFIG.buttonIcons.settings, clickHandler: showSettingsModal, tooltip: 'YouTube Alchemy Settings', ariaLabel: 'YouTube Alchemy Settings.' }, ];
  5404. buttonLocation(buttons, addSettingsButton);
  5405. }
  5406.  
  5407. // functions to handle the button clicks
  5408. function handleChatGPTClick() { handleTranscriptAction(function() { selectAndCopyTranscript('ChatGPT'); }); }
  5409. function handleNotebookLMClick() { handleTranscriptAction(function() { selectAndCopyTranscript('NotebookLM'); }); }
  5410. function handleDownloadClick() { handleTranscriptAction(downloadTranscriptAsText); }
  5411.  
  5412. // function to check for a transcript
  5413. function handleTranscriptAction(callback) {
  5414.  
  5415. // check if the transcript button exists or at least the div
  5416. const transcriptButton = document.querySelector('#button-container button[aria-label="Show transcript"]');
  5417. if (transcriptButton) {
  5418. } else {
  5419. const transcriptSection = document.querySelector('ytd-video-description-transcript-section-renderer');
  5420. if (transcriptSection) {
  5421. } else {
  5422. alert('Transcript unavailable or cannot be found.\nEnsure the "Show transcript" button exists.\nReload this page to try again.');
  5423. console.log("YouTubeAlchemy: Transcript button not found. Subtitles or closed captions are unavailable or language is unsupported. Reload this page to try again.");
  5424. return;
  5425. }
  5426. }
  5427.  
  5428. // check if the transcript has loaded
  5429. const transcriptItems = document.querySelectorAll('ytd-transcript-segment-list-renderer ytd-transcript-segment-renderer');
  5430. if (transcriptItems.length > 0) {
  5431. callback();
  5432. } else {
  5433. alert('Transcript has not loaded successfully.\nReload this page to try again.');
  5434. console.log("YouTubeAlchemy: Transcript has not loaded.");
  5435. return;
  5436. }
  5437. }
  5438.  
  5439. // function to get video information
  5440. function getVideoInfo() {
  5441. //const ytTitle = document.querySelector('#title yt-formatted-string')?.textContent.trim() || 'N/A';
  5442. const ytTitle = document.querySelector('div#title h1 > yt-formatted-string')?.textContent.trim() || 'N/A';
  5443. //const channelName = document.querySelector('ytd-channel-name#channel-name yt-formatted-string#text a')?.textContent.trim() || 'N/A';
  5444. const channelName = document.querySelector( 'ytd-video-owner-renderer ytd-channel-name#channel-name yt-formatted-string#text a' )?.textContent.trim() || 'N/A';
  5445. const uploadDate = document.querySelector('ytd-video-primary-info-renderer #info-strings yt-formatted-string')?.textContent.trim() || 'N/A';
  5446. const videoURL = window.location.href;
  5447.  
  5448. return { ytTitle, channelName, uploadDate, videoURL };
  5449. }
  5450.  
  5451. // function to get the transcript text
  5452. function getTranscriptText() {
  5453. const transcriptContainer = document.querySelector('ytd-transcript-segment-list-renderer #segments-container');
  5454. if (!transcriptContainer) {
  5455. //console.error("YouTubeAlchemy: Transcript container not found.");
  5456. return '';
  5457. }
  5458.  
  5459. const transcriptElements = transcriptContainer.children;
  5460. let transcriptLines = [];
  5461.  
  5462. Array.from(transcriptElements).forEach(element => {
  5463. if (element.tagName === 'YTD-TRANSCRIPT-SECTION-HEADER-RENDERER') {
  5464.  
  5465. // chapter header segment
  5466. if (USER_CONFIG.includeChapterHeaders) {
  5467. const chapterTitleElement = element.querySelector('h2 > span');
  5468. if (chapterTitleElement) {
  5469. const chapterTitle = chapterTitleElement.textContent.trim();
  5470. transcriptLines.push(`\nChapter: ${chapterTitle}`);
  5471. }
  5472. }
  5473. } else if (element.tagName === 'YTD-TRANSCRIPT-SEGMENT-RENDERER') {
  5474.  
  5475. // transcript segment
  5476. const timeElement = element.querySelector('.segment-timestamp');
  5477. const textElement = element.querySelector('.segment-text');
  5478. if (timeElement && textElement) {
  5479. const time = timeElement.textContent.trim();
  5480. const text = textElement.innerText.trim();
  5481. if (USER_CONFIG.includeTimestamps) {
  5482. transcriptLines.push(`${time} ${text}`);
  5483. } else { transcriptLines.push(`${text}`); }
  5484. }
  5485. }
  5486. });
  5487.  
  5488. return transcriptLines.join('\n');
  5489. }
  5490.  
  5491. // function to select and copy the transcript into the clipboard
  5492. function selectAndCopyTranscript(target) {
  5493. const transcriptText = getTranscriptText();
  5494. const { ytTitle, channelName, uploadDate, videoURL } = getVideoInfo();
  5495.  
  5496. let finalText = '';
  5497. let targetUrl = '';
  5498.  
  5499. if (target === 'ChatGPT') {
  5500. finalText = `YouTube Transcript:\n${transcriptText.trimStart()}\n\n\nAdditional Information about the YouTube Video:\nTitle: ${ytTitle}\nChannel: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nTask Instructions:\n${USER_CONFIG.ChatGPTPrompt}`;
  5501. targetUrl = USER_CONFIG.targetChatGPTUrl;
  5502. } else if (target === 'NotebookLM') {
  5503. finalText = `Information about the YouTube Video:\nTitle: ${ytTitle}\nChannel: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nYouTube Transcript:\n${transcriptText.trimStart()}`;
  5504. targetUrl = USER_CONFIG.targetNotebookLMUrl;
  5505. }
  5506.  
  5507. navigator.clipboard.writeText(finalText).then(() => {
  5508. showNotification('Transcript copied. Opening website . . .');
  5509. if (USER_CONFIG.openSameTab) { window.open(targetUrl, '_self');
  5510. } else { window.open(targetUrl, '_blank'); }
  5511. });
  5512. }
  5513.  
  5514. // function to get the formatted transcript with video details for the text file
  5515. function getFormattedTranscript() {
  5516. const transcriptText = getTranscriptText();
  5517. const { ytTitle, channelName, uploadDate, videoURL } = getVideoInfo();
  5518.  
  5519. return `Information about the YouTube Video:\nTitle: ${ytTitle}\nChannel: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nYouTube Transcript:\n${transcriptText.trimStart()}`;
  5520. }
  5521.  
  5522. // function to download the transcript as a text file
  5523. function downloadTranscriptAsText() {
  5524. const finalText = getFormattedTranscript();
  5525. const { ytTitle, channelName, uploadDate } = getVideoInfo();
  5526. const blob = new Blob([finalText], { type: 'text/plain' });
  5527.  
  5528. const sanitize = str => str.replace(/[<>:"/\\|?*]+/g, '');
  5529. const uploadDateFormatted = new Date(uploadDate).toLocaleDateString("en-CA");
  5530.  
  5531. // naming of text file based on user setting
  5532. let fileName = '';
  5533. switch (USER_CONFIG.fileNamingFormat) {
  5534. case 'title-channel': fileName = `${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`; break;
  5535. case 'channel-title': fileName = `${sanitize(channelName)} - ${sanitize(ytTitle)}.txt`; break;
  5536. case 'date-title-channel': fileName = `${sanitize(uploadDateFormatted)} - ${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`; break;
  5537. case 'date-channel-title': fileName = `${sanitize(uploadDateFormatted)} - ${sanitize(channelName)} - ${sanitize(ytTitle)}.txt`; break;
  5538. default: fileName = `${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`;
  5539. }
  5540.  
  5541. const url = URL.createObjectURL(blob);
  5542.  
  5543. // create a temporary anchor element to trigger the download
  5544. const a = document.createElement('a');
  5545. a.href = url;
  5546. a.download = fileName;
  5547. document.body.appendChild(a);
  5548. a.click();
  5549.  
  5550. // clean up
  5551. document.body.removeChild(a);
  5552. URL.revokeObjectURL(url);
  5553. showNotification('File has been downloaded.');
  5554. }
  5555.  
  5556. // function to preload the transcript
  5557. function preLoadTranscript() {
  5558. return new Promise((resolve, reject) => {
  5559. document.querySelectorAll('.button-wrapper').forEach(el => el.remove());
  5560. if (isLiveVideo) {
  5561. showNotificationError("Live Stream, No Transcript");
  5562. reject();
  5563. return;
  5564. }
  5565.  
  5566. if (!hasTranscriptPanel) {
  5567. showNotificationError("Transcript Not Available");
  5568. reject();
  5569. return;
  5570. }
  5571.  
  5572. const masthead = document.querySelector("#end");
  5573. const notification = document.createElement("div");
  5574. notification.classList.add("notification-error", "loading");
  5575. const textSpan = document.createElement("span");
  5576. textSpan.textContent = "Transcript Is Loading";
  5577. notification.appendChild(textSpan);
  5578. masthead.prepend(notification);
  5579.  
  5580. if (!USER_CONFIG.autoOpenTranscript) {
  5581. transcriptPanel.classList.add("transcript-preload");
  5582. transcriptPanel.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  5583. }
  5584.  
  5585. let loaded = false;
  5586.  
  5587. const observer = new MutationObserver(() => {
  5588. const transcriptItems = transcriptPanel.querySelectorAll("ytd-transcript-segment-renderer");
  5589. if (transcriptItems.length > 0) {
  5590. loaded = true;
  5591. cleanup(false);
  5592. clearTimeout(fallbackTimer);
  5593. observer.disconnect();
  5594. if (USER_CONFIG.transcriptTimestamps) enableTimestamps();
  5595. if (USER_CONFIG.defaultTranscriptLanguage !== 'auto') setTimeout(() => { setTranscriptLanguage(); }, 250);
  5596. resolve();
  5597. }
  5598. });
  5599.  
  5600. observer.observe(transcriptPanel, { childList: true, subtree: true });
  5601.  
  5602. const fallbackTimer = setTimeout(() => {
  5603. if (!loaded) {
  5604. console.error("YouTubeAlchemy: The transcript took too long to load. Reload this page to try again.");
  5605. observer.disconnect();
  5606. cleanup(true);
  5607. reject();
  5608. }
  5609. }, 10000);
  5610.  
  5611. function cleanup(failed) {
  5612. notification.remove();
  5613. if (!USER_CONFIG.autoOpenTranscript) {
  5614. transcriptPanel.classList.remove("transcript-preload");
  5615. transcriptPanel.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  5616. }
  5617. if (failed) { showNotificationError("Transcript Failed to Load"); }
  5618. }
  5619. });
  5620. }
  5621.  
  5622. // function to display a notification if transcript cannot be found
  5623. function showNotificationError(message) {
  5624. const masthead = document.querySelector('#end');
  5625. const notification = document.createElement('div');
  5626. notification.textContent = message;
  5627. notification.classList.add('notification-error');
  5628.  
  5629. masthead.prepend(notification);
  5630.  
  5631. if (document.visibilityState === 'hidden') {
  5632. document.addEventListener('visibilitychange', function handleVisibilityChange() {
  5633. if (document.visibilityState === 'visible') {
  5634. document.removeEventListener('visibilitychange', handleVisibilityChange);
  5635. setTimeout(() => notification.remove(), 3000);
  5636. }
  5637. });
  5638. } else { setTimeout(() => notification.remove(), 3000); }
  5639. }
  5640.  
  5641. // helper function to switch theater mode
  5642. function toggleTheaterMode() {
  5643. const event = new KeyboardEvent('keydown', {
  5644. key: 'T',
  5645. code: 'KeyT',
  5646. keyCode: 84,
  5647. bubbles: true,
  5648. cancelable: true
  5649. });
  5650.  
  5651. document.dispatchEvent(event);
  5652. }
  5653.  
  5654. // set default transcript language
  5655. function setTranscriptLanguage() {
  5656. // define language mapping
  5657. const languageMap = {
  5658. 'english': { english: 'English', native: 'English', englishTranslation: 'English' },
  5659. 'spanish': { english: 'Spanish', native: 'Español', englishTranslation: 'Inglés' },
  5660. 'hindi': { english: 'Hindi', native: 'हिन्दी', englishTranslation: 'अंग्रेज़ी' },
  5661. 'portuguese': { english: 'Portuguese', native: 'Português', englishTranslation: 'Inglês' },
  5662. 'german': { english: 'German', native: 'Deutsch', englishTranslation: 'Englisch' },
  5663. 'french': { english: 'French', native: 'Français', englishTranslation: 'Anglais' },
  5664. 'italian': { english: 'Italian', native: 'Italiano', englishTranslation: 'Inglese' },
  5665. 'dutch': { english: 'Dutch', native: 'Nederlands', englishTranslation: 'Engels' },
  5666. 'polish': { english: 'Polish', native: 'Polski', englishTranslation: 'Angielski' },
  5667. 'hebrew': { english: 'Hebrew', native: 'עברית', englishTranslation: 'אנגלית' }
  5668. };
  5669.  
  5670. // find the menu trigger that displays the current language and check for active language
  5671. const menuTrigger = document.querySelector('tp-yt-paper-button#label');
  5672. const labelTextEl = document.querySelector('#label-text');
  5673. if (!menuTrigger || !labelTextEl) return;
  5674.  
  5675. const preferredEnglish = languageMap[USER_CONFIG.defaultTranscriptLanguage].english;
  5676. const preferredNative = languageMap[USER_CONFIG.defaultTranscriptLanguage].native;
  5677. const currentText = labelTextEl.textContent.trim();
  5678. if (currentText.includes(preferredEnglish) || currentText.includes(preferredNative)) return;
  5679.  
  5680. // gather all language options and run it against preferred with english as a fallback
  5681. const itemsArray = Array.from(document.querySelectorAll('tp-yt-paper-item'));
  5682. const preferredOptionEnglish = itemsArray.find(item => item.textContent.includes(preferredEnglish));
  5683. const preferredOptionNative = itemsArray.find(item => item.textContent.includes(preferredNative));
  5684. const englishTranslations = Object.values(languageMap).map(lang => lang.englishTranslation);
  5685. const englishFallback = itemsArray.find(item => englishTranslations.some(translation => item.textContent.includes(translation)) );
  5686.  
  5687. // select first non-null option in order of preference
  5688. const optionToClick = preferredOptionNative || preferredOptionEnglish || englishFallback;
  5689. if (optionToClick) optionToClick.click();
  5690. }
  5691.  
  5692. // function tab view on video page
  5693. function tabView() {
  5694. const watchFlexy = document.querySelector('ytd-watch-flexy');
  5695. if (!watchFlexy) return;
  5696.  
  5697. let timestampsEnabled = false;
  5698. let lastActiveTab = null;
  5699. let currentActiveTab = null;
  5700. let isFirstRun = true;
  5701. let tabElements = [];
  5702. let subheaderDiv;
  5703.  
  5704. // helper function to determine the default tab
  5705. // priority: transcript > chapters > info
  5706. function determineActiveTab() {
  5707. let activeTabId = 'tab-1';
  5708. if (USER_CONFIG.autoOpenTranscript) { const tab5 = document.querySelector('[data-tab="tab-5"]'); if (tab5) return 'tab-5'; }
  5709. if (USER_CONFIG.autoOpenChapters) { const tab4 = document.querySelector('[data-tab="tab-4"]'); if (tab4) return 'tab-4'; }
  5710. return activeTabId;
  5711. }
  5712.  
  5713. // helper function active tab
  5714. function activateTab(tabId) {
  5715. const tab = document.querySelector(`[data-tab="${tabId}"]`);
  5716. if (tab) tab.classList.add('active');
  5717. currentActiveTab = tabId;
  5718. }
  5719.  
  5720. // function to update tabView based on player layout
  5721. function updateTabView() {
  5722. const isTheater = watchFlexy.hasAttribute('theater');
  5723. const isDefault = watchFlexy.hasAttribute('default-layout');
  5724.  
  5725. // if no mode change do nothing
  5726. if ((isTheater && isTheaterMode === true) || (isDefault && isTheaterMode === false)) {
  5727. if (isFirstRun && isTheaterMode) {
  5728. if (subheaderDiv) subheaderDiv.addEventListener('click', handleTabViewTabClick);
  5729. isFirstRun = false;
  5730. } else if (isFirstRun && !isTheaterMode) isFirstRun = false;
  5731. }
  5732.  
  5733. if (isTheater) {
  5734. isTheaterMode = true;
  5735.  
  5736. const activeTab = document.querySelector('.CentAnni-tabView-tab.active');
  5737. if (activeTab) lastActiveTab = activeTab.dataset.tab;
  5738.  
  5739. const tabs = document.querySelectorAll('.CentAnni-tabView-tab');
  5740. tabs.forEach(tab => tab.classList.remove('active'));
  5741. currentActiveTab = null;
  5742.  
  5743. if (subheaderDiv) subheaderDiv.addEventListener('click', handleTabViewTabClick);
  5744. } else if (isDefault) {
  5745. isTheaterMode = false;
  5746. if (lastActiveTab) activateTab(lastActiveTab);
  5747. else activateTab(determineActiveTab());
  5748.  
  5749. subheaderDiv.removeEventListener('click', handleTabViewTabClick);
  5750. }
  5751. }
  5752.  
  5753. // mode change, navigation, and clean up
  5754. document.addEventListener('yt-set-theater-mode-enabled', updateTabView);
  5755. document.addEventListener('yt-navigate-start', () => {
  5756. tabElements = [];
  5757. subheaderDiv.removeEventListener('click', handleTabViewTabClick);
  5758. document.removeEventListener('yt-set-theater-mode-enabled', updateTabView);
  5759. tabElements.forEach(tab => { tab.element.removeEventListener('click', tab.handler); });
  5760. if (videoInfoTimeoutId) { clearTimeout(videoInfoTimeoutId); videoInfoTimeoutId = null; }
  5761. document.querySelectorAll('.CentAnni-tabView-tab').forEach(tab => tab.classList.remove('active') );
  5762. if (hasTranscriptPanel) transcriptPanel.remove();
  5763. if (hasChapterPanel) chapterPanel.remove();
  5764. });
  5765.  
  5766. // click listener for tabView buttons
  5767. function handleTabViewTabClick(event) {
  5768. const tab = event.target.closest('.CentAnni-tabView-tab');
  5769. if (!tab || !isTheaterMode) return;
  5770. lastActiveTab = tab.dataset.tab;
  5771. toggleTheaterMode();
  5772. }
  5773.  
  5774. // include date in info text under videos unless live
  5775. const infoContainer = document.querySelector('#info-container');
  5776. const infoTime = infoContainer?.querySelector('yt-formatted-string span.bold:nth-child(3)');
  5777.  
  5778. if (!isLiveVideo) {
  5779. if (infoTime) {
  5780. if (!infoTime.parentNode?.querySelector('.CentAnni-info-date')) {
  5781. const dateStringElement = document.querySelector('#info-strings yt-formatted-string');
  5782. const dateString = dateStringElement?.textContent?.trim() ?? "";
  5783.  
  5784. if (dateString) {
  5785. const newSpan = document.createElement('span');
  5786. newSpan.classList.add('CentAnni-info-date', 'bold', 'style-scope', 'yt-formatted-string');
  5787. newSpan.textContent = `(${dateString})`;
  5788.  
  5789. infoTime.parentNode?.insertBefore(newSpan, infoTime.nextSibling);
  5790. }
  5791. }
  5792. }
  5793. }
  5794.  
  5795. // tabView location
  5796. const secondaryElement = watchFlexy.querySelector('#secondary');
  5797. if (!secondaryElement) return;
  5798.  
  5799. // grab the info, chapter, and transcript panels and open one by default
  5800. const selector = 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-structured-description"]';
  5801. let videoInfo = document.querySelector(selector);
  5802. let videoInfoTimeoutId;
  5803. if (!videoInfo) {
  5804. videoInfoTimeoutId = setTimeout(() => {
  5805. videoInfo = document.querySelector(selector);
  5806. videoInfoTimeoutId = null;
  5807. }, 2000);
  5808. }
  5809.  
  5810. if (USER_CONFIG.autoOpenTranscript && transcriptPanel) {
  5811. transcriptPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  5812. if (!USER_CONFIG.YouTubeTranscriptExporter && !timestampsEnabled && USER_CONFIG.transcriptTimestamps) { waitForTranscriptWithoutYTE(enableTimestamps); timestampsEnabled = true; }
  5813. if (USER_CONFIG.defaultTranscriptLanguage !== 'auto' && !USER_CONFIG.YouTubeTranscriptExporter) waitForTranscriptWithoutYTE(() => { setTimeout(() => { setTranscriptLanguage(); }, 250); });
  5814. }
  5815. else if (USER_CONFIG.autoOpenChapters && chapterPanel) chapterPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  5816. else if (videoInfo) videoInfo.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  5817.  
  5818. const clipPanel = document.querySelector('ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-clip-create"]');
  5819. if (clipPanel && clipPanel.getAttribute('visibility') === 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') {
  5820. clipPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
  5821. }
  5822.  
  5823. // create the main tabView container
  5824. const newDiv = document.createElement('div');
  5825. newDiv.classList.add('CentAnni-tabView');
  5826.  
  5827. // create the header
  5828. const headerDiv = document.createElement('div');
  5829. headerDiv.classList.add('CentAnni-tabView-header');
  5830.  
  5831. // create the subheader
  5832. subheaderDiv = document.createElement('div');
  5833. subheaderDiv.classList.add('CentAnni-tabView-subheader');
  5834.  
  5835. // define the tabs
  5836. const tabs = [
  5837. 'Info',
  5838. ...(!isLiveVideo ? ['Comments'] : []),
  5839. ...(hasPlaylistPanel ? ['Playlist'] : []),
  5840. ...(document.querySelector('ytd-donation-shelf-renderer') && !USER_CONFIG.hideFundraiser ? ['Donation'] : []),
  5841. 'Videos',
  5842. ...((!isLiveVideo && hasChapterPanel) ? ['Chapters'] : []),
  5843. ...((!isLiveVideo && hasTranscriptPanel) ? ['Transcript'] : []),
  5844. ];
  5845.  
  5846. // define the IDs
  5847. const tabIds = {
  5848. 'Info': 'tab-1',
  5849. 'Comments': 'tab-2',
  5850. 'Playlist': 'tab-6',
  5851. 'Donation': 'tab-9',
  5852. 'Videos': 'tab-3',
  5853. 'Chapters': 'tab-4',
  5854. 'Transcript': 'tab-5'
  5855. };
  5856.  
  5857. // create content sections tabs
  5858. const contentSections = [];
  5859. tabs.forEach((tabText, index) => {
  5860. const contentDiv = document.createElement('div');
  5861. contentDiv.classList.add('CentAnni-tabView-content');
  5862. contentDiv.id = tabIds[tabText];
  5863. if (index === 0) {
  5864. contentDiv.classList.add('active');
  5865. currentActiveTab = tabIds[tabText];
  5866. }
  5867. contentSections.push(contentDiv);
  5868. });
  5869.  
  5870. // populate the comments sections
  5871. const videoComments = document.querySelector('ytd-comments#comments');
  5872. if (videoComments && contentSections[1]) contentSections[1].appendChild(videoComments);
  5873.  
  5874. // create each tab link and add click behavior
  5875. tabs.forEach((tabText, index) => {
  5876. const tabLink = document.createElement('a');
  5877. tabLink.classList.add('CentAnni-tabView-tab');
  5878. tabLink.textContent = tabText;
  5879. tabLink.href = `#${tabIds[tabText]}`;
  5880. tabLink.dataset.tab = tabIds[tabText];
  5881.  
  5882. const tabClickHandler = (event) => {
  5883. event.preventDefault();
  5884.  
  5885. // if clicked tab is active enter theater mode
  5886. if (currentActiveTab === tabIds[tabText] && !isTheaterMode) {
  5887. event.stopPropagation();
  5888. toggleTheaterMode();
  5889. return;
  5890. } else currentActiveTab = tabIds[tabText];
  5891.  
  5892. // remove 'active' from all tabs
  5893. document.querySelectorAll('.CentAnni-tabView-tab').forEach(tab => tab.classList.remove('active'));
  5894.  
  5895. // mark clicked one as active
  5896. tabLink.classList.add('active');
  5897.  
  5898. // hide all content sections
  5899. document.querySelectorAll('.CentAnni-tabView-content').forEach(content => {
  5900. content.classList.remove('active');
  5901. });
  5902.  
  5903. // show the target content section
  5904. const targetDiv = document.querySelector(`#${tabIds[tabText]}`);
  5905. if (targetDiv) targetDiv.classList.add('active');
  5906.  
  5907. // info panel
  5908. if (videoInfo) videoInfo.setAttribute('visibility', tabText === 'Info' ? 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' : 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
  5909.  
  5910. // playlist container
  5911. if (hasPlaylistPanel) {
  5912. if (tabText === 'Playlist') {
  5913. playlistPanel.classList.add('CentAnni-tabView-content-active');
  5914. playlistPanel.classList.remove('CentAnni-tabView-content-hidden');
  5915. if (playlistSelectedVideo) setTimeout(() => { playlistSelectedVideo.scrollIntoView({ behavior: 'instant', block: 'center' }); },25);
  5916. } else {
  5917. playlistPanel.classList.add('CentAnni-tabView-content-hidden');
  5918. playlistPanel.classList.remove('CentAnni-tabView-content-active');
  5919. }
  5920. }
  5921.  
  5922. // donation-shelf
  5923. const donationShelf = watchFlexy.querySelector('#donation-shelf');
  5924. if (donationShelf) {
  5925. if (tabText === 'Donation') {
  5926. donationShelf.classList.add('CentAnni-tabView-content-active');
  5927. donationShelf.classList.remove('CentAnni-tabView-content-hidden');
  5928. } else {
  5929. donationShelf.classList.add('CentAnni-tabView-content-hidden');
  5930. donationShelf.classList.remove('CentAnni-tabView-content-active');
  5931. }
  5932. }
  5933.  
  5934. // more videos
  5935. const videoMore = document.querySelector('#related.style-scope.ytd-watch-flexy');
  5936. if (videoMore) {
  5937. if (tabText === 'Videos') {
  5938. videoMore.classList.add('CentAnni-tabView-content-attiva');
  5939. videoMore.classList.remove('CentAnni-tabView-content-nascosta');
  5940. } else {
  5941. videoMore.classList.add('CentAnni-tabView-content-nascosta');
  5942. videoMore.classList.remove('CentAnni-tabView-content-attiva');
  5943. }
  5944. }
  5945.  
  5946. // chapters panel
  5947. if (chapterPanel) chapterPanel.setAttribute('visibility', tabText === 'Chapters' ? 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' : 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
  5948.  
  5949. // transcript panel
  5950. if (transcriptPanel) {
  5951. if (tabText === 'Transcript') {
  5952. transcriptPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  5953. if (!USER_CONFIG.YouTubeTranscriptExporter && !timestampsEnabled && USER_CONFIG.transcriptTimestamps) { waitForTranscriptWithoutYTE(enableTimestamps); timestampsEnabled = true; }
  5954. if (USER_CONFIG.defaultTranscriptLanguage !== 'auto' && !USER_CONFIG.YouTubeTranscriptExporter) waitForTranscriptWithoutYTE(() => { setTimeout(() => { setTranscriptLanguage(); }, 250); });
  5955. } else transcriptPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
  5956. }
  5957. };
  5958.  
  5959. tabElements.push({ element: tabLink, handler: tabClickHandler });
  5960. tabLink.addEventListener('click', tabClickHandler);
  5961. subheaderDiv.appendChild(tabLink);
  5962. });
  5963.  
  5964. headerDiv.appendChild(subheaderDiv);
  5965. newDiv.appendChild(headerDiv);
  5966. contentSections.forEach(section => newDiv.appendChild(section));
  5967.  
  5968. const oldDiv = document.querySelector(".CentAnni-tabView");
  5969. if (oldDiv) oldDiv.replaceWith(newDiv);
  5970. else secondaryElement.insertBefore(newDiv, secondaryElement.firstChild);
  5971.  
  5972. // add chapter titles under videos
  5973. function chapterTitles() {
  5974. const fullscreenContainer = document.querySelector('#movie_player > div.ytp-chrome-bottom');
  5975. const titleElement = document.querySelector('ytd-watch-flexy .ytp-chapter-container');
  5976. const normalContainer = document.querySelector('ytd-watch-flexy #description');
  5977. if (!fullscreenContainer || !titleElement || !normalContainer) return;
  5978.  
  5979. const parent = titleElement.parentNode;
  5980. parent.removeChild(titleElement);
  5981.  
  5982. const containerChapterTitle = document.createElement('div');
  5983. containerChapterTitle.classList.add('CentAnni-chapter-title', 'bold', 'style-scope', 'yt-formatted-string');
  5984.  
  5985. const label = document.createElement('span');
  5986. label.textContent = 'Chapter:';
  5987.  
  5988. containerChapterTitle.appendChild(label);
  5989. containerChapterTitle.appendChild(titleElement);
  5990.  
  5991. const existingContainerChapterTitle = document.querySelector('.CentAnni-chapter-title');
  5992. if (existingContainerChapterTitle) existingContainerChapterTitle.replaceWith(containerChapterTitle);
  5993. else normalContainer.appendChild(containerChapterTitle);
  5994.  
  5995. const updateContainer = () => {
  5996. const currentContainer = document.querySelector('.CentAnni-chapter-title');
  5997. const targetContainer = document.getElementById('movie_player')?.classList.contains('ytp-fullscreen')
  5998. ? fullscreenContainer
  5999. : normalContainer;
  6000.  
  6001. if (currentContainer) targetContainer.appendChild(currentContainer);
  6002. else targetContainer.appendChild(containerChapterTitle);
  6003. };
  6004.  
  6005. function handleFullscreenChangeCT() { updateContainer(); }
  6006. document.removeEventListener('fullscreenchange', handleFullscreenChangeCT);
  6007. document.addEventListener('fullscreenchange', handleFullscreenChangeCT);
  6008. }
  6009.  
  6010. updateTabView();
  6011. if (USER_CONFIG.tabViewChapters) chapterTitles();
  6012. }
  6013.  
  6014. // function to hide the 'X Products' span
  6015. function hideProductsSpan() {
  6016. const spans = document.querySelectorAll('yt-formatted-string#info > span');
  6017. spans.forEach(span => {
  6018. if (span.textContent.includes('products')) {
  6019. span.style.display = 'none';
  6020. }
  6021. });
  6022. }
  6023.  
  6024. // playback speed functions
  6025. function initialSpeed() {
  6026. document.removeEventListener('yt-player-updated', initialSpeed);
  6027.  
  6028. const video = document.querySelector('video.html5-main-video[style]');
  6029. if (!video) return;
  6030.  
  6031. if (USER_CONFIG.VerifiedArtist) {
  6032. const isMusicMeta = document.querySelector('meta[itemprop="genre"][content="Music"]');
  6033. const isMusicVideoTag = document.querySelector('ytd-badge-supported-renderer .badge-style-type-verified-artist');
  6034. if (isMusicMeta && isMusicVideoTag) { video.playbackRate = 1; return; }
  6035. }
  6036.  
  6037. if (new URL(window.location.href).pathname.startsWith('/shorts/')) video.playbackRate = lastUserRate !== null ? lastUserRate : defaultSpeed;
  6038. else if (document.querySelector('.ytp-time-display')?.classList.contains('ytp-live')) video.playbackRate = 1;
  6039. else video.playbackRate = defaultSpeed;
  6040. }
  6041.  
  6042. // controller
  6043. function createPlaybackSpeedController(options = {}) {
  6044. const { videoSelector = 'video.html5-main-video[style]' } = options;
  6045. const MIN_SPEED = 0.25;
  6046. const MAX_SPEED = 17;
  6047. const STEP_SIZE = 0.25;
  6048.  
  6049. const video = document.querySelector(videoSelector);
  6050. if (!video) return null;
  6051.  
  6052. function updateSpeedDisplay() {
  6053. const speedDisplay = document.getElementById("CentAnni-speed-display");
  6054. if (speedDisplay && video) speedDisplay.textContent = `${video.playbackRate}x`;
  6055. }
  6056.  
  6057. // set playback speed and update display
  6058. function setSpeed() {
  6059. ignoreRateChange = true;
  6060. const clamped = Math.max(MIN_SPEED, Math.min(MAX_SPEED, video.playbackRate));
  6061. video.playbackRate = clamped;
  6062. video.mozPreservesPitch = video.webkitPreservesPitch = video.preservePitch = true;
  6063.  
  6064. updateSpeedDisplay();
  6065. lastUserRate = video.playbackRate;
  6066. if (speedNotification) showSpeedNotification(video.playbackRate);
  6067. }
  6068.  
  6069. // initial speed setting
  6070. function initializeSpeed() {
  6071. if (isShortPage) { video.playbackRate = lastUserRate !== null ? lastUserRate : defaultSpeed; }
  6072. else if ((USER_CONFIG.VerifiedArtist && isMusicVideo) || isLiveVideo || isLiveStream) video.playbackRate = 1;
  6073. else video.playbackRate = defaultSpeed;
  6074.  
  6075. video.addEventListener('ratechange', updateSpeedDisplay);
  6076. setSpeed();
  6077. speedNotification = true;
  6078. }
  6079.  
  6080. // handle rate change events
  6081. function onRateChange() {
  6082. if (ignoreRateChange) { ignoreRateChange = false; return; }
  6083. else video.playbackRate = lastUserRate;
  6084. }
  6085.  
  6086. // keyboard control handler
  6087. function playbackSpeedKeyListener(event) {
  6088. const key = event.key.toLowerCase();
  6089. const isValidKey = ['a', 's', 'd'].includes(key);
  6090. const isTextInput = [
  6091. 'input',
  6092. 'textarea',
  6093. 'select',
  6094. 'contenteditable'
  6095. ].includes(event.target.tagName.toLowerCase()) || event.target.isContentEditable;
  6096.  
  6097. if (!video || !isValidKey || isTextInput) return;
  6098.  
  6099. switch (key) {
  6100. case 's':
  6101. video.playbackRate = (video.playbackRate !== 1 ? 1 : defaultSpeed);
  6102. setSpeed();
  6103. break;
  6104. case 'a':
  6105. video.playbackRate = video.playbackRate - STEP_SIZE;
  6106. setSpeed();
  6107. break;
  6108. case 'd':
  6109. video.playbackRate = video.playbackRate + STEP_SIZE;
  6110. setSpeed();
  6111. break;
  6112. }
  6113. }
  6114.  
  6115. // setup event listeners
  6116. function setupEventListeners() {
  6117. video.addEventListener('ratechange', onRateChange);
  6118. window.addEventListener('keydown', playbackSpeedKeyListener);
  6119.  
  6120. // clean up event listeners on navigation
  6121. document.addEventListener('yt-navigate-start', () => {
  6122. window.removeEventListener('keydown', playbackSpeedKeyListener);
  6123. video.removeEventListener('ratechange', updateSpeedDisplay);
  6124. video.removeEventListener('ratechange', onRateChange);
  6125. speedNotification = false;
  6126. });
  6127. }
  6128.  
  6129. // initialize function return controller API
  6130. initializeSpeed();
  6131. setupEventListeners();
  6132. return {
  6133. video,
  6134. setSpeed,
  6135. STEP_SIZE,
  6136. };
  6137. }
  6138.  
  6139. // playback speed regular videos
  6140. async function videoPlaybackSpeed() {
  6141. // wait for menu renderer
  6142. const menuRendererSelector = 'ytd-watch-flexy #primary #top-row #top-level-buttons-computed';
  6143. let menuRenderer = document.querySelector(menuRendererSelector);
  6144.  
  6145. if (!menuRenderer) {
  6146. await new Promise((resolve) => {
  6147. const observer = new MutationObserver((mutations, obs) => {
  6148. if ((menuRenderer = document.querySelector(menuRendererSelector))) {
  6149. clearTimeout(timeout);
  6150. obs.disconnect();
  6151. resolve();
  6152. }
  6153. });
  6154.  
  6155. observer.observe(document.body, {
  6156. childList: true,
  6157. subtree: true
  6158. });
  6159.  
  6160. const timeout = setTimeout(() => {
  6161. observer.disconnect();
  6162. resolve();
  6163. }, 5000);
  6164. });
  6165.  
  6166. if (!menuRenderer) return;
  6167. }
  6168.  
  6169. // create controller
  6170. const controller = createPlaybackSpeedController();
  6171. if (!controller) return;
  6172. const { video, setSpeed, STEP_SIZE } = controller;
  6173.  
  6174. // create container for buttons, display speed, and icon
  6175. const oldControlDiv = document.getElementById("CentAnni-playback-speed-control");
  6176. if (menuRenderer) {
  6177. const controlDiv = document.createElement("div");
  6178. controlDiv.id = "CentAnni-playback-speed-control";
  6179. controlDiv.classList.add(
  6180. "CentAnni-playback-control",
  6181. "top-level-buttons",
  6182. "style-scope",
  6183. "ytd-menu-renderer"
  6184. );
  6185.  
  6186. // create the SVG icon
  6187. const iconDiv = document.createElement("div");
  6188. iconDiv.className = "CentAnni-playback-speed-icon";
  6189.  
  6190. const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  6191. svg.setAttribute("fill", "none");
  6192. svg.setAttribute("height", "24");
  6193. svg.setAttribute("viewBox", "0 0 24 24");
  6194. svg.setAttribute("width", "24");
  6195.  
  6196. const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  6197. path.setAttribute("d", "M10,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z");
  6198. path.setAttribute("fill", "whitesmoke");
  6199.  
  6200. svg.appendChild(path);
  6201. iconDiv.appendChild(svg);
  6202. controlDiv.appendChild(iconDiv);
  6203.  
  6204. // display the speed
  6205. const speedDisplay = document.createElement("span");
  6206. speedDisplay.id = "CentAnni-speed-display";
  6207. speedDisplay.classList.add(
  6208. "CentAnni-playback-speed-display",
  6209. "animated-rolling-number-wiz"
  6210. );
  6211. speedDisplay.textContent = video ? `${video.playbackRate}x` : `${defaultSpeed}x`;
  6212.  
  6213. // create minus and plus buttons
  6214. const createButton = (change) => {
  6215. const button = document.createElement("button");
  6216. button.textContent = change > 0 ? "+" : "-";
  6217. button.classList.add(
  6218. "CentAnni-playback-speed-button",
  6219. "yt-spec-button-shape-next",
  6220. "yt-spec-button-shape-next--tonal",
  6221. "yt-spec-button-shape-next--mono",
  6222. "yt-spec-button-shape-next--size-m",
  6223. "yt-spec-button-shape-next--icon-button"
  6224. );
  6225. button.addEventListener("click", () => {
  6226. video.playbackRate = video.playbackRate + change;
  6227. setSpeed();
  6228. });
  6229. return button;
  6230. };
  6231.  
  6232. controlDiv.appendChild(createButton(-STEP_SIZE));
  6233. controlDiv.appendChild(speedDisplay);
  6234. controlDiv.appendChild(createButton(STEP_SIZE));
  6235.  
  6236. if (oldControlDiv) oldControlDiv.replaceWith(controlDiv);
  6237. else menuRenderer.children[0].after(controlDiv);
  6238. }
  6239. }
  6240.  
  6241. // playback speed notification
  6242. function showSpeedNotification(speed) {
  6243. if (!isVideoPage && !isShortPage) return;
  6244. const fullscreenContainer = document.querySelector('#movie_player');
  6245. let notification = document.getElementById('CentAnni-playback-speed-popup');
  6246. if (!notification) {
  6247. notification = document.createElement('div');
  6248. notification.id = 'CentAnni-playback-speed-popup';
  6249. if (!isFullscreen) document.body.appendChild(notification);
  6250. else fullscreenContainer.appendChild(notification);
  6251. }
  6252.  
  6253. notification.textContent = `${speed}x`;
  6254. notification.classList.add('active');
  6255.  
  6256. if (hideNotificationTimeout) clearTimeout(hideNotificationTimeout);
  6257. hideNotificationTimeout = setTimeout(() => {
  6258. notification.classList.remove('active');
  6259. }, 1000);
  6260.  
  6261. if (isFullscreen) fullscreenContainer.appendChild(notification);
  6262. else document.body.appendChild(notification);
  6263. }
  6264.  
  6265. // function to display the remaining time based on playback speed minus SponsorBlock segments
  6266. function remainingTime() {
  6267. const fullscreenContainer = document.querySelector('#movie_player > div.ytp-chrome-bottom');
  6268. const normalContainer = document.querySelector('#columns #primary #below');
  6269. const STREAM_SELECTOR = '.video-stream.html5-main-video';
  6270.  
  6271. // function to format seconds into time string
  6272. function formatTime(seconds) {
  6273. if (!isFinite(seconds) || seconds < 0) seconds = 0;
  6274. const h = Math.floor(seconds / 3600);
  6275. const m = Math.floor((seconds % 3600) / 60);
  6276. const s = Math.floor(seconds % 60);
  6277. return h > 0
  6278. ? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  6279. : `${m}:${s.toString().padStart(2, '0')}`;
  6280. }
  6281.  
  6282. const element = document.createElement('div');
  6283. element.classList.add('CentAnni-remaining-time-container');
  6284.  
  6285. const textNode = document.createTextNode('');
  6286. element.appendChild(textNode);
  6287.  
  6288. const initialContainer = normalContainer;
  6289. if (initialContainer) { initialContainer.prepend(element); }
  6290.  
  6291. // hide for live stream
  6292. if (isLiveVideo) element.classList.add('live');
  6293. else element.classList.remove('live');
  6294.  
  6295. const updateContainer = () => {
  6296. const currentContainer = document.querySelector('.CentAnni-remaining-time-container');
  6297. const targetContainer = document.getElementById('movie_player')?.classList.contains('ytp-fullscreen')
  6298. ? fullscreenContainer
  6299. : normalContainer;
  6300.  
  6301. if (currentContainer && currentContainer.parentNode !== targetContainer) {
  6302. if (targetContainer === fullscreenContainer) {
  6303. targetContainer.appendChild(currentContainer);
  6304. } else {
  6305. targetContainer.prepend(currentContainer);
  6306. if (getComputedStyle(normalContainer).position === 'static') {
  6307. normalContainer.style.position = 'relative';
  6308. }
  6309. }
  6310. }
  6311. };
  6312.  
  6313. updateContainer();
  6314. function handleFullscreenChange() { updateContainer(); }
  6315. document.removeEventListener('fullscreenchange', handleFullscreenChange);
  6316. document.addEventListener('fullscreenchange', handleFullscreenChange);
  6317.  
  6318. // time update event listener
  6319. const video = document.querySelector(STREAM_SELECTOR);
  6320. if (video) {
  6321. if (!video.paused && !video.ended && video.readyState > 2) remainingTimeMinusSponsorBlockSegments();
  6322. else video.addEventListener('playing', remainingTimeMinusSponsorBlockSegments, { once: true });
  6323. }
  6324.  
  6325. // calculates and displays remaining time while accounting for SponsorBlock segments
  6326. function remainingTimeMinusSponsorBlockSegments() {
  6327. let baseEffective = NaN;
  6328. let cachedSegments = null;
  6329. let lastVideoTime = -1;
  6330. let lastDuration = -1;
  6331.  
  6332. // retrieves and validates video duration
  6333. function ensureBaseEffectiveIsValid() {
  6334. if (!isNaN(baseEffective)) return;
  6335. if (!video.duration || isNaN(video.duration) || video.duration <= 0) return;
  6336.  
  6337. const sponsorBlockTimeElement = document.getElementById('sponsorBlockDurationAfterSkips');
  6338. if (sponsorBlockTimeElement && sponsorBlockTimeElement.textContent.trim()) {
  6339. const rawText = sponsorBlockTimeElement.textContent.trim().replace(/[()]/g, '');
  6340. baseEffective = parseTime(rawText);
  6341. } else baseEffective = video.duration;
  6342. }
  6343.  
  6344. // retrieves SponsorBlock segments from preview bar
  6345. function getSegments(rawDuration) {
  6346. if (rawDuration === lastDuration && cachedSegments) return cachedSegments;
  6347.  
  6348. const segments = [];
  6349. const previewbar = document.getElementById('previewbar');
  6350. if (previewbar) {
  6351. const liElements = previewbar.querySelectorAll('li.previewbar');
  6352. liElements.forEach(li => {
  6353. const category = li.getAttribute('sponsorblock-category');
  6354. const style = li.getAttribute('style');
  6355. const leftMatch = style.match(/left:\s*([\d.]+)%/);
  6356. const rightMatch = style.match(/right:\s*([\d.]+)%/);
  6357. if (leftMatch && rightMatch) {
  6358. const leftFraction = parseFloat(leftMatch[1]) / 100;
  6359. const rightFraction = parseFloat(rightMatch[1]) / 100;
  6360. const startTime = Math.round(rawDuration * leftFraction * 1000) / 1000;
  6361. const endTime = Math.round(rawDuration * (1 - rightFraction) * 1000) / 1000;
  6362. const segDuration = Math.round((endTime - startTime) * 1000) / 1000;
  6363. if (segDuration > 0)
  6364. segments.push({ category, start: startTime, duration: segDuration, end: endTime });
  6365. }
  6366. });
  6367. }
  6368.  
  6369. lastDuration = rawDuration;
  6370. cachedSegments = segments;
  6371. return segments;
  6372. }
  6373.  
  6374. // merges SponsorBlock segments that overlap
  6375. function mergeSegments(segments) {
  6376. if (!segments.length) return segments;
  6377. segments.sort((a, b) => a.start - b.start);
  6378. const merged = [segments[0]];
  6379. for (let i = 1; i < segments.length; i++) {
  6380. const last = merged[merged.length - 1];
  6381. const current = segments[i];
  6382. if (current.start <= last.end + 0.001) {
  6383. last.end = Math.max(last.end, current.end);
  6384. last.duration = Math.round((last.end - last.start) * 1000) / 1000;
  6385. } else merged.push(current);
  6386. }
  6387. return merged;
  6388. }
  6389.  
  6390. function computeAddedTime(segments, currentTime) {
  6391. let sum = 0;
  6392. segments.forEach(seg => {
  6393. if (currentTime >= seg.start - 0.001) {
  6394. sum += seg.duration;
  6395. }
  6396. });
  6397. return Math.round(sum * 1000) / 1000;
  6398. }
  6399.  
  6400. // debounce the update to prevent excessive updates
  6401. let animationFrameId = null;
  6402. video.ontimeupdate = () => {
  6403. if (animationFrameId) cancelAnimationFrame(animationFrameId);
  6404.  
  6405. animationFrameId = requestAnimationFrame(() => {
  6406. ensureBaseEffectiveIsValid();
  6407. if (isNaN(baseEffective)) return;
  6408.  
  6409. const rawDuration = video.duration;
  6410. const currentTime = video.currentTime;
  6411.  
  6412. if (Math.abs(currentTime - lastVideoTime) < 0.2) {
  6413. animationFrameId = null;
  6414. return;
  6415. }
  6416.  
  6417. lastVideoTime = currentTime;
  6418.  
  6419. const playbackRate = video.playbackRate;
  6420. const rawSegments = getSegments(rawDuration);
  6421. const segments = mergeSegments(rawSegments);
  6422. const addedTime = computeAddedTime(segments, currentTime);
  6423. const effectiveTotal = baseEffective + addedTime;
  6424. const remaining = (effectiveTotal - currentTime) / playbackRate;
  6425. const watchedPercent = rawDuration ? Math.round((currentTime / rawDuration) * 100) + '%' : '0%';
  6426. const totalFormatted = formatTime(baseEffective);
  6427. const elapsedFormatted = formatTime(currentTime);
  6428. const remainingFormatted = formatTime(remaining);
  6429.  
  6430. textNode.data = `total: ${totalFormatted} | elapsed: ${elapsedFormatted} watched: ${watchedPercent} remaining: ${remainingFormatted} (${playbackRate}x)`;
  6431.  
  6432. animationFrameId = null;
  6433. });
  6434. };
  6435. }
  6436. }
  6437.  
  6438. // helper function to convert a time string into seconds
  6439. function parseTime(timeString) {
  6440. const parts = timeString.split(':').map(Number);
  6441. if (parts.length === 2) return parts[0] * 60 + parts[1];
  6442. else if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
  6443. return 0;
  6444. }
  6445.  
  6446. // function to keep the progress bar visible with chapters container
  6447. function keepProgressBarVisible() {
  6448. const watchFlexy = document.querySelector('ytd-watch-flexy');
  6449. const player = watchFlexy?.querySelector('.html5-video-player');
  6450. const video = player?.querySelector('video[src]');
  6451. const chaptersContainer = player && player.querySelector('.ytp-chapters-container');
  6452. const progressBarContainer = player && player.querySelector('.ytp-progress-bar-container');
  6453.  
  6454. if (!player || !video || !progressBarContainer ) { console.error("YouTubeAlchemy: ProgressBar: A Required querySelector Not Found."); return; }
  6455.  
  6456. document.documentElement.classList.add('progressBar');
  6457.  
  6458. const bar = document.createElement('div');
  6459. bar.id = 'progress-bar-bar';
  6460.  
  6461. const progress = document.createElement('div');
  6462. progress.id = 'progress-bar-progress';
  6463.  
  6464. const buffer = document.createElement('div');
  6465. buffer.id = 'progress-bar-buffer';
  6466.  
  6467. const startDiv = document.createElement('div');
  6468. startDiv.id = 'progress-bar-start';
  6469.  
  6470. const endDiv = document.createElement('div');
  6471. endDiv.id = 'progress-bar-end';
  6472.  
  6473. player.appendChild(bar);
  6474. bar.appendChild(buffer);
  6475. bar.appendChild(progress);
  6476. player.appendChild(startDiv);
  6477. player.appendChild(endDiv);
  6478.  
  6479. progress.style.transform = 'scaleX(0)';
  6480.  
  6481. // live stream check
  6482. if (isLiveVideo) {
  6483. bar.classList.remove('active');
  6484. startDiv.classList.remove('active');
  6485. endDiv.classList.remove('active');
  6486. } else {
  6487. bar.classList.add('active');
  6488. startDiv.classList.add('active');
  6489. endDiv.classList.add('active');
  6490. }
  6491.  
  6492. let animationFrameId;
  6493. function animateProgress() {
  6494. const fraction = video.currentTime / video.duration;
  6495. progress.style.transform = `scaleX(${fraction})`;
  6496. animationFrameId = requestAnimationFrame(animateProgress);
  6497. }
  6498. animationFrameId = requestAnimationFrame(animateProgress);
  6499.  
  6500. function renderBuffer() {
  6501. for (let i = video.buffered.length - 1; i >= 0; i--) {
  6502. if (video.currentTime < video.buffered.start(i)) continue;
  6503. buffer.style.transform = `scaleX(${video.buffered.end(i) / video.duration})`;
  6504. break;
  6505. }
  6506. }
  6507.  
  6508. video.addEventListener('progress', renderBuffer);
  6509. video.addEventListener('seeking', renderBuffer);
  6510.  
  6511. // chapters container
  6512. let previousChaptersLength = 0;
  6513. let cachedMaskImage = null;
  6514.  
  6515. function updateLayout() {
  6516. const initialWidth = progressBarContainer.getBoundingClientRect().width;
  6517.  
  6518. let attempts = 0;
  6519. const maxAttempts = 6;
  6520.  
  6521. const waitForSizeChange = new Promise((resolve) => {
  6522. const intervalId = setInterval(() => {
  6523. const currentWidth = progressBarContainer.getBoundingClientRect().width;
  6524.  
  6525. if (currentWidth !== initialWidth) { clearInterval(intervalId); resolve(); }
  6526. else if (++attempts >= maxAttempts) { clearInterval(intervalId); resolve(); }
  6527. }, 250);
  6528. });
  6529.  
  6530. waitForSizeChange.then(() => {
  6531. const playerRect = player.getBoundingClientRect();
  6532. const progressBarRect = progressBarContainer.getBoundingClientRect();
  6533. const progressBarWidth = progressBarRect.width;
  6534.  
  6535. bar.style.position = 'absolute';
  6536. bar.style.left = (progressBarRect.left - playerRect.left) + 'px';
  6537. bar.style.width = progressBarWidth + 'px';
  6538.  
  6539. if (chaptersContainer) {
  6540. const chapters = chaptersContainer.querySelectorAll('.ytp-chapter-hover-container');
  6541.  
  6542. // Only regenerate SVG if chapters changed
  6543. if (chapters.length !== previousChaptersLength || !cachedMaskImage) {
  6544. previousChaptersLength = chapters.length;
  6545.  
  6546. if (chapters.length) {
  6547. const svgWidth = 100;
  6548. const svgHeight = 10;
  6549. let rects = '';
  6550.  
  6551. chapters.forEach((chapter) => {
  6552. const rect = chapter.getBoundingClientRect();
  6553. const startPx = rect.left - progressBarRect.left;
  6554. const chapterWidth = rect.width;
  6555.  
  6556. const startPerc = (startPx / progressBarWidth) * svgWidth;
  6557. const widthPerc = (chapterWidth / progressBarWidth) * svgWidth;
  6558.  
  6559. rects += `<rect x="${startPerc}" y="0" width="${widthPerc}" height="${svgHeight}" fill="white"/>`;
  6560. });
  6561.  
  6562. const svg = `<svg width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">${rects}</svg>`;
  6563. const encoded = encodeURIComponent(svg).replace(/%20/g, ' ');
  6564. cachedMaskImage = `url("data:image/svg+xml;utf8,${encoded}")`;
  6565. } else {
  6566. cachedMaskImage = null;
  6567. }
  6568. }
  6569.  
  6570. // Apply mask based on current state
  6571. if (cachedMaskImage) {
  6572. bar.style.maskImage = cachedMaskImage;
  6573. bar.style.webkitMaskImage = cachedMaskImage;
  6574. bar.style.maskRepeat = 'no-repeat';
  6575. bar.style.webkitMaskRepeat = 'no-repeat';
  6576. bar.style.maskSize = '100% 100%';
  6577. bar.style.webkitMaskSize = '100% 100%';
  6578. } else {
  6579. bar.style.maskImage = '';
  6580. bar.style.webkitMaskImage = '';
  6581. bar.style.maskRepeat = '';
  6582. bar.style.webkitMaskRepeat = '';
  6583. bar.style.maskSize = '';
  6584. bar.style.webkitMaskSize = '';
  6585. }
  6586. }
  6587. });
  6588. }
  6589.  
  6590. // handle layout changes
  6591. function handleTheaterMode() { updateLayout(); }
  6592. function handleResize() { updateLayout(); }
  6593. document.addEventListener('yt-set-theater-mode-enabled', handleTheaterMode);
  6594. window.addEventListener('resize', handleResize);
  6595.  
  6596. // handle cleanup
  6597. function cleanupProgressBar() {
  6598. document.removeEventListener('yt-set-theater-mode-enabled', handleTheaterMode);
  6599. video.removeEventListener('progress', renderBuffer);
  6600. video.removeEventListener('seeking', renderBuffer);
  6601. window.removeEventListener('resize', handleResize);
  6602. window.cancelAnimationFrame(animationFrameId);
  6603. }
  6604. document.addEventListener('yt-navigate-start', cleanupProgressBar);
  6605.  
  6606. // initialization
  6607. renderBuffer();
  6608. updateLayout();
  6609. }
  6610.  
  6611. // close live chat initially
  6612. function closeLiveChat() {
  6613. const chatFrame = document.querySelector('ytd-live-chat-frame');
  6614. if (!chatFrame) return;
  6615.  
  6616. const retryInterval = 250;
  6617. const maxRetries = 12;
  6618. let iframeAttempts = 0;
  6619. let buttonAttempts = 0;
  6620.  
  6621. function tryCloseChat() {
  6622. const iframe = document.querySelector('#chatframe');
  6623. if (!iframe?.contentWindow?.document) {
  6624. if (iframeAttempts < maxRetries) {
  6625. iframeAttempts++;
  6626. setTimeout(tryCloseChat, retryInterval);
  6627. } else {
  6628. document.documentElement.classList.remove('CentAnni-close-live-chat');
  6629. initialRun = false;
  6630. }
  6631. return;
  6632. }
  6633.  
  6634. const button = iframe.contentWindow.document.querySelector('#close-button button');
  6635. if (!button) {
  6636. if (buttonAttempts < maxRetries) {
  6637. buttonAttempts++;
  6638. setTimeout(tryCloseChat, retryInterval);
  6639. } else {
  6640. document.documentElement.classList.remove('CentAnni-close-live-chat');
  6641. initialRun = false;
  6642. }
  6643. return;
  6644. }
  6645.  
  6646. button.click();
  6647.  
  6648. const chatElement = document.querySelector('ytd-live-chat-frame#chat');
  6649. const observer = new MutationObserver((mutations) => {
  6650. if (chatElement.hasAttribute('collapsed')) {
  6651. removeChatCSS();
  6652. clearTimeout(fallbackTimer);
  6653. observer.disconnect();
  6654. }
  6655. });
  6656.  
  6657. observer.observe(chatElement, {
  6658. attributes: true,
  6659. attributeFilter: ['collapsed']
  6660. });
  6661.  
  6662. const fallbackTimer = setTimeout(() => {
  6663. if (initialRun) {
  6664. removeChatCSS();
  6665. observer.disconnect();
  6666. }
  6667. }, 3000);
  6668.  
  6669. function removeChatCSS() {
  6670. initialRun = false;
  6671. setTimeout(() => { document.documentElement.classList.remove('CentAnni-close-live-chat'); }, 250);
  6672. }
  6673. }
  6674.  
  6675. tryCloseChat();
  6676. }
  6677.  
  6678. // set video quality
  6679. function setVideoQuality(desiredQuality, defaultQualityPremium) {
  6680. let qualitySet = null;
  6681. let qualityUHD = null;
  6682. let found = false;
  6683.  
  6684. // define quality levels
  6685. const qualityLevels = [
  6686. 'highres',
  6687. 'hd2160',
  6688. 'hd1440',
  6689. 'hd1080',
  6690. 'hd720',
  6691. 'large',
  6692. 'medium',
  6693. 'small',
  6694. 'tiny'
  6695. ];
  6696.  
  6697. // get the YouTube player
  6698. const player = document.getElementById("movie_player");
  6699. if (!player?.getAvailableQualityLevels) return;
  6700.  
  6701. // get available qualities for the video and check for UHD
  6702. const availableQualities = player.getAvailableQualityLevels();
  6703. if ( availableQualities.includes('hd1440') || availableQualities.includes('hd2160') || availableQualities.includes('highres') ) qualityUHD = true;
  6704.  
  6705. // helper function to reduce repetition and store picked quality
  6706. const setQuality = (quality) => {
  6707. player.setPlaybackQualityRange(quality, quality);
  6708. qualitySet = quality;
  6709. };
  6710.  
  6711. // find closest available quality if exact match isn't available
  6712. if (availableQualities.includes(desiredQuality)) {
  6713. setQuality(desiredQuality);
  6714. } else if (desiredQuality === "highest") {
  6715. setQuality(availableQualities[0]);
  6716. } else if (desiredQuality === "lowest") {
  6717. const lowest = availableQualities[availableQualities.length - 1] === "auto"
  6718. ? availableQualities[availableQualities.length - 2]
  6719. : availableQualities[availableQualities.length - 1];
  6720. setQuality(lowest);
  6721. } else {
  6722. const desiredIndex = qualityLevels.indexOf(desiredQuality);
  6723. if (desiredIndex !== -1) {
  6724. for (let i = desiredIndex; i < qualityLevels.length; i++) {
  6725. if (availableQualities.includes(qualityLevels[i])) {
  6726. setQuality(qualityLevels[i]);
  6727. found = true;
  6728. break;
  6729. }
  6730. }
  6731. if (!found) setQuality('auto');
  6732. } else setQuality('auto');
  6733. }
  6734.  
  6735. // set 1080p premium quality
  6736. if (defaultQualityPremium && qualitySet === 'hd1080' && !qualityUHD) setVideoQualityPremium();
  6737.  
  6738. function setVideoQualityPremium() {
  6739. // helper function to handle cleanup and style to hide the settings menu
  6740. function cleanup() {
  6741. document.body.click();
  6742. document.documentElement.classList.remove('CentAnni-style-hide-yt-settings');
  6743. }
  6744. document.documentElement.classList.add('CentAnni-style-hide-yt-settings');
  6745.  
  6746. // recursive function to ensure the quality menu is visible
  6747. function qualityMenuVisible(retryCount = 0) {
  6748. const qualityMenu = document.querySelector('.ytp-settings-menu');
  6749. const isQualityMenuVisible = qualityMenu && window.getComputedStyle(qualityMenu).display !== 'none';
  6750.  
  6751. if (!isQualityMenuVisible) {
  6752. const settingsButton = document.querySelector('.ytp-settings-button');
  6753. if (settingsButton) {
  6754. settingsButton.click();
  6755. setTimeout(() => {
  6756. const qualityItem = Array.from(document.querySelectorAll('.ytp-menuitem'))
  6757. .find(item => item.textContent.includes('Quality'));
  6758. if (qualityItem) {
  6759. qualityItem.click();
  6760. if (retryCount < 5) setTimeout(() => qualityMenuVisible(retryCount + 1), 300);
  6761. else cleanup();
  6762. } else cleanup();
  6763. }, 150);
  6764. } else cleanup();
  6765. return;
  6766. }
  6767.  
  6768. // pick 1080p premium with enhanced bitrate
  6769. const enhancedBitrateOption = Array.from(qualityMenu.querySelectorAll('.ytp-menuitem'))
  6770. .find(item => {
  6771. const labelEl = item.querySelector('.ytp-menuitem-label');
  6772. return labelEl && labelEl.innerText.includes('1080p Premium') && labelEl.innerText.includes('Enhanced bitrate');
  6773. });
  6774. if (enhancedBitrateOption) enhancedBitrateOption.click();
  6775. cleanup();
  6776. }
  6777.  
  6778. qualityMenuVisible();
  6779. }
  6780. }
  6781.  
  6782. // sidebar and links in header
  6783. function buttonsLeftHeader() {
  6784. function opentabSidebar() {
  6785. const guideButton = document.querySelector('#guide-button button');
  6786. if (guideButton) guideButton.click();
  6787. }
  6788.  
  6789. // create sidebar button
  6790. function createButton(text, onClick) {
  6791. const btn = document.createElement('button');
  6792. btn.textContent = text;
  6793. btn.classList.add('buttons-left');
  6794. btn.addEventListener('click', (e) => {
  6795. e.preventDefault();
  6796. onClick();
  6797. });
  6798. return btn;
  6799. }
  6800.  
  6801. // create links
  6802. function createLink(text, url) {
  6803. const link = document.createElement('a');
  6804. link.textContent = text;
  6805. link.classList.add('buttons-left');
  6806. link.href = url;
  6807. return link;
  6808. }
  6809.  
  6810. const masthead = document.querySelector('ytd-masthead'); if (!masthead) return;
  6811. const container = masthead.querySelector('#container #start'); if (!container) return;
  6812.  
  6813. const isHideSidebarChecked = USER_CONFIG.mButtonDisplay;
  6814.  
  6815. if (container.querySelector('.buttons-left')) return;
  6816.  
  6817. // adding the buttons
  6818. const buttonsConfig = [
  6819. { type: 'button', text: USER_CONFIG.mButtonText, onClick: opentabSidebar },
  6820. { type: 'link', text: USER_CONFIG.buttonLeft1Text, url: USER_CONFIG.buttonLeft1Url },
  6821. { type: 'link', text: USER_CONFIG.buttonLeft2Text, url: USER_CONFIG.buttonLeft2Url },
  6822. { type: 'link', text: USER_CONFIG.buttonLeft3Text, url: USER_CONFIG.buttonLeft3Url },
  6823. { type: 'link', text: USER_CONFIG.buttonLeft4Text, url: USER_CONFIG.buttonLeft4Url },
  6824. { type: 'link', text: USER_CONFIG.buttonLeft5Text, url: USER_CONFIG.buttonLeft5Url },
  6825. { type: 'link', text: USER_CONFIG.buttonLeft6Text, url: USER_CONFIG.buttonLeft6Url },
  6826. { type: 'link', text: USER_CONFIG.buttonLeft7Text, url: USER_CONFIG.buttonLeft7Url },
  6827. { type: 'link', text: USER_CONFIG.buttonLeft8Text, url: USER_CONFIG.buttonLeft8Url },
  6828. { type: 'link', text: USER_CONFIG.buttonLeft9Text, url: USER_CONFIG.buttonLeft9Url },
  6829. { type: 'link', text: USER_CONFIG.buttonLeft10Text, url: USER_CONFIG.buttonLeft10Url },
  6830. ];
  6831.  
  6832. buttonsConfig.forEach(config => {
  6833. if (config.text && config.text.trim() !== '') {
  6834. let element;
  6835. if (config.type === 'button') {
  6836. if (isHideSidebarChecked) {
  6837. element = createButton(config.text, config.onClick);
  6838. if (config.text === DEFAULT_CONFIG.mButtonText) {
  6839. element.style.display = 'inline-block';
  6840. element.style.fontSize = '25px';
  6841. element.style.margin = '0';
  6842. element.style.padding = '0 0 5px 0';
  6843. element.style.transform = 'scaleX(1.25)';
  6844. }
  6845. }
  6846. } else if (config.type === 'link') element = createLink(config.text, config.url);
  6847. if (element) container.appendChild(element);
  6848. }
  6849. });
  6850. }
  6851.  
  6852. // color code videos on home
  6853. function homeColorCodeVideos() {
  6854. let processedAllVideos = false;
  6855. // define age categories
  6856. const categories = {
  6857. live: ['watching'],
  6858. // streamed: ['Streamed'],
  6859. upcoming: ['waiting', 'scheduled for'],
  6860. newly: ['1 day ago', 'hours ago', 'hour ago', 'minutes ago', 'minute ago', 'seconds ago', 'second ago'],
  6861. recent: ['1 week ago', '7 days ago', '6 days ago', '5 days ago', '4 days ago', '3 days ago', '2 days ago'],
  6862. lately: ['1 month ago', 'weeks ago', '14 days ago', '13 days ago', '12 days ago', '11 days ago', '10 days ago', '9 days ago', '8 days ago'],
  6863. old: ['years ago', '1 year ago', '12 months ago', '11 months ago', '10 months ago', '9 months ago', '8 months ago', '7 months ago']
  6864. };
  6865.  
  6866. const homePage = document.querySelector('ytd-browse[page-subtype="home"]:not([hidden])');
  6867. if (!homePage) return;
  6868.  
  6869. function processVideos() {
  6870. const unprocessedVideos = Array.from(homePage.querySelectorAll('ytd-rich-item-renderer:not([data-centanni-video-processed])'));
  6871. if (unprocessedVideos.length === 0) { processedAllVideos = true; return; }
  6872.  
  6873. unprocessedVideos.forEach(videoContainer => {
  6874. videoContainer.setAttribute('data-centanni-video-processed', 'true');
  6875. const metaBlock = videoContainer.querySelector('#metadata-line');
  6876. if (!metaBlock) return;
  6877.  
  6878. const textContent = metaBlock.textContent.trim().toLowerCase();
  6879. for (const [className, ages] of Object.entries(categories)) {
  6880. if (ages.some(age => textContent.includes(age.toLowerCase()))) {
  6881. videoContainer.classList.add(`CentAnni-style-${className}-video`);
  6882. break;
  6883. }
  6884. }
  6885.  
  6886. const spanElements = videoContainer.querySelectorAll('span.ytd-video-meta-block');
  6887. spanElements.forEach(el => {
  6888. const text = el.textContent;
  6889.  
  6890. if (/Scheduled for/i.test(text) && !videoContainer.classList.contains('CentAnni-style-upcoming-video'))
  6891. videoContainer.classList.add('CentAnni-style-upcoming-video');
  6892.  
  6893. if (/Streamed/i.test(text) && !el.querySelector('.CentAnni-style-streamed-text')) {
  6894. el.childNodes.forEach(node => {
  6895. if (node.nodeType === Node.TEXT_NODE && /Streamed/i.test(node.nodeValue)) {
  6896. const span = document.createElement('span');
  6897. span.className = 'CentAnni-style-streamed-text';
  6898. span.textContent = node.nodeValue.match(/Streamed/i)[0];
  6899. const rest = document.createTextNode(node.nodeValue.replace(/Streamed/i, ''));
  6900. el.replaceChild(rest, node);
  6901. el.insertBefore(span, rest);
  6902. }
  6903. });
  6904. }
  6905. });
  6906. });
  6907. }
  6908.  
  6909. function runProcessVideos(times, initialDelay, interval, callback) {
  6910. let count = 0;
  6911.  
  6912. function runProcess() {
  6913. processVideos();
  6914. count++;
  6915. if (count < times && !processedAllVideos) setTimeout(runProcess, interval);
  6916. else if (callback) callback();
  6917. }
  6918.  
  6919. setTimeout(runProcess, initialDelay);
  6920. }
  6921.  
  6922. // handle navigation
  6923. const serviceRequestSentHandler = () => { runProcessVideos(3, 1000, 1000, null); };
  6924. const navigateFinishHandler = () => { setTimeout(cleanupAndReprocessVideos, 300); };
  6925. const pageTypeChangedHandler = function() {
  6926. document.removeEventListener('yt-service-request-sent', serviceRequestSentHandler);
  6927. document.removeEventListener('yt-service-request-completed', cleanupAndReprocessVideos);
  6928. document.removeEventListener('yt-navigate-finish', navigateFinishHandler);
  6929. document.removeEventListener('yt-page-type-changed', pageTypeChangedHandler);
  6930. };
  6931.  
  6932. runProcessVideos(6, 250, 500, function() {
  6933. document.addEventListener('yt-service-request-sent', serviceRequestSentHandler);
  6934. document.addEventListener('yt-service-request-completed', cleanupAndReprocessVideos);
  6935. document.addEventListener('yt-navigate-finish', navigateFinishHandler);
  6936. setTimeout(checkProcessVideos, 1250);
  6937. });
  6938.  
  6939. document.addEventListener('yt-page-type-changed', pageTypeChangedHandler);
  6940.  
  6941. // ensure correct categories
  6942. function checkProcessVideos() {
  6943. const processedVideos = Array.from(homePage.querySelectorAll('ytd-rich-item-renderer[data-centanni-video-processed]')).slice(0, 8);
  6944. if (processedVideos.length === 0) return;
  6945.  
  6946. let allCorrect = true;
  6947. for (const video of processedVideos) {
  6948. const metaBlock = video.querySelector('#metadata-line');
  6949. if (!metaBlock) continue;
  6950.  
  6951. const textContent = metaBlock.textContent.trim().toLowerCase();
  6952. let expectedCategory = null;
  6953.  
  6954. for (const [className, ages] of Object.entries(categories)) {
  6955. if (ages.some(age => textContent.includes(age.toLowerCase()))) {
  6956. expectedCategory = className;
  6957. break;
  6958. }
  6959. }
  6960.  
  6961. const spanElements = video.querySelectorAll('span.ytd-video-meta-block');
  6962. for (const el of spanElements) {
  6963. if (/Scheduled for/i.test(el.textContent)) {
  6964. expectedCategory = 'upcoming';
  6965. break;
  6966. }
  6967. }
  6968.  
  6969. if (expectedCategory && !video.classList.contains(`CentAnni-style-${expectedCategory}-video`)) {
  6970. allCorrect = false;
  6971. break;
  6972. }
  6973. }
  6974.  
  6975. if (!allCorrect) cleanupAndReprocessVideos();
  6976. }
  6977.  
  6978. // handle feed filter 'All' button
  6979. let allButtonObserver = null;
  6980. function handleFeedFilterAll() {
  6981. if (allButtonObserver) return;
  6982.  
  6983. const allButton = document.querySelector('yt-chip-cloud-chip-renderer[chip-shape-data*="All"]');
  6984. if (!allButton) return;
  6985.  
  6986. allButtonObserver = new MutationObserver((mutations) => {
  6987. for (const mutation of mutations) {
  6988. if (mutation.type === 'attributes' && mutation.attributeName === 'class' && allButton.classList.contains('iron-selected')) {
  6989. checkProcessVideos();
  6990. handleFeedFilterAllCleanup();
  6991. break;
  6992. }
  6993. }
  6994. });
  6995.  
  6996. allButtonObserver.observe(allButton, {
  6997. attributes: true,
  6998. attributeFilter: ['class', 'selected']
  6999. });
  7000. }
  7001.  
  7002. function handleFeedFilterAllCleanup() {
  7003. if (allButtonObserver) {
  7004. allButtonObserver.disconnect();
  7005. allButtonObserver = null;
  7006. }
  7007. }
  7008.  
  7009. // handle cleanup
  7010. function cleanupAndReprocessVideos() {
  7011. const videosToReprocess = homePage.querySelectorAll('ytd-rich-item-renderer[data-centanni-video-processed]');
  7012.  
  7013. videosToReprocess.forEach(video => {
  7014. video.classList.remove(
  7015. 'CentAnni-style-live-video',
  7016. 'CentAnni-style-upcoming-video',
  7017. 'CentAnni-style-newly-video',
  7018. 'CentAnni-style-recent-video',
  7019. 'CentAnni-style-lately-video',
  7020. 'CentAnni-style-old-video',
  7021. 'CentAnni-style-streamed-video'
  7022. );
  7023.  
  7024. video.querySelectorAll('.CentAnni-style-streamed-text').forEach(span => span.remove());
  7025. video.removeAttribute('data-centanni-video-processed');
  7026. });
  7027.  
  7028. processVideos();
  7029. handleFeedFilterAll();
  7030. addSettingsButton();
  7031. }
  7032. }
  7033.  
  7034. // mark last seen video on subscription page
  7035. async function markLastSeenVideo() {
  7036. const subscriptionPage = document.querySelector('ytd-browse[page-subtype="subscriptions"]:not([hidden])');
  7037. if (!subscriptionPage) return;
  7038.  
  7039. const videoContainers = subscriptionPage.querySelectorAll('ytd-rich-item-renderer');
  7040. if (!videoContainers.length) return;
  7041.  
  7042. // helper function to check if a video is live or upcoming
  7043. const isSpecialVideo = (container) => {
  7044. if (container.querySelector('.badge-style-type-live-now-alternate') !== null) return true;
  7045. if (container.querySelector('ytd-thumbnail-overlay-time-status-renderer[overlay-style="UPCOMING"]') !== null) return true;
  7046.  
  7047. // backup checks
  7048. const metadataItems = container.querySelectorAll('.inline-metadata-item');
  7049. for (const item of metadataItems) {
  7050. if (item.textContent.includes('Scheduled for') || item.textContent.includes('watching')) {
  7051. return true;
  7052. }
  7053. }
  7054. return false;
  7055. };
  7056.  
  7057. // helper function to extract video ID
  7058. const extractVideoID = (container) => {
  7059. const mainThumbnail = container.querySelector('ytd-thumbnail a#thumbnail[href*="/watch?v="]');
  7060. if (!mainThumbnail) return null;
  7061.  
  7062. const href = mainThumbnail.getAttribute('href');
  7063. if (!href) return null;
  7064.  
  7065. return new URLSearchParams(href.split('?')[1]).get("v");
  7066. };
  7067.  
  7068. // helper function to find first valid video
  7069. const findFirstValidVideo = (callback) => {
  7070. for (const container of videoContainers) {
  7071. if (isSpecialVideo(container)) continue;
  7072. const videoID = extractVideoID(container);
  7073. if (!videoID) continue;
  7074.  
  7075. const result = callback(container, videoID);
  7076. if (result) return result;
  7077. }
  7078. return null;
  7079. };
  7080.  
  7081. const lastSeenID = localStorage.getItem("CentAnni_lastSeenVideoID");
  7082. let targetElement = null;
  7083.  
  7084. // find last seen video
  7085. const newLastSeenID = findFirstValidVideo((container, videoID) => videoID);
  7086. if (newLastSeenID) localStorage.setItem("CentAnni_lastSeenVideoID", newLastSeenID);
  7087.  
  7088. // find previous seen video
  7089. if (lastSeenID) {
  7090. targetElement = findFirstValidVideo((container, videoID) => {
  7091. if (videoID === lastSeenID) {
  7092. container.classList.add("CentAnni-style-last-seen");
  7093. return container;
  7094. }
  7095. return null;
  7096. });
  7097. }
  7098.  
  7099. // scroll to the last seen video
  7100. if (USER_CONFIG.lastSeenVideoScroll && targetElement) {
  7101. requestAnimationFrame(() => {
  7102. targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
  7103. });
  7104. }
  7105. }
  7106.  
  7107. // add trashcan icon to playlists to easily remove videos
  7108. function playlistRemovalButtons() {
  7109. const sortButton = document.querySelector('#filter, #filter-menu');
  7110. if (!sortButton) return;
  7111.  
  7112. const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"]:not([hidden])');
  7113. if (!playlistPage) return;
  7114.  
  7115. const playlistContainer = playlistPage.querySelector('#primary > ytd-section-list-renderer > #contents > ytd-item-section-renderer #contents');
  7116. if (!playlistContainer) return;
  7117.  
  7118. // watch for playlist changes
  7119. const playlistObserver = new MutationObserver(() => {
  7120. const videoElements = playlistPage.querySelectorAll('ytd-playlist-video-renderer:not([data-centanni-playlist-video-processed])');
  7121. if (videoElements.length > 0) videoElements.forEach(attachRemoveButtonToVideo);
  7122. });
  7123.  
  7124. playlistObserver.observe(playlistContainer, {
  7125. childList: true,
  7126. subtree: true
  7127. });
  7128.  
  7129. // run and cleanup
  7130. attachRemoveButtonsToAllVideos();
  7131. const navigateHandler = () => {
  7132. playlistObserver.disconnect();
  7133. cleanupRemoveButtons();
  7134. document.removeEventListener('yt-navigate-start', navigateHandler);
  7135. };
  7136. document.addEventListener('yt-navigate-start', navigateHandler);
  7137.  
  7138. function attachRemoveButtonToVideo(videoEl) {
  7139. if (videoEl.hasAttribute('data-centanni-playlist-video-processed')) return;
  7140. videoEl.setAttribute('data-centanni-playlist-video-processed', 'true');
  7141.  
  7142. const metaContainer = videoEl.querySelector('#meta');
  7143. if (!metaContainer) return;
  7144.  
  7145. const removeBtn = document.createElement('button');
  7146. removeBtn.className = 'CentAnni-style-playlist-remove-btn';
  7147. removeBtn.title = 'Remove from Playlist';
  7148. removeBtn.innerText = '🗑️';
  7149. removeBtn.addEventListener('click', (event) => {
  7150. event.stopPropagation();
  7151. event.preventDefault();
  7152. if (removeBtn.classList.contains('removing')) return;
  7153. removeBtn.classList.add('removing');
  7154. simulateVideoRemoval(videoEl)
  7155. .finally(() => { removeBtn.classList.remove('removing'); });
  7156. });
  7157. metaContainer.appendChild(removeBtn);
  7158. }
  7159.  
  7160. function attachRemoveButtonsToAllVideos() {
  7161. const videoElements = playlistPage.querySelectorAll('ytd-playlist-video-renderer:not([data-centanni-playlist-video-processed])');
  7162. videoElements.forEach(attachRemoveButtonToVideo);
  7163. }
  7164.  
  7165. function cleanupRemoveButtons() {
  7166. document.querySelectorAll('.CentAnni-style-playlist-remove-btn').forEach(btn => btn.remove());
  7167. document.querySelectorAll('ytd-playlist-video-renderer[data-centanni-playlist-video-processed]').forEach(videoEl => {
  7168. videoEl.removeAttribute('data-centanni-playlist-video-processed');
  7169. });
  7170. }
  7171.  
  7172. function simulateVideoRemoval(videoEl) {
  7173. return new Promise((resolve) => {
  7174. const menuBtn = videoEl.querySelector('#button');
  7175. if (!menuBtn) {
  7176. resolve();
  7177. return;
  7178. }
  7179.  
  7180. const popupContainer = document.querySelector('ytd-popup-container');
  7181. if (popupContainer) popupContainer.classList.add('CentAnni-style-playlist-hide-menu');
  7182.  
  7183. menuBtn.click();
  7184.  
  7185. waitForElement('#items > ytd-menu-service-item-renderer', 300)
  7186. .then(() => { return new Promise(r => setTimeout(r, 75)); })
  7187. .then(() => {
  7188. const menuItems = Array.from(document.querySelectorAll('#items > ytd-menu-service-item-renderer'));
  7189. let removeOption = null;
  7190.  
  7191. for (const item of menuItems) {
  7192. const formattedString = item.querySelector('yt-formatted-string');
  7193. if (formattedString && formattedString.textContent.includes('Remove from')) {
  7194. removeOption = item;
  7195. break;
  7196. }
  7197. }
  7198.  
  7199. if (!removeOption) {
  7200. document.body.click();
  7201. return Promise.resolve();
  7202. }
  7203.  
  7204. removeOption.click();
  7205. return new Promise(r => setTimeout(r, 400));
  7206. })
  7207. .then(() => {
  7208. if (popupContainer) popupContainer.classList.remove('CentAnni-style-playlist-hide-menu');
  7209. resolve();
  7210. })
  7211. .catch(() => {
  7212. try { document.body.click(); } catch (e) {}
  7213. if (popupContainer) popupContainer.classList.remove('CentAnni-style-playlist-hide-menu');
  7214. resolve();
  7215. });
  7216. });
  7217. }
  7218.  
  7219. function waitForElement(selector, timeout = 700) {
  7220. return new Promise((resolve) => {
  7221. const element = playlistPage.querySelector(selector);
  7222. if (element) return resolve(element);
  7223.  
  7224. const observer = new MutationObserver(() => {
  7225. const element = playlistPage.querySelector(selector);
  7226. if (element) {
  7227. clearTimeout(timer);
  7228. observer.disconnect();
  7229. resolve(element);
  7230. }
  7231. });
  7232.  
  7233. const timer = setTimeout(() => {
  7234. observer.disconnect();
  7235. resolve();
  7236. }, timeout);
  7237.  
  7238. observer.observe(playlistPage, { childList: true, subtree: true });
  7239. });
  7240. }
  7241. }
  7242.  
  7243. // open playlist videos without being in a playlist
  7244. function handlePlaylistLinks() {
  7245. let processedAllListVideos = false;
  7246.  
  7247. function chromeClickHandler(event) {
  7248. if(!event.ctrlKey && !event.metaKey && !event.shiftKey) {
  7249. event.stopImmediatePropagation();
  7250. event.preventDefault();
  7251. const cleanUrl = event.currentTarget.getAttribute('CentAnni-chrome-pl-url');
  7252. window.open(cleanUrl, '_self');
  7253. }
  7254. }
  7255.  
  7256. const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"]:not([hidden])');
  7257. if (!playlistPage) return;
  7258.  
  7259. function processVideos() {
  7260. const unprocessedVideos = Array.from( playlistPage.querySelectorAll('#contents > ytd-playlist-video-list-renderer > #contents > ytd-playlist-video-renderer:not([data-centanni-list-video-processed])'));
  7261. if (unprocessedVideos.length === 0) { processedAllListVideos = true; return; }
  7262.  
  7263. unprocessedVideos.forEach(videoItem => {
  7264. videoItem.setAttribute('data-centanni-list-video-processed', 'true');
  7265. const thumbnailLink = videoItem.querySelector('a#thumbnail');
  7266. const titleLink = videoItem.querySelector('a#video-title');
  7267.  
  7268. [thumbnailLink, titleLink].forEach(link => {
  7269. if (link && link.href && (link.href.includes('list=WL') || link.href.includes('list=PL') || link.href.includes('list=LL'))) {
  7270. try {
  7271. const url = new URL(link.href);
  7272. const videoID = url.searchParams.get('v');
  7273. if (videoID) {
  7274. const cleanUrl = `https://www.youtube.com/watch?v=${videoID}`;
  7275. link.href = cleanUrl;
  7276. if (!USER_CONFIG.preventBackgroundExecution) link.setAttribute('onclick',`if(!event.ctrlKey&&!event.metaKey&&!event.shiftKey){event.stopPropagation();event.preventDefault();window.location='${cleanUrl}';return!1}return!0`);
  7277. else {
  7278. link.setAttribute('CentAnni-chrome-pl-url', cleanUrl);
  7279. link.addEventListener('click', chromeClickHandler, true);
  7280. }
  7281. }
  7282. } catch (e) {
  7283. console.error('Error processing link:', e);
  7284. }
  7285. }
  7286. });
  7287. });
  7288. }
  7289.  
  7290. function runProcessVideos(times, initialDelay, interval, callback) {
  7291. let count = 0;
  7292.  
  7293. function runProcess() {
  7294. processVideos();
  7295. count++;
  7296. if (count < times && !processedAllListVideos) setTimeout(runProcess, interval);
  7297. else if (callback) callback();
  7298. }
  7299.  
  7300. setTimeout(runProcess, initialDelay);
  7301. }
  7302.  
  7303. // handle navigation
  7304. const serviceRequestSentHandler = () => { runProcessVideos(3, 1000, 1000, null); };
  7305. const pageTypeChangedHandler = function() {
  7306. document.removeEventListener('yt-service-request-sent', serviceRequestSentHandler);
  7307. document.removeEventListener('yt-navigate-start', pageTypeChangedHandler);
  7308. document.querySelectorAll('[data-centanni-list-video-processed]').forEach(el => {
  7309. el.removeAttribute('data-centanni-list-video-processed');
  7310. });
  7311.  
  7312. if (USER_CONFIG.preventBackgroundExecution) {
  7313. playlistPage.querySelectorAll('[CentAnni-chrome-pl-url]').forEach(link => {
  7314. link.removeEventListener('click', chromeClickHandler, true);
  7315. });
  7316. }
  7317. };
  7318.  
  7319. runProcessVideos(6, 250, 500, function() {
  7320. document.addEventListener('yt-service-request-sent', serviceRequestSentHandler);
  7321. });
  7322.  
  7323. document.addEventListener('yt-navigate-start', pageTypeChangedHandler);
  7324. }
  7325.  
  7326. // RSS feed button on channel page
  7327. function addRSSFeedButton() {
  7328. if (document.getElementById('CentAnni-channel-btn')) return;
  7329.  
  7330. const rssLinkElement = document.querySelector('link[rel="alternate"][type="application/rss+xml"]');
  7331. if (!rssLinkElement) return;
  7332. const rssFeedUrl = rssLinkElement.getAttribute('href');
  7333.  
  7334. const actionsContainer = document.querySelector('.yt-flexible-actions-view-model-wiz');
  7335. if (!actionsContainer) return;
  7336.  
  7337. const buttonContainer = document.createElement('div');
  7338. buttonContainer.className = 'yt-flexible-actions-view-model-wiz__action';
  7339.  
  7340. const rssLink = document.createElement('a');
  7341. rssLink.id = 'CentAnni-channel-btn';
  7342. rssLink.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
  7343. rssLink.title = 'Click to open RSS feed or right-click to Copy Link';
  7344. rssLink.rel = 'noopener noreferrer';
  7345. rssLink.href = rssFeedUrl;
  7346. rssLink.target = '_blank';
  7347.  
  7348. const rssIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  7349. rssIcon.setAttribute('viewBox', '0 0 24 24');
  7350. rssIcon.setAttribute('width', '24');
  7351. rssIcon.setAttribute('height', '24');
  7352. rssIcon.style.fill = '#FF9800';
  7353.  
  7354. const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
  7355. circle.setAttribute('cx', '6.18');
  7356. circle.setAttribute('cy', '17.82');
  7357. circle.setAttribute('r', '2.18');
  7358. rssIcon.appendChild(circle);
  7359.  
  7360. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  7361. path.setAttribute('d', 'M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z');
  7362. rssIcon.appendChild(path);
  7363.  
  7364. rssLink.appendChild(rssIcon);
  7365. rssLink.appendChild(document.createTextNode('RSS Feed'));
  7366. buttonContainer.appendChild(rssLink);
  7367. actionsContainer.appendChild(buttonContainer);
  7368. }
  7369.  
  7370. // add playlist buttons to channel page
  7371. function addPlaylistButtons() {
  7372. if (document.querySelector('a[data-centanni-playlist-channel-btn="all"]')) return;
  7373.  
  7374. const channelID = document.querySelector('[itemprop="identifier"]')?.content;
  7375. if (!channelID) return;
  7376.  
  7377. const allVideosURL = `https://www.youtube.com/playlist?list=UU${channelID.slice(2)}`;
  7378. const fullVideoURL = `https://www.youtube.com/playlist?list=UULF${channelID.slice(2)}`;
  7379. const shortsURL = `https://www.youtube.com/playlist?list=UUSH${channelID.slice(2)}`;
  7380.  
  7381. const actionsContainer = document.querySelector('.yt-flexible-actions-view-model-wiz');
  7382. if (!actionsContainer) return;
  7383.  
  7384. const createPlaylistButton = (url, text, buttonID) => {
  7385. const buttonContainer = document.createElement('div');
  7386. buttonContainer.className = 'yt-flexible-actions-view-model-wiz__action';
  7387.  
  7388. const buttonLink = document.createElement('a');
  7389. buttonLink.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
  7390. buttonLink.id = 'CentAnni-channel-btn';
  7391. buttonLink.setAttribute('data-centanni-playlist-channel-btn', buttonID);
  7392. buttonLink.title = `Click to Open ${text} Playlist`;
  7393. buttonLink.rel = 'noopener noreferrer';
  7394. buttonLink.href = url;
  7395. buttonLink.target = '_self';
  7396.  
  7397. const playlistIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  7398. playlistIcon.setAttribute('viewBox', '0 0 24 24');
  7399. playlistIcon.setAttribute('width', '24');
  7400. playlistIcon.setAttribute('height', '24');
  7401. playlistIcon.style.fill = 'var(--yt-spec-brand-icon-inactive)';
  7402. const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  7403. iconPath.setAttribute('clip-rule', 'evenodd');
  7404. iconPath.setAttribute('fill-rule', 'evenodd');
  7405. iconPath.setAttribute('d', 'M3.75 5c-.414 0-.75.336-.75.75s.336.75.75.75h16.5c.414 0 .75-.336.75-.75S20.664 5 20.25 5H3.75Zm0 4c-.414 0-.75.336-.75.75s.336.75.75.75h16.5c.414 0 .75-.336.75-.75S20.664 9 20.25 9H3.75Zm0 4c-.414 0-.75.336-.75.75s.336.75.75.75h8.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-8.5Zm8.5 4c.414 0 .75.336.75.75s-.336.75-.75.75h-8.5c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h8.5Zm3.498-3.572c-.333-.191-.748.05-.748.434v6.276c0 .384.415.625.748.434L22 17l-6.252-3.572Z');
  7406.  
  7407. playlistIcon.appendChild(iconPath);
  7408. buttonLink.appendChild(playlistIcon);
  7409. buttonLink.appendChild(document.createTextNode(text));
  7410.  
  7411. buttonContainer.appendChild(buttonLink);
  7412. return buttonContainer;
  7413. };
  7414.  
  7415. if (!USER_CONFIG.hideShorts) {
  7416. const allVideosButton = createPlaylistButton(allVideosURL, 'All Uploads', 'all');
  7417. actionsContainer.appendChild(allVideosButton);
  7418. }
  7419.  
  7420. const fullVideosButton = createPlaylistButton(fullVideoURL, 'Videos', 'full');
  7421. actionsContainer.appendChild(fullVideosButton);
  7422.  
  7423. if (!USER_CONFIG.hideShorts) {
  7424. const shortsButton = createPlaylistButton(shortsURL, 'Shorts', 'shorts');
  7425. actionsContainer.appendChild(shortsButton);
  7426. }
  7427. }
  7428.  
  7429. // function to change playlist direction
  7430. function playlistDirection() {
  7431. const watchFlexy = document.querySelector('ytd-watch-flexy');
  7432. if (!hasPlaylistPanel && !playlistPanel && !watchFlexy) return;
  7433.  
  7434. let playlistItems;
  7435. let currentVideoIndex;
  7436. let videoAboveInfo;
  7437. let videoBelowInfo;
  7438. let nextButton;
  7439. let cleanupDone = false;
  7440. let playNextBtnStatus = false;
  7441. let ValidDataVideoAbove = false;
  7442. let ValidDataVideoBelow = false;
  7443. let savedDirection = localStorage.getItem('CentAnni_playlistDirection') || 'down';
  7444. let reverseDirection = savedDirection === 'up';
  7445.  
  7446. function getPlaylistInfo() {
  7447. function getVideoThumbnailURL(videoURL) {
  7448. let videoID = null;
  7449. if (videoURL) {
  7450. const watchMatch = videoURL.match(/[?&]v=([^&#]*)/);
  7451. if (watchMatch && watchMatch[1]) videoID = watchMatch[1];
  7452. else {
  7453. const shortMatch = videoURL.match(/youtu\.be\/([^?&#]*)/);
  7454. if (shortMatch && shortMatch[1]) videoID = shortMatch[1];
  7455. }
  7456. }
  7457. return videoID ? `https://i.ytimg.com/vi/${videoID}/mqdefault.jpg` : null;
  7458. }
  7459.  
  7460. function processVideoInfo(videoElement) {
  7461. if (!videoElement) return { info: null, isValid: false };
  7462.  
  7463. const link = videoElement.querySelector('a#wc-endpoint');
  7464. const title = videoElement.querySelector('#video-title');
  7465. const href = link ? link.href : null;
  7466. const thumbnailUrl = getVideoThumbnailURL(href);
  7467.  
  7468. if (!(link && title)) return { info: null, isValid: false };
  7469.  
  7470. const info = {
  7471. link: link,
  7472. href: href,
  7473. thumbnail: thumbnailUrl,
  7474. title: title ? title.textContent.trim() : null
  7475. };
  7476. const isValid = (link && href && thumbnailUrl && thumbnailUrl.trim() !== '' && title && title.textContent && title.textContent.trim() !== '');
  7477.  
  7478. return { info, isValid };
  7479. }
  7480.  
  7481. if (!playlistSelectedVideo) return null;
  7482.  
  7483. playlistItems = Array.from(watchFlexy.querySelectorAll('ytd-playlist-panel-video-renderer'));
  7484. if (!playlistItems || playlistItems.length <= 1) return null;
  7485.  
  7486. currentVideoIndex = playlistItems.indexOf(playlistSelectedVideo);
  7487. if (currentVideoIndex === -1) return null;
  7488.  
  7489. nextButton = watchFlexy.querySelector('.ytp-next-button');
  7490.  
  7491. if (currentVideoIndex > 0) {
  7492. const videoAbove = playlistItems[currentVideoIndex - 1];
  7493. const result = processVideoInfo(videoAbove);
  7494. videoAboveInfo = result.info;
  7495. ValidDataVideoAbove = result.isValid;
  7496. }
  7497.  
  7498. if (currentVideoIndex < playlistItems.length - 1) {
  7499. const videoBelow = playlistItems[currentVideoIndex + 1];
  7500. const result = processVideoInfo(videoBelow);
  7501. videoBelowInfo = result.info;
  7502. ValidDataVideoBelow = result.isValid;
  7503. }
  7504.  
  7505. const success = ValidDataVideoAbove && ValidDataVideoBelow;
  7506. return success;
  7507. }
  7508.  
  7509. function createButtons(reverseDirection) {
  7510. const playlistActionMenu = watchFlexy.querySelector('#playlist-action-menu .top-level-buttons');
  7511. if (!playlistActionMenu) return null;
  7512.  
  7513. const BTN_CLASS = 'yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-only-default CentAnni-playlist-direction-btn';
  7514.  
  7515. const directionContainer = document.createElement('div');
  7516. directionContainer.id = 'CentAnni-playlist-direction-container';
  7517.  
  7518. const fragment = document.createDocumentFragment();
  7519.  
  7520. const directionText = document.createElement('span');
  7521. directionText.textContent = 'Playlist Direction:';
  7522. fragment.appendChild(directionText);
  7523.  
  7524. function createDirectionButton(isUpButton) {
  7525. const button = document.createElement('button');
  7526. button.className = BTN_CLASS;
  7527. if (reverseDirection === isUpButton) button.classList.add('active');
  7528. button.title = isUpButton
  7529. ? 'Play Videos Ascending'
  7530. : 'Play Videos Descending (YouTube Default)';
  7531.  
  7532. const iconDiv = document.createElement('div');
  7533. iconDiv.className = 'yt-spec-button-shape-next__icon';
  7534. iconDiv.setAttribute('aria-hidden', 'true');
  7535. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  7536. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  7537. svg.setAttribute('height', '24');
  7538. svg.setAttribute('viewBox', '0 0 24 24');
  7539. svg.setAttribute('width', '24');
  7540. svg.setAttribute('focusable', 'false');
  7541. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  7542. path.setAttribute('d', isUpButton
  7543. ? 'M7.41 15.41 12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z'
  7544. : 'M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z');
  7545.  
  7546. svg.appendChild(path);
  7547. iconDiv.appendChild(svg);
  7548. button.appendChild(iconDiv);
  7549.  
  7550. return button;
  7551. }
  7552.  
  7553. const upButton = createDirectionButton(true);
  7554. const downButton = createDirectionButton(false);
  7555. fragment.appendChild(upButton);
  7556. fragment.appendChild(downButton);
  7557. directionContainer.appendChild(fragment);
  7558.  
  7559. const existingContainer = watchFlexy.querySelector('#CentAnni-playlist-direction-container');
  7560. if (existingContainer) existingContainer.replaceWith(directionContainer);
  7561. else playlistActionMenu.appendChild(directionContainer);
  7562.  
  7563. return { upButton, downButton };
  7564. }
  7565.  
  7566. function upNextBtn() {
  7567. if (reverseDirection === playNextBtnStatus || !nextButton) return;
  7568. if (reverseDirection) {
  7569. if (videoAboveInfo) {
  7570. if (videoAboveInfo.href && videoAboveInfo.thumbnail && videoAboveInfo.title) {
  7571. nextButton.href = videoAboveInfo.href;
  7572. nextButton.dataset.preview = videoAboveInfo.thumbnail;
  7573. nextButton.dataset.tooltipText = videoAboveInfo.title;
  7574. }
  7575.  
  7576. nextButton.removeEventListener('click', nextButtonClickHandler, true);
  7577. nextButton.addEventListener('click', nextButtonClickHandler, true);
  7578. document.addEventListener('keydown', handleKeyboardShortcut, true);
  7579.  
  7580. playNextBtnStatus = true;
  7581. }
  7582. } else {
  7583. document.removeEventListener('keydown', handleKeyboardShortcut, true);
  7584. nextButton.removeEventListener('click', nextButtonClickHandler, true);
  7585.  
  7586. if (videoBelowInfo) {
  7587. if (videoBelowInfo.href && videoBelowInfo.thumbnail && videoBelowInfo.title) {
  7588. nextButton.href = videoBelowInfo.href;
  7589. nextButton.dataset.preview = videoBelowInfo.thumbnail;
  7590. nextButton.dataset.tooltipText = videoBelowInfo.title;
  7591. }
  7592. }
  7593.  
  7594. playNextBtnStatus = false;
  7595. }
  7596. }
  7597.  
  7598. function nextButtonClickHandler(e) {
  7599. if (reverseDirection && videoAboveInfo) {
  7600. e.preventDefault();
  7601. e.stopPropagation();
  7602.  
  7603. if (videoAboveInfo.link) videoAboveInfo.link.click();
  7604. return false;
  7605. }
  7606. }
  7607.  
  7608. function handleKeyboardShortcut(e) {
  7609. if (e.key === 'N' && e.shiftKey) {
  7610. if (reverseDirection && videoAboveInfo) {
  7611. e.preventDefault();
  7612. e.stopPropagation();
  7613.  
  7614. if (videoAboveInfo.link) videoAboveInfo.link.click();
  7615. return false;
  7616. }
  7617. }
  7618. }
  7619.  
  7620. function upButtonHandler() {
  7621. localStorage.setItem('CentAnni_playlistDirection', 'up');
  7622. reverseDirection = true;
  7623.  
  7624. upButton.classList.add('active');
  7625. downButton.classList.remove('active');
  7626.  
  7627. upNextBtn();
  7628. }
  7629.  
  7630. // Handle down button click
  7631. function downButtonHandler() {
  7632. localStorage.setItem('CentAnni_playlistDirection', 'down');
  7633. reverseDirection = false;
  7634.  
  7635. upButton.classList.remove('active');
  7636. downButton.classList.add('active');
  7637.  
  7638. upNextBtn();
  7639. }
  7640.  
  7641. // cleanup
  7642. const handleCleanup = () => {
  7643. if (cleanupDone) return;
  7644. document.removeEventListener('yt-navigate', handleCleanup);
  7645. document.removeEventListener('yt-autonav-pause-player-ended', handleCleanup);
  7646. document.removeEventListener('keydown', handleKeyboardShortcut, true);
  7647. nextButton.removeEventListener('click', nextButtonClickHandler, true);
  7648. upButton.removeEventListener('click', upButtonHandler);
  7649. downButton.removeEventListener('click', downButtonHandler);
  7650. cleanupDone = true;
  7651. };
  7652.  
  7653. const handleVideoEnd = () => {
  7654. document.removeEventListener('yt-autonav-pause-player-ended', handleVideoEnd);
  7655. if (!reverseDirection || !videoAboveInfo) return;
  7656. else if (videoAboveInfo.link) videoAboveInfo.link.click();
  7657. };
  7658.  
  7659. document.addEventListener('yt-navigate', handleCleanup);
  7660. document.addEventListener('yt-autonav-pause-player-ended', handleCleanup);
  7661. document.addEventListener('yt-autonav-pause-player-ended', handleVideoEnd);
  7662.  
  7663. // initiation
  7664. const { upButton, downButton } = createButtons(reverseDirection);
  7665. if (upButton && downButton) {
  7666. upButton.addEventListener('click', upButtonHandler);
  7667. downButton.addEventListener('click', downButtonHandler);
  7668. }
  7669.  
  7670. const observer = new MutationObserver((mutations) => {
  7671. const selectedItem = watchFlexy.querySelector('ytd-playlist-panel-video-renderer[selected]');
  7672. const nextButton = watchFlexy.querySelector('.ytp-next-button');
  7673. if (selectedItem && nextButton) {
  7674. if (getPlaylistInfo()) {
  7675. observer.disconnect();
  7676. setTimeout(() => { upNextBtn(); },1000);
  7677. return;
  7678. }
  7679. }
  7680. });
  7681.  
  7682. const config = {
  7683. childList: true,
  7684. subtree: true,
  7685. };
  7686.  
  7687. observer.observe(watchFlexy, config);
  7688. }
  7689.  
  7690. // function to prevent autoplay
  7691. function pauseYouTubeVideo() {
  7692. document.removeEventListener('yt-player-updated', pauseYouTubeVideo);
  7693.  
  7694. if ( !/^https:\/\/.*\.youtube\.com\/watch\?v=/.test(window.location.href) || document.querySelector('.ytp-time-display')?.classList.contains('ytp-live') ) return;
  7695.  
  7696. const elements = {
  7697. player: document.getElementById('movie_player'),
  7698. video: document.querySelector('video'),
  7699. watchFlexy: document.querySelector('ytd-watch-flexy'),
  7700. thumbnailOverlayImage: document.querySelector('.ytp-cued-thumbnail-overlay-image'),
  7701. thumbnailOverlay: document.querySelector('.ytp-cued-thumbnail-overlay')
  7702. };
  7703.  
  7704. if (!elements.player || !elements.video || !elements.watchFlexy) return;
  7705.  
  7706. const urlParams = new URLSearchParams(window.location.search);
  7707. const vinteo = urlParams.get('v');
  7708.  
  7709. if (elements.watchFlexy.hasAttribute('default-layout')) {
  7710. if (USER_CONFIG.autoTheaterMode) toggleTheaterMode();
  7711. setTimeout(pauseVideo, 200);
  7712. } else if (elements.watchFlexy.hasAttribute('theater')) {
  7713. pauseVideo();
  7714. }
  7715.  
  7716. function pauseVideo() {
  7717. const { player, video, thumbnailOverlayImage, thumbnailOverlay } = elements;
  7718. if (player && video) {
  7719. video.pause();
  7720. player.classList.remove('playing-mode');
  7721. player.classList.add('unstarted-mode', 'paused-mode');
  7722. const playingHandler = () => {
  7723. if (thumbnailOverlayImage) {
  7724. thumbnailOverlayImage.removeAttribute('style');
  7725. thumbnailOverlayImage.style.display = 'none';
  7726. thumbnailOverlay.removeAttribute('style');
  7727. thumbnailOverlay.style.display = 'none';
  7728. }
  7729. video.removeEventListener('playing', playingHandler);
  7730. };
  7731. video.addEventListener('playing', playingHandler);
  7732. }
  7733.  
  7734. showThumbnail(vinteo);
  7735. }
  7736.  
  7737. function showThumbnail(vinteo) {
  7738. const { thumbnailOverlayImage, thumbnailOverlay } = elements;
  7739. if (thumbnailOverlayImage && thumbnailOverlay && lastVideoID !== vinteo) {
  7740. thumbnailOverlay.style.cssText = 'display: block';
  7741. void thumbnailOverlayImage.offsetHeight;
  7742. thumbnailOverlayImage.style.cssText = `
  7743. display: block;
  7744. z-index: 10;
  7745. background-image: url("https://i.ytimg.com/vi/${vinteo}/maxresdefault.jpg");
  7746. `;
  7747. }
  7748. }
  7749. }
  7750.  
  7751. // function to sort comments newest first
  7752. function sortCommentsNewFirst() {
  7753. let observationTimeout = null;
  7754. let dropdownTimeout = null;
  7755.  
  7756. // handler for YouTube service requests in tab view mode
  7757. const serviceRequestHandler = function(event) {
  7758. const commentsTab = document.querySelector('.CentAnni-tabView-tab[data-tab="tab-2"]');
  7759. if (commentsTab && commentsTab.classList.contains('active')) {
  7760. document.removeEventListener('yt-service-request-sent', serviceRequestHandler);
  7761. observeCommentSection();
  7762. }
  7763. };
  7764.  
  7765. // comment sorting detection
  7766. function setupCommentSortingDetection() {
  7767. if (USER_CONFIG.videoTabView) document.addEventListener('yt-service-request-sent', serviceRequestHandler);
  7768. else observeCommentSection();
  7769. }
  7770.  
  7771. // observe comment section and detect when it's loaded
  7772. function observeCommentSection() {
  7773. const commentsSection = document.querySelector('ytd-comments#comments');
  7774. if (!commentsSection) {
  7775. if (observationTimeout) clearTimeout(observationTimeout);
  7776. observationTimeout = setTimeout(observeCommentSection, 250);
  7777. return;
  7778. }
  7779.  
  7780. const observer = new MutationObserver((mutations) => {
  7781. for (const mutation of mutations) {
  7782. if (mutation.addedNodes.length > 0) {
  7783. const commentsHeader = commentsSection.querySelector('ytd-comments-header-renderer');
  7784. const sortButton = commentsSection.querySelector('yt-sort-filter-sub-menu-renderer');
  7785.  
  7786. if (commentsHeader && sortButton) {
  7787. observer.disconnect();
  7788. changeSortingToNewestFirst();
  7789. return;
  7790. }
  7791. }
  7792. }
  7793. });
  7794.  
  7795. observer.observe(commentsSection, {
  7796. childList: true,
  7797. subtree: true,
  7798. attributes: false
  7799. });
  7800. }
  7801.  
  7802. // set newest first
  7803. function changeSortingToNewestFirst() {
  7804. const sortButton = document.querySelector('yt-sort-filter-sub-menu-renderer yt-dropdown-menu tp-yt-paper-button');
  7805. if (sortButton) {
  7806. sortButton.click();
  7807.  
  7808. if (dropdownTimeout) clearTimeout(dropdownTimeout);
  7809. dropdownTimeout = setTimeout(() => {
  7810. const options = document.querySelectorAll('tp-yt-paper-listbox a.yt-simple-endpoint');
  7811. let newestFirstOption = null;
  7812.  
  7813. for (const option of options) {
  7814. if (option.textContent.toLowerCase().includes('newest')) {
  7815. newestFirstOption = option;
  7816. break;
  7817. }
  7818. }
  7819.  
  7820. if (!newestFirstOption && options.length > 1) newestFirstOption = options[1];
  7821. if (newestFirstOption) newestFirstOption.click();
  7822. }, 200);
  7823. }
  7824. }
  7825.  
  7826. document.addEventListener('yt-navigate-start', () => {
  7827. document.removeEventListener('yt-service-request-sent', serviceRequestHandler);
  7828.  
  7829. if (observationTimeout) {
  7830. clearTimeout(observationTimeout);
  7831. observationTimeout = null;
  7832. }
  7833.  
  7834. if (dropdownTimeout) {
  7835. clearTimeout(dropdownTimeout);
  7836. dropdownTimeout = null;
  7837. }
  7838. });
  7839.  
  7840. setupCommentSortingDetection();
  7841. }
  7842.  
  7843. // theater mode check
  7844. function theaterModeCheck() {
  7845. const watchFlexy = document.querySelector('ytd-watch-flexy');
  7846. isTheaterMode = watchFlexy?.hasAttribute('theater') || false;
  7847. }
  7848.  
  7849. // fullscreen check
  7850. function fullscreenCheck() {
  7851. isFullscreen = document.getElementById('movie_player')?.classList.contains('ytp-fullscreen') || false;
  7852. }
  7853.  
  7854. function musicVideoCheck() {
  7855. isMusicVideo = !!( document.querySelector('meta[itemprop="genre"][content="Music"]') && document.querySelector('ytd-badge-supported-renderer .badge-style-type-verified-artist') );
  7856. }
  7857.  
  7858. // live stream check
  7859. function liveVideoCheck() {
  7860. isLiveVideo = document.querySelector('.ytp-time-display')?.classList.contains('ytp-live') || false;
  7861. }
  7862.  
  7863. // chapter panel check
  7864. function chapterPanelCheck() {
  7865. chapterPanel = document.querySelector( 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"]' ) || document.querySelector( 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"]' );
  7866. hasChapterPanel = !!chapterPanel;
  7867. }
  7868.  
  7869. // function to automatically open the chapter panel
  7870. function openChapters() {
  7871. if (hasChapterPanel)
  7872. chapterPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  7873. }
  7874.  
  7875. // playlist panel check
  7876. function playlistPanelCheck() {
  7877. const watchFlexy = document.querySelector('ytd-watch-flexy');
  7878. const playlistVideoItem = watchFlexy ? watchFlexy.querySelector('ytd-playlist-panel-video-renderer[id="playlist-items"]') : null;
  7879. playlistPanel = watchFlexy ? watchFlexy.querySelector('ytd-playlist-panel-renderer[id="playlist"]') : null;
  7880. hasPlaylistPanel = !!(playlistVideoItem && playlistPanel);
  7881. playlistSelectedVideo = document.querySelector('ytd-playlist-panel-video-renderer[selected]');
  7882. }
  7883.  
  7884. // transcript panel check
  7885. function transcriptPanelCheck() {
  7886. transcriptPanel = document.querySelector( 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"]' );
  7887. hasTranscriptPanel = !!transcriptPanel;
  7888. }
  7889.  
  7890. // function to automatically open the transcript panel
  7891. function openTranscript() {
  7892. if (hasTranscriptPanel) {
  7893. transcriptPanel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
  7894.  
  7895. if (USER_CONFIG.defaultTranscriptLanguage !== 'auto' && !USER_CONFIG.YouTubeTranscriptExporter) waitForTranscriptWithoutYTE(() => { setTimeout(() => { setTranscriptLanguage(); }, 250); });
  7896. if (USER_CONFIG.transcriptTimestamps && !USER_CONFIG.YouTubeTranscriptExporter) waitForTranscriptWithoutYTE(enableTimestamps);
  7897. }
  7898. }
  7899.  
  7900. // transcript enable timestamps
  7901. function enableTimestamps() {
  7902. const transcriptPanelTimestamps = document.querySelector('ytd-transcript-search-panel-renderer[hide-timestamps]');
  7903. if (transcriptPanelTimestamps) transcriptPanelTimestamps.removeAttribute('hide-timestamps');
  7904. }
  7905.  
  7906. // transcript enable timestamps tabview
  7907. function waitForTranscriptWithoutYTE(callback) {
  7908. let transcriptLoadedWithoutYTE = false;
  7909.  
  7910. const transcriptObserver = new MutationObserver(() => {
  7911. const transcriptItems = transcriptPanel.querySelectorAll("ytd-transcript-segment-renderer");
  7912. if (transcriptItems.length > 0) {
  7913. transcriptLoadedWithoutYTE = true;
  7914. clearTimeout(transcriptFallbackTimer);
  7915. transcriptObserver.disconnect();
  7916. callback();
  7917. }
  7918. });
  7919.  
  7920. transcriptObserver.observe(transcriptPanel, { childList: true, subtree: true });
  7921.  
  7922. const transcriptFallbackTimer = setTimeout(() => {
  7923. if (!transcriptLoadedWithoutYTE) transcriptObserver.disconnect();
  7924. }, 6000);
  7925. }
  7926.  
  7927. // check and close chat window and close
  7928. function chatWindowCheck() {
  7929. hasChatPanel = !!(document.querySelector('#chat, iframe#chatframe, yt-video-metadata-carousel-view-model'));
  7930.  
  7931. const chatMessageElement = document.querySelector('#chat-container ytd-live-chat-frame #message');
  7932. const chatMessageElementStatus = document.querySelector('ytd-message-renderer.style-scope.ytd-live-chat-frame');
  7933. const isChatMessageElementVisible = chatMessageElementStatus && window.getComputedStyle(chatMessageElementStatus).display !== 'none';
  7934.  
  7935. if (chatMessageElement && isChatMessageElementVisible) {
  7936. const messageText = chatMessageElement.textContent.trim();
  7937. if (messageText === 'Chat is disabled for this live stream.') chatEnabled = false;
  7938. } else chatEnabled = true;
  7939.  
  7940. if (USER_CONFIG.closeChatWindow && initialRun && hasChatPanel && chatEnabled) closeLiveChat();
  7941. else document.documentElement.classList.remove('CentAnni-close-live-chat');
  7942. }
  7943.  
  7944. // redirect channel page to videos
  7945. function channelRedirect() {
  7946. window.location.href = window.location.href.replace(/\/$/, '') + '/videos';
  7947. }
  7948.  
  7949. // redirect shorts to video page
  7950. function redirectShortsToVideoPage() {
  7951. window.location.href = window.location.href.replace('/shorts/', '/watch?v=');
  7952. }
  7953.  
  7954. // reset function
  7955. function handleYTNavigation() {
  7956. if (USER_CONFIG.playbackSpeed) document.addEventListener('yt-player-updated', initialSpeed); // set playback speed
  7957. if (USER_CONFIG.preventAutoplay) document.addEventListener('yt-player-updated', pauseYouTubeVideo); // prevent autoplay
  7958.  
  7959. if (USER_CONFIG.videoTabView) {
  7960. document.querySelector('#related.style-scope.ytd-watch-flexy')?.classList.remove('CentAnni-tabView-content-attiva');
  7961. document.querySelector('ytd-playlist-panel-renderer[id="playlist"].style-scope.ytd-watch-flexy')?.classList.remove('CentAnni-tabView-content-active');
  7962. }
  7963.  
  7964. document.querySelectorAll('.button-wrapper:not(:has(#transcript-settings-button)), #CentAnni-channel-btn, .CentAnni-remaining-time-container, .CentAnni-chapter-title, #progress-bar-bar, #progress-bar-start, #progress-bar-end, #yt-transcript-settings-modal').forEach(el => el.remove());
  7965. }
  7966.  
  7967. // initiate the script
  7968. let initializeDelay;
  7969. let ultimoURL = null;
  7970. let lastVideoURL = null;
  7971. let lastVideoID = null;
  7972. let videoURL = null;
  7973. let videoID = null;
  7974. let isHomePage = false;
  7975. let isVideoPage = false;
  7976. let isLiveVideo = false;
  7977. let isLiveStream = false;
  7978. let isShortPage = false;
  7979. let isSubscriptionsPage = false;
  7980. let isPlaylistPage = false;
  7981. let isPlaylistVideoPage = false;
  7982. let hasPlaylistPanel = false;
  7983. let playlistPanel = null;
  7984. let playlistSelectedVideo = null;
  7985. let isChannelPage = false;
  7986. let isChannelHome = false;
  7987. let isMusicVideo = false;
  7988. let initialRun = null;
  7989. let chatEnabled = null;
  7990. let isTheaterMode = null;
  7991. let hasChapterPanel = null;
  7992. let chapterPanel = null;
  7993. let hasTranscriptPanel = null;
  7994. let transcriptPanel = null;
  7995. let hasChatPanel = null;
  7996. let isFullscreen = null;
  7997. let ignoreRateChange = false;
  7998. let lastUserRate = null;
  7999. let speedNotification = false;
  8000. let hideNotificationTimeout;
  8001. let defaultSpeed = USER_CONFIG.playbackSpeedValue;
  8002.  
  8003. async function initializeAlchemy() {
  8004. if (USER_CONFIG.preventBackgroundExecution) { await chromeUserWait(); }
  8005. buttonsLeftHeader();
  8006.  
  8007. if (isVideoPage || isLiveStream) {
  8008. liveVideoCheck();
  8009. musicVideoCheck();
  8010. fullscreenCheck();
  8011. theaterModeCheck();
  8012. chapterPanelCheck();
  8013. playlistPanelCheck();
  8014. transcriptPanelCheck();
  8015. if (USER_CONFIG.videoTabView) tabView();
  8016. if (USER_CONFIG.hideAdSlots) hideProductsSpan();
  8017. if (USER_CONFIG.playbackSpeed) videoPlaybackSpeed();
  8018. if (USER_CONFIG.commentsNewFirst) sortCommentsNewFirst();
  8019. if (USER_CONFIG.autoTheaterMode && !isTheaterMode) toggleTheaterMode();
  8020. if (USER_CONFIG.closeChatWindow) setTimeout(() => { chatWindowCheck(); }, 500);
  8021. if (USER_CONFIG.playlistDirectionBtns && isPlaylistVideoPage) playlistDirection();
  8022. if (USER_CONFIG.progressBar && !isLiveVideo && !isLiveStream) keepProgressBarVisible();
  8023. if (USER_CONFIG.displayRemainingTime && !isLiveVideo && !isLiveStream) remainingTime();
  8024. if (USER_CONFIG.autoOpenChapters && !USER_CONFIG.videoTabView && hasChapterPanel) openChapters();
  8025. if (USER_CONFIG.autoOpenTranscript && !USER_CONFIG.videoTabView && hasTranscriptPanel) openTranscript();
  8026. if (USER_CONFIG.defaultQuality !== 'auto') {try {document.documentElement.appendChild(Object.assign(document.createElement('script'),{textContent:`(${setVideoQuality.toString()})(${JSON.stringify(USER_CONFIG.defaultQuality)},${JSON.stringify(USER_CONFIG.defaultQualityPremium)});`}));} catch (error) {setVideoQuality(USER_CONFIG.defaultQuality,USER_CONFIG.defaultQualityPremium);}}
  8027.  
  8028. // transcript exporter
  8029. let transcriptLoaded = false;
  8030. if (USER_CONFIG.YouTubeTranscriptExporter) {
  8031. try { await preLoadTranscript(); transcriptLoaded = true; }
  8032. catch (error) { setTimeout(() => { addSettingsButton(); }, 3000); }
  8033. if (transcriptLoaded) addButton();
  8034. } else addSettingsButton();
  8035. } else {
  8036. addSettingsButton();
  8037. if (USER_CONFIG.channelRSSBtn && isChannelPage) addRSSFeedButton();
  8038. if (USER_CONFIG.playlistLinks && isPlaylistPage) handlePlaylistLinks();
  8039. if (USER_CONFIG.channelPlaylistBtn && isChannelPage) addPlaylistButtons();
  8040. if (USER_CONFIG.lastSeenVideo && isSubscriptionsPage) markLastSeenVideo();
  8041. if (USER_CONFIG.colorCodeVideosEnabled && isHomePage) homeColorCodeVideos();
  8042. if (USER_CONFIG.playlistTrashCan && isPlaylistPage) playlistRemovalButtons();
  8043. if (USER_CONFIG.playbackSpeed && isShortPage) createPlaybackSpeedController();
  8044. }
  8045. }
  8046.  
  8047. // YouTube navigation handler
  8048. function handleYouTubeNavigation() {
  8049. // console.log("YouTubeAlchemy: Event Listner Arrived");
  8050. const currentVideoURL = window.location.href;
  8051. if (currentVideoURL !== ultimoURL) {
  8052. ultimoURL = currentVideoURL;
  8053. // console.log("YouTubeAlchemy: Only One Survived");
  8054. if (!cssSettingsApplied) loadCSSsettings();
  8055. initialRun = true;
  8056.  
  8057. const urlObj = new URL(currentVideoURL);
  8058. isHomePage = urlObj.pathname === '/';
  8059. isVideoPage = urlObj.pathname === '/watch';
  8060. isLiveStream = urlObj.pathname.startsWith('/live/');
  8061. isShortPage = urlObj.pathname.startsWith('/shorts/');
  8062. isPlaylistPage = urlObj.pathname === '/playlist';
  8063. isPlaylistVideoPage = currentVideoURL.includes('&list=');
  8064. isSubscriptionsPage = urlObj.pathname === '/feed/subscriptions';
  8065. isChannelPage = /^\/@[a-zA-Z0-9._-]+/.test(urlObj.pathname);
  8066. isChannelHome = /^(\/@[a-zA-Z0-9._-]+|\/channel\/[a-zA-Z0-9_\-=.]+)$/.test(urlObj.pathname);
  8067. if (USER_CONFIG.channelReindirizzare && isChannelHome) { channelRedirect(); return; }
  8068. if (USER_CONFIG.redirectShorts && isShortPage) { redirectShortsToVideoPage(); return; }
  8069.  
  8070. lastVideoURL = videoURL;
  8071. videoURL = window.location.href;
  8072.  
  8073. if (isVideoPage || isLiveStream) {
  8074. lastVideoID = videoID;
  8075. videoID = urlObj.searchParams.get('v');
  8076.  
  8077. document.documentElement.style.setProperty(
  8078. '--video-url',
  8079. `url("https://i.ytimg.com/vi/${videoID}/maxresdefault.jpg")`
  8080. );
  8081. }
  8082.  
  8083. if (lastVideoURL === null) initializeDelay = 500;
  8084. else initializeDelay = 700;
  8085. setTimeout(() => { initializeAlchemy(); }, initializeDelay);
  8086. }
  8087. }
  8088.  
  8089. // pause script until tab becomes visible
  8090. async function chromeUserWait() {
  8091. if (document.visibilityState !== 'visible') {
  8092. //console.log("YouTubeAlchemy: Waiting for this tab to become visible...");
  8093. return new Promise((resolve) => {
  8094. document.addEventListener('visibilitychange', function onVisibilityChange() {
  8095. if (document.visibilityState === 'visible') {
  8096. document.removeEventListener('visibilitychange', onVisibilityChange);
  8097. resolve();
  8098. }
  8099. });
  8100. });
  8101. }
  8102. }
  8103.  
  8104. // event listeners
  8105. document.addEventListener('yt-navigate-start', handleYTNavigation); // reset
  8106. document.addEventListener('yt-navigate-finish', handleYouTubeNavigation); // default
  8107. document.addEventListener('yt-page-data-updated', handleYouTubeNavigation); // backup
  8108. document.addEventListener('yt-page-data-fetched', handleYouTubeNavigation); // redundancy
  8109. if (USER_CONFIG.playbackSpeed) document.addEventListener('fullscreenchange', fullscreenCheck); // fullscreen change
  8110. if (USER_CONFIG.preventAutoplay) document.addEventListener('yt-player-updated', pauseYouTubeVideo); // prevent autoplay
  8111. if (USER_CONFIG.playbackSpeed) document.addEventListener('yt-player-updated', initialSpeed); // set playback speed
  8112. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址