// ==UserScript== // @name YouTube Alchemy // @description Toolkit for YouTube with 200+ options accessible via settings panels. Key features include: tab view, playback speed control, video quality selection, export transcripts, prevent autoplay, hide Shorts, 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. // @author Tim Macy // @license AGPL-3.0-or-later // @version 11.12 // @namespace TimMacy.YouTubeAlchemy // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @match https://*.youtube.com/* // @grant GM.setValue // @grant GM.getValue // @run-at document-start // @noframes // @homepageURL https://github.com/TimMacy/YouTubeAlchemy // @supportURL https://github.com/TimMacy/YouTubeAlchemy/issues // @updateURL https://raw.githubusercontent.com/TimMacy/YouTubeAlchemy/main/YouTubeAlchemy.js // @downloadURL https://raw.githubusercontent.com/TimMacy/YouTubeAlchemy/main/YouTubeAlchemy.js // ==/UserScript== /************************************************************************ * * * Copyright © 2026 Tim Macy * * GNU Affero General Public License v3.0 * * Version: 11.12 - YouTube Alchemy * * * * Visit: https://github.com/TimMacy * * * ************************************************************************/ (async function () { 'use strict'; // CSS const styleSheet = document.createElement('style'); styleSheet.textContent = ` html { font-size: var(--fontSize) !important; font-family: "Roboto", Arial, sans-serif; --CentAnniTabViewHeader: 50px; --mastheadHeight: 56px; --topHeaderMargin: var(--ytd-margin-6x, 24px); --ytd-margin-0x: 16px; --yt-primary: black; --yt-secondary: white; --yt-text-primary: #0f0f0f; --yt-text-secondary: #606060; --yt-base-background: #fff; --yt-base-background-secondary: #f2f2f2; --yt-raised-background: #212121; --yt-spec-background: rgb(0 0 0 / .05); --yt-btn-hover: rgb(0 0 0 / .1); --CentAnniRed: rgb(253 1 48); --bronze-color: #CD7F32; } html[dark] { --yt-primary: white; --yt-secondary: black; --yt-text-primary: #f1f1f1; --yt-text-secondary: #aaa; --yt-base-background: #0f0f0f; --yt-base-background-secondary: #282828; --yt-spec-background: rgb(255 255 255 / .1); --yt-btn-hover: rgb(255 255 255 / .2); } .is-watch-page ytd-watch-flexy { --horizontalMargin: var(--ytd-watch-flexy-horizontal-page-margin, 16px); &[reduced-top-margin] { --topHeaderMargin: var(--ytd-margin-3x, 12px); } } /* main CSS */ #yt-alchemy-settings-modal { --NotebookLM-color: hsl(134, 61%, 40%); --ChatGPT-color: hsl(217, 91%, 59%); --copy-color: hsl(33, 100%, 50%); --lazy-color: hsl(51, 100%, 50%); --download-color: hsl(359, 88%, 57%); --settings-color: hsl(0, 0%, 100%); } .CentAnni-overlay { position: fixed; display: none; z-index: 2053; opacity: 0; left: 0; top: 0; width: 100%; height: 100%; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, .5); backdrop-filter: blur(5px); transition: opacity, display; transition-behavior: allow-discrete; transition-timing-function: linear; transition-duration: .2s; &.active { display: flex; opacity: 1; @starting-style { opacity: 0; } } &.reload { transition: none; } } .CentAnni-modal-content { z-index: 2077; background-color: rgba(17, 17, 17, .8); padding: 20px 0 20px 20px; border: 1px solid rgba(255, 255, 255, .25); border-radius: 8px; width: 500px; max-height: 90dvh; font-family: "Roboto", "Arial", sans-serif; font-size: 9px; line-height: 1.2; color: white; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } #CentAnni-main-settings-form { max-height: calc(90dvh - 40px); overflow-y: auto; padding-right: 20px; } .CentAnni-notification { background: hsl(0, 0%, 7%); padding: 20px 30px; border-radius: 8px; border: 1px solid hsl(0, 0%, 18.82%); max-width: 80%; text-align: center; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 16px; color: white; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .CentAnni-header-wrapper { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; justify-content: center; margin: 0 20px 20px 0; width: calc(100% - 20px); } .CentAnni-header { margin: 0; padding: 0; border: 0; grid-column: 2; text-align: center; text-decoration: none; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 2.5em; line-height: 1.1em; font-weight: 700; text-overflow: ellipsis; white-space: normal; text-shadow: 0 0 20px black; cursor: pointer; background: transparent; display: block; background-image: linear-gradient(45deg, #FFFFFF, #F2F2F2, #E6E6E6, #D9D9D9, #CCCCCC); -webkit-background-clip: text; background-clip: text; color: transparent; transition: color .3s ease-in-out; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; &:hover { color: white; } } .CentAnni-version-label { grid-column: 3; padding: 0; margin: 0 0 0 5px; color: ghostwhite; cursor: default; opacity: .3; transition: opacity .5s; justify-self: start; max-width: 10ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .CentAnni-header:hover + .CentAnni-version-label, html.is-watch-page:not(.CentAnni-progress-bar) .html5-video-player.ytp-fullscreen.ytp-autohide .ytp-chrome-bottom:has(#CentAnni-remaining-time-container, #CentAnni-chapter-title) { opacity: 1; } .label-style-settings { display: block; margin-bottom: 5px; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; cursor: default; } .reset-prompt-text { display: block; float: right; cursor: pointer; margin: 4px 10px 0 0; font-size: inherit; font-weight: 400; line-height: inherit; color: ghostwhite; &:hover { color: red; } &:active { color: magenta; } } .label-NotebookLM { color: var(--NotebookLM-color); } .label-ChatGPT { color: var(--ChatGPT-color); } .label-copy { color: var(--copy-color); } .label-download { color: var(--download-color); } .label-lazy { color: var(--lazy-color); } .label-settings { color: var(--settings-color); } .label-gold { color: gold; } .label-silver { color: silver; } .label-bronze { color: var(--bronze-color); } .buttonIconNotebookLM-input-field, .input-field-targetNotebookLMUrl { --field-color: var(--NotebookLM-color); } .buttonIconChatGPT-input-field, .input-field-targetChatGPTUrl { --field-color: var(--ChatGPT-color); } .buttonIconCopy-input-field { --field-color: var(--copy-color); } .buttonIconlazyLoad-input-field { --field-color: var(--lazy-color); } .buttonIconDownload-input-field { --field-color: var(--download-color); } .buttonIconSettings-input-field { --field-color: var(--settings-color); } .input-field-watchLaterGold { --field-color: gold; } .input-field-watchLaterSilver { --field-color: silver; } .input-field-watchLaterBronze { --field-color: var(--bronze-color); } .input-field-targetNotebookLMUrl:focus, .buttonIconNotebookLM-input-field:focus { border-color: var(--NotebookLM-color); } .input-field-targetChatGPTUrl:focus, .buttonIconChatGPT-input-field:focus { border-color: var(--ChatGPT-color); } .buttonIconCopy-input-field:focus { border-color: var(--copy-color); } .buttonIconlazyLoad-input-field:focus { border-color: var(--lazy-color); } .buttonIconDownload-input-field:focus { border-color: var(--download-color); } .input-field-watchLaterGold:focus { border-color: gold; } .input-field-watchLaterSilver:focus { border-color: silver; } .input-field-watchLaterBronze:focus { border-color: var(--bronze-color); } .buttonIconSettings-input-field:focus, .links-header-container input:focus, .sidebar-container input:focus, #custom-css-form .select-file-naming:focus, #custom-css-form .dropdown-list { border-color: var(--settings-color, hsl(0, 0%, 100%)); } .file-naming-container .label-Video-Quality ~ .dropdown-list { max-height: 300px; } .file-naming-container .label-audio-language ~ .dropdown-list, .file-naming-container .label-secondary-language ~ .dropdown-list, .file-naming-container .label-transcript-language ~ .dropdown-list { max-height: 325px; } .file-naming-container .label-subtitle-language ~ .dropdown-list { max-height: 365px; } .input-field-url:hover, .select-file-naming:hover, .chatgpt-prompt-textarea:hover, .buttonIconCopy-input-field:hover, .input-field-targetChatGPTUrl:hover, .buttonIconChatGPT-input-field:hover, .buttonIconDownload-input-field:hover, .buttonIconSettings-input-field:hover, .buttonIconlazyLoad-input-field:hover, .input-field-targetNotebookLMUrl:hover, .buttonIconNotebookLM-input-field:hover { background-color: hsl(0, 0%, 10.37%); } .btn-style-settings { padding: 5px 10px; cursor: pointer; color: whitesmoke; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 400; background-color: hsl(0, 0%, 7%); border: 1px solid hsl(0, 0%, 18.82%); border-radius: 2px; transition: background-color .2s ease-out, border-color .2s ease-out; &:hover { background-color: hsl(0, 0%, 25%); border-color: transparent; } &:active { background-color: hsl(0, 0%, 35%); border-color: hsl(0, 0%, 45%); } } .button-icons { display: block; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; } .icons-container { display: flex; justify-content: space-between; margin-bottom: 15px; } .container-button { display: flex; flex-direction: column; align-items: center; margin: 5px 0 0 0; } .button-icons.features-text { margin: 20px 0 -5px 0; font-size: 1.7em; display: flex; cursor: default; justify-content: center; } .container-button-input { width: 73px; padding: 8px; text-align: center; color: ghostwhite; font-family: -apple-system, system-ui, "Roboto", "Arial", sans-serif; font-size: 2em; line-height: 1.5em; font-weight: 400; transition: background-color .5s ease-in-out, border-color .5s ease-in-out; outline: none; background-color: hsl(0, 0%, 7%); border: 1px solid hsl(0, 0%, 18.82%); border-radius: 2px; box-sizing: border-box; caret-color: var(--field-color); } .container-button-input:focus { background-color: hsl(0, 0%, 10.37%); } .container-button-label { margin-top: 5px; text-align: center; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; } .spacer-5 { height: 5px; } .spacer-10 { height: 10px; } .spacer-15 { height: 15px; } .spacer-20 { height: 20px; } .CentAnni-copyright { font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; color: white; text-decoration: none; transition: color .2s ease-in-out; &:hover { color: #369eff; } } .url-container { margin-bottom: 10px; } .input-field-url { width: 100%; padding: 8px; color: ghostwhite; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 400; transition: background-color .5s ease-in-out, border-color .5s ease-in-out; outline: none; background-color: hsl(0, 0%, 7%); border: 1px solid hsl(0, 0%, 18.82%); border-radius: 2px; box-sizing: border-box; caret-color: var(--field-color, auto); } .input-field-url:focus { background-color: hsl(0, 0%, 10.37%); } .file-naming-container { position: relative; margin-bottom: 15px; } .select-file-naming { width: 100%; padding: 8px; cursor: pointer; color: ghostwhite; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 400; transition: background-color .5s ease-in-out, border-color .5s ease-in-out; outline: none; appearance: none; -webkit-appearance: none; background-color: hsl(0, 0%, 7%); border: 1px solid hsl(0, 0%, 18.82%); border-radius: 2px; box-sizing: border-box; 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'); background-repeat: no-repeat; background-position: right 10px center; background-size: 20px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .hidden-select { position: absolute; visibility: hidden; opacity: 0; pointer-events: none; width: 100%; height: 100%; top: 0; left: 0; } .dropdown-list { visibility: hidden; opacity: 0; position: absolute; z-index: 2100; top: calc(100% + 10px); left: 0; width: 100%; max-height: 60dvh; overflow-y: auto; background-color: hsl(0, 0%, 7%); border: 1px solid var(--download-color, hsl(0, 0%, 100%)); border-radius: 1px 1px 8px 8px; box-sizing: border-box; transform: translateY(-10px); transition: opacity .5s ease-in-out, transform .5s ease-in-out, visibility .5s, border-color .5s ease-in-out; .file-naming-container :is(.label-channel-page, .label-Video-Quality, .label-Text-Transform) ~ & { top: unset; bottom: calc(100% + 10px); transform: translateY(10px); border-radius: 8px 8px 1px 1px; } &.show { visibility: visible; opacity: 1; transform: translateY(0) !important; } } .dropdown-item { padding: 15px; padding-left: 1.6em; cursor: pointer; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.47em; line-height: 1em; font-weight: 400; color: lightgray; position: relative; isolation: isolate; transition: color .2s; &::after { content: ""; position: absolute; z-index: -1; inset: 0; background-color: white; opacity: 0; pointer-events: none; transition: opacity .2s; } &.dropdown-item-selected { font-weight: 600; color: var(--download-color); &::after { opacity: .025; } &::before { content: '✓'; position: absolute; left: 6px; color: var(--download-color); } } &:hover { color: ghostwhite; &::after { opacity: .05; } &::before { color: ghostwhite; font-weight: 600; } } } .select-file-naming:focus { background-color: hsl(0, 0%, 10.37%); border-color: var(--download-color); } .checkbox-label, .number-input-label span { display: flex; align-items: center; align-self: center; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 500; cursor: pointer; text-decoration: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; white-space: pre-line; #CentAnni-main-settings-form & { accent-color: var(--ChatGPT-color); } } .checkbox-label:hover { text-decoration: underline; } .checkbox-container { margin-bottom: 5px; } .checkbox-field { width: 12px; height: 12px; cursor: pointer; margin: 3px 10px 3px 2px; } @supports (corner-shape: squircle) { .sub-panel, .CentAnni-modal-content { border-radius: 16px !important; corner-shape: superellipse(2); } .input-field-url, .btn-style-settings, .select-file-naming, .container-button-input, .chatgpt-prompt-textarea { border-radius: 4px; corner-shape: superellipse(2); } .dropdown-list { border-radius: 0 0 16px 16px; corner-shape: superellipse(2); } } .playback-speed-keys { display: grid; overflow-x: auto; grid-template-columns: auto 1fr; grid-template-rows: auto auto; } .playback-speed-labels { grid-column: 1; grid-row: 1/3; display: flex; flex-direction: column; align-items: center; } .playback-speed-labels > div { height: 45px; display: flex; align-items: center; margin: 0 10px; font-size: 1em; } .key-button-container, .set-speed-container { display: flex; grid-column: 2; } .key-button-container { grid-row: 1; } .set-speed-container { grid-row: 2; } .set-speed-container > .label-style-settings { display: flex; width: 50px; margin-right: 10px; justify-content: center; align-items: center; } .CentAnni-info-text { color: rgba(190, 190, 190, .9); font-family: "Roboto", "Arial", sans-serif; font-size: 1.2em; line-height: 1.5em; font-weight: 400; display: block; margin: -5px 0px 5px 24px; pointer-events: none; cursor: default; white-space: pre-line; &.hide-watched, &.playback-speed { margin: 5px 0; } &.playback-speed { white-space: pre-wrap; } } .button-naming { margin: 0; text-align: center; } .extra-button-container { display: flex; justify-content: center; gap: 5%; margin: 20px 0; } .chatgpt-prompt-textarea { display: block; width: 100%; padding: 8px; height: 50px; outline: none; resize: none; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 1.5em; font-weight: 400; color: ghostwhite; background-color: hsl(0, 0%, 7%); border: 1px solid hsl(0, 0%, 18.82%); border-radius: 2px; box-sizing: border-box; overscroll-behavior: contain; overflow: hidden; cursor: pointer; caret-color: var(--ChatGPT-color); transition: height .8s ease-in-out, background-color .8s ease-in-out, border-color .8s ease-in-out; } .chatgpt-prompt-textarea:focus { height: 630px; background-color: hsl(0, 0%, 10.37%); border-color: var(--ChatGPT-color); overflow: auto; cursor: auto; } chatgpt-prompt { position: relative; display: block; } chatgpt-prompt::after { content: '...'; position: absolute; width: 45%; height: 25px; inset: auto 1px 1px auto; color: ghostwhite; font-size: 2em; line-height: 1em; font-weight: 400; text-align: end; padding-right: 5%; pointer-events: none; transition: opacity .8s cubic-bezier(0, 0, 1, -1.5); background: linear-gradient(to right, transparent, hsl(0, 0%, 7%) 85%); } chatgpt-prompt:focus-within::after { opacity: 0; transition: opacity 0s; } .button-container-end { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; padding-top: 16px; border: none; border-top: solid 1px transparent; border-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, .5), rgba(255, 255, 255, 0)); border-image-slice: 1; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .button-container-backup { display: flex; justify-content: end; gap: 23.5px; } .button-container-settings { display: flex; align-items: center; justify-content: end; gap: 10px; } #voice-search-button.ytd-masthead { margin-right: 8px; } #masthead-skeleton-icons { height: 40px; .masthead-skeleton-icon { background-color: transparent !important; margin: 0 8px 0 0; &:first-child { width: 97.1875px; } &:nth-child(2) { width: 40px; } &:last-child { width: 54px; margin: 0; } } } .CentAnni-button-wrapper { margin-right: 8px; display: flex; background-color: transparent; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } .CentAnni-button-wrapper.disabled, #masthead #end.disabled .CentAnni-button-wrapper:not(.transcript-settings-button) { cursor: not-allowed; } .CentAnni-button-wrapper.disabled > button, #masthead #end.disabled .CentAnni-button-wrapper:not(.transcript-settings-button) > button { pointer-events: none; opacity: .5; } .CentAnni-settings-btn { tp-yt-app-drawer.ytd-app[persistent] #guide-content > #header { display: flex !important; margin-top: -56px; background: var(--yt-base-background); } ytd-app tp-yt-app-drawer.ytd-app[persistent] { background: var(--yt-base-background); } #guide-wrapper { .CentAnni-button-wrapper { margin-left: auto; margin-right: 12px; } .button-tooltip { justify-self: auto; margin-inline: unset; left: anchor(center); transform: translateX(-50%); } } #transcript-settings-button { width: 40px; &:hover { background-color: rgba(255, 255, 255, .1); border-radius: 24px; } &:active { background-color: rgba(255, 255, 255, .2); border-radius: 24px; } } } html:not([dark]) #guide-wrapper { .button-style-settings:hover { color: black; } .transcript-settings-button { &:hover { background-color: rgba(0, 0, 0, .1); border-radius: 24px; } &:active { background-color: rgba(0, 0, 0, .2); border-radius: 24px; } } } .CentAnni-button-wrapper:not(.transcript-settings-button):hover { background-color: rgba(255, 255, 255, .1); border-radius: 24px; } .CentAnni-button-wrapper:not(.transcript-settings-button):active { background-color: rgba(255, 255, 255, .2); border-radius: 24px; } .button-style { width: 40px; height: 40px; anchor-name: var(--centanni-tooltip-anchor); font-family: -apple-system, system-ui, "Roboto", "Arial", sans-serif; font-size: 24px; display: inline-block; position: relative; box-sizing: border-box; vertical-align: middle; color: white; outline: none; background: transparent; margin: 0; border: none; padding: 0; cursor: pointer; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; } #transcript-lazy-button, #transcript-error-button { opacity: .8; filter: grayscale(1); transition: opacity .25s, filter .25s; &:hover { opacity: 1; filter: grayscale(0); } } .button-style-settings { width: fit-content; min-width: 10px; color: rgb(170, 170, 170); &:hover { color: white; } } .button-tooltip { opacity: 0; visibility: hidden; background-color: black; color: white; text-align: center; font-size: 12px; font-family: -apple-system, "Roboto", "Arial", sans-serif; border-radius: 2px; white-space: nowrap; padding: 6px 8px; position: fixed; position-anchor: var(--centanni-tooltip-anchor); inset: auto; top: calc(anchor(bottom) + 8px); justify-self: anchor-center; z-index: 2053; margin-inline: 16px; pointer-events: none; transition: visibility 0s, opacity 0s; border-top: solid 1px white; border-bottom: solid 1px black; border-left: solid 1px transparent; border-right: solid 1px transparent; border-image: linear-gradient(to bottom, white, black); border-image-slice: 1; + .button-tooltip-arrow { opacity: 0; visibility: hidden; position: fixed; position-anchor: var(--centanni-tooltip-anchor); inset: auto; top: calc(anchor(bottom) + 4px); justify-self: anchor-center; width: 10px; height: 10px; z-index: 2052; pointer-events: none; transform: rotate(45deg); transition: visibility 0s, opacity 0s; background: linear-gradient(135deg, white 0%, white 50%, black 50%, black 100%); } .button-style:hover + & { visibility: visible; opacity: 1; transition-delay: 700ms; + .button-tooltip-arrow { visibility: visible; opacity: 1; transition-delay: 700ms; } } } #CentAnni-remaining-time-container { position: relative; display: block; height: 0; top: 3px; text-align: right; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4rem; font-weight: 500; line-height: 1em; color: var(--yt-text-primary); pointer-events: auto; cursor: default; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } #movie_player #CentAnni-remaining-time-container { position: absolute; display: flex; z-index: 2053; height: 56px; opacity: 0; top: unset; bottom: 0; left: 50%; transform: translate(-50%, 0); font-size: 2rem; align-items: center; white-space: nowrap; color: ghostwhite; pointer-events: none; text-shadow: 0 0 2px black, 0 0 1.75px black, 0 0 1.5px black, 0 0 1.25px black, 0 0 1px black, 0 0 1px black, 0 0 1px black; } .html5-video-player.ad-showing #CentAnni-progress-bar-bar, .html5-video-player.ad-showing #CentAnni-progress-bar-start, .html5-video-player.ad-showing #CentAnni-progress-bar-end, ytd-watch-flexy:has(.html5-video-player:is(.ad-showing, .ended-mode)) #CentAnni-remaining-time-container, html.is-watch-page ytd-watch-flexy:has(.html5-video-player:is(.ad-showing, .ended-mode)) #CentAnni-chapter-title, html.is-watch-page:not(.CentAnni-progress-bar) .html5-video-player.ytp-fullscreen.ytp-autohide .ytp-chrome-bottom:has(#CentAnni-remaining-time-container, #CentAnni-chapter-title) .ytp-chrome-controls, html.is-watch-page:not(.CentAnni-progress-bar) .html5-video-player.ytp-fullscreen.ytp-autohide .ytp-chrome-bottom:has(#CentAnni-remaining-time-container, #CentAnni-chapter-title) .ytp-progress-bar-container:not(.active) { opacity: 0 !important; } .ytp-autohide .ytp-chrome-bottom #CentAnni-remaining-time-container { opacity: .8 !important; } .transcript-preload { position: fixed !important; top: var(--ytd-masthead-height, var(--ytd-toolbar-height, 56px)) !important; max-height: var(--ytd-watch-flexy-panel-max-height, 500px) !important; transform: translateX(-50%) !important; left: 50% !important; z-index: -1 !important; opacity: 0 !important; pointer-events: none !important; } #CentAnni-notification-error, #CentAnni-notification-wrapper { pointer-events: none; cursor: default; } #CentAnni-notification-error { z-index: 2053; box-shadow: none; text-decoration: none; display: inline-block; background-color: hsl(0, 0%, 7%); padding: 10px 12px; margin: 0 8px; border: 1px solid hsl(0, 0%, 18.82%); border-radius: 5px; font-family: 'Roboto', Arial, sans-serif; color: rgba(255, 255, 255, .5); text-align: center; font-weight: 500; font-size: 14px; white-space: nowrap; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } html.CentAnni-style-square-search-bar.CentAnni-style-hide-create-btn.CentAnni-style-hide-voice-search #masthead[is-watch-page][theater] { #CentAnni-notification-wrapper { width: 0; display: grid; overflow: visible; position: relative; place-items: center; } #CentAnni-notification-error { position: absolute; right: 0; } } html[dark]:not(.CentAnni-style-no-ambient):has(ytd-watch-flexy[cinematics-active][default-layout] .html5-video-player:not(.unstarted-mode, .ended-mode)) #CentAnni-notification-error { background-color: transparent; border-color: rgba(250, 250, 250, .4); } #CentAnni-playback-speed-popup { position: fixed; top: var(--ytd-toolbar-height, 56px); left: 50%; transform: translateX(-50%); padding: 8px 16px; background: var(--yt-base-background); border-radius: 2px; border: 1px solid hsl(0, 0%, 18.82%); color: var(--yt-text-primary); font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 2.3rem; font-weight: 600; text-align: center; z-index: 2050; transition: opacity .3s cubic-bezier(0, 0, .5, 0); opacity: 0; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; pointer-events: none; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; &.active { opacity: 1; transition: opacity .1s cubic-bezier(0, 1, .5, 1); } } #CentAnni-notification-error.loading span::before { content: "Transcript is loading"; position: absolute; inset: initial; color: rgba(255, 250, 250, .86); opacity: 0; -webkit-animation: pulse 1.5s infinite; animation: pulse 1.5s infinite; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } @-webkit-keyframes pulse { 0%, 100% { opacity: 0; } 50% { opacity: .71; } } @keyframes pulse { 0%, 100% { opacity: 0; } 50% { opacity: .71; } } #start.ytd-masthead { gap: 10px; #guide-button.ytd-masthead { margin-right: -10px; } &:not(:has(.buttons-left)) { min-width: var(--startMastheadWidth); } } #logo.ytd-masthead { width: auto !important; } .buttons-left { font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 14px; font-weight: 500; line-height: 1em; letter-spacing: -.5px; display: inline-block; position: relative; color: ghostwhite; text-decoration: none; cursor: pointer; margin: 0; padding: 0; outline: none; background: transparent; border: none; text-align: center; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; &:hover { color: #ff0000 !important; } &:active { color: rgb(200, 25, 25) !important; } } .sub-panel-overlay { position: fixed; z-index: 2100; left: 0; top: 0; width: 100%; height: 100%; display: none; opacity: 0; transition: opacity, display; transition-behavior: allow-discrete; transition-duration: .5s; transition-timing-function: cubic-bezier(.18, .47, .3, .97); background-color: rgba(0, 0, 0, .5); font-family: "Roboto", "Arial", sans-serif; font-size: 9px; line-height: 1.2; color: white; justify-content: center; align-items: center; backdrop-filter: blur(1px); -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; .sub-panel { z-index: 2177; background-color: rgba(17, 17, 17, .8); padding: 20px; border: 1px solid rgba(255, 255, 255, .25); border-radius: 8px; width: 60dvw; max-width: 70dvw; max-height: 90dvh; position: relative; display: flex; flex-direction: column; overflow-y: auto; color: whitesmoke; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; translate: 0 10dvh; transition: translate .5s cubic-bezier(.18, .47, .3, .97); button { position: sticky; top: 0; align-self: flex-end; box-shadow: 0 0 20px 10px black; } } &.active { display: flex; opacity: 1; .sub-panel { translate: 0 0; } @starting-style { opacity: 0; .sub-panel { translate: 0 -10dvh; } } } } .sub-panel-header { margin: -24px 60px 20px 0px; padding: 0px 0px 0px 0px; border: 0; text-align: left; text-decoration: none; font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 2em; line-height: 1em; font-weight: 700; text-overflow: ellipsis; white-space: normal; text-shadow: 0 0 30px 20px black; cursor: default; color: white; align-self: left; position: relative; z-index: 2180; } #links-in-header-form .CentAnni-info-text { margin: -10px 80px 20px 0px; } .links-header-container { display: flex; align-items: center; gap: 20px; } .links-header-container label { color: whitesmoke; } .slider-container .sub-panel input[type="range"], .links-header-container .url-container:first-child { flex: 1; } .sidebar-container .url-container, .links-header-container .url-container:last-child { flex: 2; } .sidebar-container { display: flex; align-items: center; margin: 10px 0 0 0; color: whitesmoke; justify-content: flex-start; gap: 20px; } .sidebar-container .checkbox-container { margin-bottom: 0 !important; flex: 1; } #custom-css-form { .playback-speed-container { display: flex; gap: 10px; margin: 10px 0; .checkbox-container { max-width: calc(55% - 5px); margin: 0; align-content: center; flex: 0 0 auto; } .number-input-container { max-width: calc(45% - 5px); margin: 0; align-self: center; flex: 1 0 auto; } .number-input-label { display: flex; } } .CentAnni-container-theater-mode { display: flex; flex-direction: row; gap: 10px; margin-bottom: -10px; span.checkbox-label { pointer-events: none; } } } #color-code-videos-form .checkbox-container { margin: 20px 0 0 0; } #color-code-videos-form .label-style-settings, #color-code-videos-form > div.videos-old-container > span { margin: 0; } #color-code-videos-form .CentAnni-info-text { margin: 5px 60px 20px 0px; } #custom-css-form .checkbox-container { margin: 10px 0; } #custom-css-form .file-naming-container { max-width: 90%; margin: 20px 0; display: flex; gap: 25px; align-content: center; } #custom-css-form .label-style-settings { margin-bottom: 0; white-space: nowrap; align-content: center; } #custom-css-form .dropdown-item { line-height: 2em; padding-top: 7px; padding-right: 7px; padding-bottom: 7px; } #custom-css-form .dropdown-item-selected, #custom-css-form .dropdown-item-selected::before { color: var(--settings-color); } .sub-panel input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: #ccc; border-radius: 5px; outline: none; } .sub-panel input[type="range"]::-moz-range-thumb, .sub-panel input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px; background: #007bff; border-radius: 50%; cursor: pointer; border: 2px solid #ffffff; } .sub-panel input[type="range"]::-moz-range-track, .sub-panel input[type="range"]::-webkit-slider-runnable-track { background: #007bff; height: 6px; border-radius: 5px; } .videos-old-container { display: flex; max-width: 90%; align-items: center; gap: 25px; margin: 20px 0; } .slider-container { display: flex; align-items: center; gap: 10px; flex: 1; } .videos-colorpicker-container { display: flex; flex-direction: column; align-items: center; gap: 30px; } .videos-colorpicker-row { display: flex; justify-content: flex-start; align-items: center; width: 100%; gap: 20px; margin: 0; } .videos-colorpicker-row span { text-align: right; flex: 1; max-width: 50%; } .videos-colorpicker-row input { flex: 1; margin: 0; padding: 0; max-width: 62px; height: 26px; cursor: pointer; background: none; border: none; box-shadow: none; appearance: none; -webkit-appearance: none; -moz-appearance: none; } .sub-panel input[type="color"]::-webkit-color-swatch-wrapper { border: none; padding: 0; } .sub-panel input[type="color"]::-webkit-color-swatch { border: none; } #custom-css-form .videos-colorpicker-row span { text-align: left; flex: initial; } #custom-css-form .videos-colorpicker-row input { margin: 0 0 0 -3px; } #custom-css-form .videos-colorpicker-row { gap: 10px; } #color-code-videos-form .videos-colorpicker-row { flex-direction: row-reverse; justify-content: flex-end; } #color-code-videos-form .videos-colorpicker-row span:nth-child(1), #color-code-videos-form .videos-colorpicker-row span:nth-child(3) { margin-left: -10px; width: fit-content; } #color-code-videos-form .videos-colorpicker-row span { flex: unset; width: 48%; } .number-input-container { margin: 10px 0; } .number-input-field { width: 5ch; min-width: 44px; margin: 0 10px 0 0; padding: 3px; align-items: center; font-size: 1.4em; line-height: 1.5em; font-weight: 700; cursor: auto; text-decoration: none; text-align: center; display: inline-block; border: none; } .sub-panel input[type="number"] { -webkit-appearance: none; -moz-appearance: textfield !important; appearance: none; } .sub-panel input[type="number"]::-webkit-outer-spin-button, .sub-panel input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .number-input-label span { display: initial; cursor: auto; } .selection-color-container { margin: 10px 0; flex-direction: row; } .selection-color-container > .checkbox-container { margin: 0 !important; } .selection-color-container > .videos-colorpicker-row { width: auto; } :is(:fullscreen, :-webkit-full-screen, .html5-video-player.ytp-fullscreen) :has(#CentAnni-remaining-time-container) #ytp-caption-window-container { top: 0; bottom: unset; } :is(:fullscreen, :-webkit-full-screen, .html5-video-player.ytp-fullscreen) :has(#CentAnni-remaining-time-container) .ytp-autohide #ytp-caption-window-container { bottom: 40px; top: unset; } /* customCSS CSS */ /* progress bar css */ .CentAnni-progress-bar { #CentAnni-progress-bar-bar { width: 100%; height: 3px; background: rgba(255, 255, 255, .2); position: absolute; bottom: 0; opacity: 0; z-index: 58; pointer-events: none; } #CentAnni-progress-bar-progress, #CentAnni-progress-bar-buffer { width: 100%; height: 3px; transform-origin: 0 0; position: absolute; } #CentAnni-progress-bar-progress { background: var(--progressBarColor); filter: none; z-index: 1; } .ytp-ad-persistent-progress-bar-container, .ytp-autohide .ytp-chrome-bottom .ytp-load-progress, .ytp-autohide .ytp-chrome-bottom .ytp-play-progress { display: none !important; } .ytp-autohide .ytp-chrome-bottom, .ytp-delhi-modern.ytp-fullscreen-grid-active:not(.html5-video-player.ended-mode):not(.ytp-grid-scrolling) .ytp-chrome-bottom { opacity: 1 !important; display: block !important; } .ad-interrupting #CentAnni-progress-bar-progress { background: transparent; } #CentAnni-progress-bar-buffer { background: rgba(255, 255, 255, .4); } .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container { bottom: 0 !important; opacity: 1 !important; height: 4px !important; transform: translateX(0px) !important; z-index: 100; } .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar, .ytp-autohide .ytp-chrome-bottom .ytp-progress-list { background: transparent !important; box-shadow: none !important; } ytd-watch-flexy[fullscreen] .ytp-autohide .ytp-chrome-bottom .previewbar, ytd-watch-flexy:not([fullscreen]) .html5-video-player.ytp-autohide:not(.paused-mode) .ytp-chrome-bottom .previewbar { height: calc(100% + 1px) !important; bottom: -1px !important; margin-bottom: 0 !important; border: none !important; box-shadow: none !important; will-change: opacity, transform !important; &:not(.segment-watched) { opacity: 1 !important; } } .ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container:not(.active) .ytp-scrubber-container { opacity: 0; pointer-events: none; } #CentAnni-progress-bar-start, #CentAnni-progress-bar-end { position: absolute; height: 3px; width: 12px; bottom: 0; z-index: 2077; opacity: 0; pointer-events: none; } #CentAnni-progress-bar-start { left: calc(var(--progressBarMargin) - 12px); background: var(--progressBarColor); } #CentAnni-progress-bar-end { right: calc(var(--progressBarMargin) - 12px); background: rgba(255, 255, 255, .2); } #CentAnni-progress-bar-end.loaded { width: 8px; background: rgba(255, 255, 255, .52); } .ytp-autohide .ytp-chrome-bottom .ytp-timed-marker { background-color: black; width: 2px; height: 5px; bottom: -1px; border-radius: 0; } ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom .ytp-progress-bar-container { bottom: var(--yt-delhi-bottom-controls-height, 72px) !important; height: 6px !important; } ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom .ytp-load-progress, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom .ytp-play-progress { display: block !important; } ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom .ytp-progress-list { background: rgba(40, 40, 40, .6) !important; } .ytp-autohide.ytp-delhi-modern:not(.playing-mode, .ended-mode) .caption-window.ytp-caption-window-bottom { margin-bottom: 0; bottom: 2%; } .ytp-delhi-modern.ytp-fullscreen-grid-peeking .caption-window.ytp-caption-window-bottom { margin-bottom: 40px; } ytd-watch-flexy:not([fullscreen]) .ytp-delhi-modern:not(.playing-mode, .ended-mode) .caption-window.ytp-caption-window-bottom { margin-bottom: calc(var(--yt-delhi-bottom-controls-height, 72px) + 14px); } .caption-window.ytp-caption-window-bottom { -webkit-transition: unset !important; transition: unset !important; } ytd-watch-flexy[fullscreen] .ytp-delhi-modern .ytp-progress-bar-container { margin-bottom: -4px; } ytd-watch-flexy[fullscreen] .ytp-autohide #previewbar { bottom: 4px; } .ytp-autohide #CentAnni-progress-bar-start.active, .ytp-autohide #CentAnni-progress-bar-bar.active, .ytp-autohide #CentAnni-progress-bar-end.active, ytd-shorts #scrubber .ytPlayerProgressBarHostHidden, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) .ytp-chrome-bottom .ytp-chrome-controls { opacity: 1 !important; } .ytp-autohide .ytp-chrome-bottom .ytp-chrome-controls, #ytd-player .html5-video-player.ended-mode #CentAnni-progress-bar-start, #ytd-player .html5-video-player.ended-mode #CentAnni-progress-bar-bar, #ytd-player .html5-video-player.ended-mode #CentAnni-progress-bar-end, #ytd-player .html5-video-player.ended-mode.ytp-fullscreen #CentAnni-chapter-title, #ytd-player .html5-video-player.ended-mode.ytp-autohide .ytp-chrome-bottom .previewbar, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) #CentAnni-progress-bar-start.active, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) #CentAnni-progress-bar-bar.active, ytd-watch-flexy:not([fullscreen]) #ytd-player .html5-video-player.ytp-autohide:not(.playing-mode, .ended-mode) #CentAnni-progress-bar-end.active { opacity: 0 !important; } } /* button click animation */ @keyframes CentAnni-btn-stroke { from { border-color: color-mix(in srgb, var(--yt-primary) 20%, transparent); } to { border-color: transparent; } } .CentAnni-btn-feedback-shape { position: relative; isolation: isolate; &:hover { background-color: var(--yt-btn-hover); } &::after { content: ""; position: absolute; inset: 0; box-sizing: border-box; z-index: -1; border: 1px solid transparent; border-radius: inherit; pointer-events: none; background-color: transparent; } &:active::after { animation: none; background-color: var(--yt-spec-background); } &:focus:not(:active)::after { animation: CentAnni-btn-stroke 400ms cubic-bezier(.2, 0, .6, 1); } } /* playback speed css */ .CentAnni-playback-speed { #actions.ytd-watch-metadata { justify-content: end; } .yt-spec-button-view-model.style-scope.ytd-menu-renderer { display: flex; height: 36px; width: 36px; margin-right: 8px; overflow: hidden; } #below yt-button-view-model.ytd-menu-renderer:not(:empty):not(#flexible-item-buttons yt-button-view-model):not([hidden] yt-button-view-model) { margin-left: 8px; } ytd-menu-renderer[has-flexible-items][safe-area] .top-level-buttons.ytd-menu-renderer { margin-bottom: 0; } .CentAnni-playback-control { display: flex; justify-content: center; align-items: center; flex: 1; margin-left: 8px; } .CentAnni-playback-speed-icon { height: 24px; width: 24px; padding: 0 4px 0 0; opacity: .9; } .CentAnni-playback-speed-display { height: 36px; min-width: 4.5ch; padding: 0 8px; border-radius: 0; justify-content: center; align-items: center; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; box-sizing: content-box !important; } .CentAnni-playback-speed-button { outline: none; cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; box-sizing: content-box !important; } .CentAnni-playback-speed-button:nth-child(2) { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } .CentAnni-playback-speed-button:last-child { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; } .CentAnni-playback-speed-display::before, .CentAnni-playback-speed-button:last-child::before { content: ""; background: var(--yt-btn-hover); position: absolute; left: 0; top: 6px; height: 24px; width: 1px; } ytd-watch-metadata[flex-menu-enabled] #actions-inner.ytd-watch-metadata ytd-menu-renderer { max-width: 580px; } ytd-watch-metadata[flex-menu-enabled] #menu.ytd-watch-metadata { margin-left: auto; } #above-the-fold:not(:has(#CentAnni-playback-speed-control)) .ytSegmentedLikeDislikeButtonViewModelHost { margin-left: 148px; } ytd-watch-metadata[actions-on-separate-line] #actions.ytd-watch-metadata ytd-menu-renderer.ytd-watch-metadata { justify-content: flex-end; } ytd-watch-metadata[actions-on-separate-line] #top-row.ytd-watch-metadata { display: flex; flex-wrap: wrap; justify-content: space-between; } ytd-watch-metadata[action-buttons-update-owner-width] #actions.ytd-watch-metadata { min-width: 50%; } ytd-watch-metadata[action-buttons-update-owner-width] #owner.ytd-watch-metadata { margin-right: 0; max-width: 50%; } .ytp-menuitem:has(path[d^="M12 1c1.4"]), .ytp-menuitem:has(path[d^="M10,8v8l6-4L10"]) { display: none; } } .CentAnni-playback-speed-btns { ytd-watch-flexy[theater][is-two-columns_][full-bleed-player] #secondary.ytd-watch-flexy { margin-top: unset !important; flex-direction: column-reverse; } #CentAnni-speed-buttons { position: relative; display: flex; height: fit-content; width: var(--ytd-watch-flexy-sidebar-width); min-width: var(--ytd-watch-flexy-sidebar-min-width); padding-bottom: 12px; box-sizing: border-box; overflow: auto hidden; gap: 12px; } .CentAnni-speed-preset-button { padding: 0 10px !important; min-width: fit-content !important; } .CentAnni-speed-preset-button.active { background: var(--CentAnniRed) !important; } ytd-watch-flexy[default-layout] #CentAnni-speed-buttons { margin-top: 24px; width: calc(100% - 12px); min-width: unset; justify-content: flex-start; } ytd-watch-flexy #bottom-row.ytd-watch-metadata { height: fit-content !important; } &.CentAnni-video-tabView ytd-watch-flexy[default-layout] .ytVideoMetadataCarouselViewModelHost { margin-bottom: -10px; } } html.is-watch-page:not(.CentAnni-video-tabView) ytd-watch-flexy[theater] #CentAnni-speed-buttons { position: absolute; top: 12px; } html.CentAnni-playback-speed-btns:not(.CentAnni-video-tabView) { ytd-watch-flexy[theater][is-two-columns_][full-bleed-player] #secondary.ytd-watch-flexy { padding-top: 50px; } ytd-watch-flexy[theater] :where(#chat.ytd-watch-flexy, #donation-shelf.ytd-watch-flexy ytd-donation-shelf-renderer.ytd-watch-flexy, #donation-shelf.ytd-watch-flexy ytd-donation-unavailable-renderer.ytd-watch-flexy, #playlist.ytd-watch-flexy, #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy, ytd-watch-flexy[persistent-panel-visible] #persistent-panel-container.ytd-watch-flexy) { margin-top: 10px; } } /* video tab view css */ .CentAnni-video-tabView { .CentAnni-tabView { position: relative; display: flex; flex-direction: column; width: var(--ytd-watch-flexy-sidebar-width); min-width: var(--ytd-watch-flexy-sidebar-min-width); font-family: "Roboto", "Arial", sans-serif; margin: 0; padding: 0; border-radius: 12px 12px 0 0; border: 1px solid var(--yt-btn-hover); overflow: hidden; box-sizing: border-box; background-color: transparent; z-index: 10; } .CentAnni-tabView:has(.CentAnni-tabView-tab.active[data-tab="tab-2"]) { border-radius: 12px; } .CentAnni-tabView-header { display: flex; position: relative; overflow: hidden; height: 48px; padding: 0; margin: 0; color: var(--yt-text-primary); background-color: var(--yt-raised-background); z-index: 7; } .CentAnni-tabView-subheader { display: flex; flex: 1 1 auto; flex-direction: row; min-width: 0; width: 100%; height: 48px; font-size: 9px; line-height: 1; padding: 0 8px; align-items: center; box-sizing: border-box; gap: 8px; z-index: 8; overflow-x: auto; overflow-y: hidden; -webkit-mask-image: linear-gradient(to right, transparent, black 8px, black calc(100% - 8px), transparent); mask-image: linear-gradient(to right, transparent, black 8px, black calc(100% - 8px), transparent); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; scrollbar-width: thin; } .CentAnni-tabView-tab { align-items: center; outline: none; border: none; border-radius: 8px; display: inline-flex; height: 32px; min-width: 12px; white-space: nowrap; font-family: "Roboto", "Arial", sans-serif; font-size: 1.4em; line-height: 2em; font-weight: 500; margin: 0; background-color: var(--yt-spec-background); color: var(--yt-text-primary); text-decoration: none; padding: 0 12px; flex: 0 0 auto; z-index: 10; &:hover { background-color: var(--yt-btn-hover); } &.hidden { display: none; } } .CentAnni-tabView-content { min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0; margin: 0; display: none; overscroll-behavior: contain; max-height: var(--ytd-watch-flexy-panel-max-height); } .CentAnni-info-date { margin-left: 6px; } #tab-2 { border-top: 1px solid var(--yt-btn-hover); } #donation-shelf, .CentAnni-tabView-content-nascosta, ytd-watch-flexy:not([fullscreen]) .CentAnni-tabView-content-hidden { opacity: 0; visibility: hidden; } #donation-shelf, [target-id="PAyouchat"] .chatInputViewModelPromoButton, ytd-watch-flexy:not([fullscreen]) .CentAnni-tabView-content-hidden { display: none; } .CentAnni-tabView-content-active, .CentAnni-tabView-content-attiva { opacity: 1 !important; visibility: visible !important; } .CentAnni-tabView-content.active, .CentAnni-tabView-content-active, .CentAnni-tabView-content-block, #expandable-metadata.ytd-watch-flexy:not(:empty), ytd-watch-flexy #expandable-metadata #content.ytd-expandable-metadata-renderer { display: block !important; } #related.ytd-watch-flexy, ytd-donation-shelf-renderer, #container.ytd-playlist-panel-renderer, ytd-engagement-panel-section-list-renderer:not([live-chat-engagement-panel]) { border-radius: 0 0 12px 12px !important; } #related.ytd-watch-flexy, ytd-watch-flexy:not([fullscreen]) #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy { position: absolute; width: var(--ytd-watch-flexy-sidebar-width); top: calc(50px + var(--topHeaderMargin)); left: 0; margin: 0; z-index: 5; padding: 0; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; border: 1px solid var(--yt-btn-hover); border-top: none; } #related.ytd-watch-flexy { padding: 15px 10px 0 10px; max-height: var(--ytd-watch-flexy-panel-max-height); visibility: hidden; opacity: 0; &[full-bleed-player] { top: 30px; } .ytLockupViewModelHorizontal .ytLockupViewModelContentImage { width: 44% !important; } } ytd-watch-flexy:not([fullscreen]) #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-structured-description] { background: transparent; padding-left: 10px; } ytd-watch-flexy:not([fullscreen]) ytd-structured-description-content-renderer[engagement-panel] #items.ytd-structured-description-content-renderer { padding: 10px 10px 0 0; } #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-structured-description] #content.ytd-engagement-panel-section-list-renderer { mask-image: linear-gradient(to bottom, transparent, black 10px); } ytd-watch-flexy:not([fullscreen]) #playlist { position: absolute; top: calc(50px + var(--topHeaderMargin)); width: var(--ytd-watch-flexy-sidebar-width); left: 0; margin: 0; z-index: 5; padding: 0; opacity: 0; margin-bottom: 0; display: none; visibility: hidden; } ytd-watch-flexy { /* theater mode active */ &[theater] { .CentAnni-tabView { border-radius: 25px; } .CentAnni-tabView-tab { border-radius: 18px; &.active { background: rgb(245 245 245 / .3); outline: 1px solid rgb(250 250 250 / .4); } } #donation-shelf, .CentAnni-tabView-content { display: none !important; } #primary { overflow-x: hidden; } &:not([fullscreen]) { #playlist, ytd-comments, #related.ytd-watch-flexy, #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy, ytd-playlist-panel-renderer[collapsible] .header.ytd-playlist-panel-renderer { height: 0; padding-top: 0 !important; padding-bottom: 0 !important; margin-top: 0; margin-bottom: 0; opacity: 0; visibility: hidden; z-index: -1; pointer-events: none; border: none !important; } } &[flexy][js-panel-height_]:not([fullscreen]) #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy, &[flexy][js-panel-height_]:not([fullscreen]) #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id="engagement-panel-structured-description"] { height: 0; padding-top: 0; padding-bottom: 0; margin-top: 0; margin-bottom: 0; opacity: 0; visibility: hidden; z-index: -1; pointer-events: none; border: none; } ytd-engagement-panel-section-list-renderer[target-id=PAyouchat], ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-posts] { right: 16px; opacity: 1 !important; z-index: 17 !important; left: unset !important; height: 500px !important; position: fixed !important; visibility: visible !important; pointer-events: all !important; } &:not([hidden]) #sponsorBlockPopupContainer { position: fixed; top: 56px; right: 0; } &[is-two-columns_] { &[full-bleed-player] #secondary.ytd-watch-flexy { margin-top: 0; display: flex; justify-content: center; align-self: center; } #columns { height: fit-content; max-height: calc(100dvh - var(--ytd-masthead-height, var(--ytd-toolbar-height)) - clamp(480px, 100dvw * var(--ytd-watch-flexy-height-ratio) / var(--ytd-watch-flexy-width-ratio), 100dvh - 169px)); } } #title ytd-badge-supported-renderer:not([hidden]) { bottom: 0; transform: translate(48px, calc(100% + 5px)); } } /* default mode active */ &[default-layout] { .CentAnni-tabView-tab.active { background-color: #f1f1f1; color: #0f0f0f; } #container.ytd-playlist-panel-renderer { max-height: var(--ytd-watch-flexy-panel-max-height) !important; ytd-playlist-panel-video-renderer[selected][use-color-palette], ytd-playlist-panel-video-renderer[selected][use-color-palette]:hover:not(.dragging) { background-color: var(--yt-spec-background); } ytd-playlist-panel-video-renderer[selected][use-color-palette] { #video-title.ytd-playlist-panel-video-renderer { color: var(--yt-text-primary); } #index.ytd-playlist-panel-video-renderer, #byline.ytd-playlist-panel-video-renderer, #play-icon.ytd-playlist-panel-video-renderer, #video-info.ytd-playlist-panel-video-renderer { color: var(--yt-text-secondary); } } ytd-playlist-panel-video-renderer.ytd-playlist-panel-renderer { padding: 4px 0 !important; } #thumbnail-container.ytd-playlist-panel-video-renderer { width: 37%; height: fit-content; aspect-ratio: 16 / 9; > ytd-thumbnail.ytd-playlist-panel-video-renderer { width: 100%; height: 100%; } } #menu.ytd-playlist-panel-video-renderer { display: none; } } ytd-live-chat-frame:not([no-border]), ytd-engagement-panel-section-list-renderer[target-id=PAyouchat], ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-posts] { height: var(--ytd-watch-flexy-chat-max-height) !important; border: 1px solid rgb(51, 51, 51); } /* adjustments for: Ask about this video and Quizzes */ ytd-engagement-panel-section-list-renderer[target-id=PAyouchat], ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-posts] { z-index: 2026 !important; max-height: unset !important; margin-top: -50px !important; } #title ytd-badge-supported-renderer:not([hidden]) { bottom: unset; transform: translate(48px, calc(500% + 12px)); } #primary #below { overflow-y: auto; overflow-x: hidden; min-height: var(--spaceBelow); height: calc(100dvh - var(--ytd-masthead-height, var(--ytd-toolbar-height)) - var(--ytd-margin-6x) - (calc(100dvw - (var(--ytd-margin-6x) * 3) - var(--ytd-watch-flexy-sidebar-width)) * var(--ytd-watch-flexy-height-ratio) / var(--ytd-watch-flexy-width-ratio))); } &:not([fullscreen]) { --ytd-watch-flexy-chat-max-height: calc(100dvh - var(--ytd-masthead-height, var(--ytd-toolbar-height)) - var(--topHeaderMargin) - var(--ytd-margin-0x)) !important; --ytd-watch-flexy-panel-max-height: calc(var(--ytd-watch-flexy-chat-max-height) - var(--CentAnniTabViewHeader)) !important; --ytd-watch-flexy-structured-description-max-height: var(--ytd-watch-flexy-panel-max-height) !important; } &[cinematics-active] #cinematics-container { position: fixed; inset: 0; #cinematics { height: 100dvh; width: 100dvw; overflow: hidden; > div { height: 100% !important; width: 100% !important; margin: 0 !important; aspect-ratio: auto !important; } } } .ytd-watch-flexy[target-id="engagement-panel-macro-markers-description-chapters"] h3.ytd-macro-markers-list-item-renderer { line-clamp: 4; -webkit-line-clamp: 4; max-height: unset; } &[flexy-small-window_] #playlist[playlist-type="TLPQ"] #end-actions { position: absolute; right: 0; transform: translateY(-40px); } #playlist .header { pointer-events: none; .title a, #publisher-container a, #playlist-actions button { pointer-events: auto; } .publisher.ytd-playlist-panel-renderer { display: flex !important; } h3:not(#next-video-title) { display: block !important; } #next-video-title, #next-video-title + #publisher-container > .byline-title, .publisher[is-empty] ~ .index-message-wrapper.ytd-playlist-panel-renderer::before { display: none; } } } #above-the-fold #top-row { border: none !important; padding: 0 !important; } #related yt-chip-cloud-renderer:not([no-top-margin]) yt-chip-cloud-chip-renderer.yt-chip-cloud-renderer { margin: 0 8px 8px 0; } ytd-watch-metadata.ytd-watch-flexy { margin: 12px 0 0 0 !important; } &:not([fullscreen]) ytd-comments { margin: 0; padding: 0 10px; } .ryd-tooltip-new-design { bottom: 0; .ryd-tooltip-bar-container { padding: 0; top: 0 } } ytd-watch-metadata:not(:has(#CentAnni-chapter-title)) #description.ytd-watch-metadata { margin-top: 0; } #title ytd-badge-supported-renderer:not([hidden]) { position: absolute; cursor: default; .ytBadgeShapeDefault { background: transparent; padding: 0; &[aria-label^="AI"] { color: white; transform: translate(-150%, -200%); } } } } #comments > #sections > #header > ytd-comments-header-renderer > #title > #additional-section { margin-left: auto; } #comments > #sections > #header { margin: 0 12px; } ytd-engagement-panel-section-list-renderer { box-sizing: content-box; display: flexbox; display: flex; flex-direction: column; } ytd-watch-flexy #description.ytd-expandable-video-description-body-renderer { padding-right: 10px !important; } #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer.ytd-watch-flexy { margin-bottom: 0; } ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-structured-description"] ytd-structured-description-content-renderer { padding: 0 !important; } ytd-watch-flexy #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-searchable-transcript] #footer #menu.ytd-engagement-panel-title-header-renderer { margin-left: auto; } ytd-transcript-renderer { flex: 1 1 auto; } ytd-transcript-segment-list-renderer { height: 100%; } ytd-text-inline-expander[engagement-panel] { display: block; margin: 0; padding: 0; border: none; border-radius: 0; background: transparent; } .ytwHowThisWasMadeSectionViewModelHost { padding: 16px 10px 16px 0; } ytd-expandable-video-description-body-renderer > ytd-text-inline-expander > #snippet { max-height: 100% !important; } #media-lockups > ytd-structured-description-playlist-lockup-renderer > #lockup-container > #playlist-thumbnail { width: 100%; } #media-lockups #lockup-container:hover { filter: brightness(1.1); } ytd-expandable-video-description-body-renderer[engagement-panel]:not([shorts-panel]) ytd-expander.ytd-expandable-video-description-body-renderer { border-radius: 0; padding: 0; background: transparent; } ytd-structured-description-content-renderer[engagement-panel] ytd-expandable-video-description-body-renderer.ytd-structured-description-content-renderer, 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 { padding: 0; } ytd-watch-flexy:not([fullscreen]) ytd-video-description-transcript-section-renderer { position: fixed; bottom: 0; left: 0; z-index: -9999; pointer-events: none; opacity: 0; } ytd-video-description-infocards-section-renderer[engagement-panel] #social-links.ytd-video-description-infocards-section-renderer { margin: 0 0 16px 0; padding: 0; } ytd-structured-description-content-renderer[engagement-panel] ytd-video-description-infocards-section-renderer.ytd-structured-description-content-renderer { padding: 16px 0 0 0; } #infocards-section > ytd-compact-infocard-renderer:last-of-type { margin-bottom: 0; } .ytp-chapter-container, #bottom-row > #description { cursor: default; pointer-events: none; } #content > #description a:hover, #snippet > #snippet-text > yt-attributed-string a:hover { text-decoration: underline; } #shelf-container .arrow:hover, #social-links .arrow-container:hover { background-color: var(--yt-base-background-secondary); } ytd-compact-video-renderer:hover, .ytwFactoidRendererColorSampledHost, #topic-link a#topic-link-container:hover, .yt-video-attribute-view-model--clickable:hover, .ytVideoAttributeViewModelContentContainer:hover, .ytSpecTouchFeedbackShapeHovered .ytSpecTouchFeedbackShapeFill, ytd-video-description-course-section-renderer > #topic-link:hover, ytd-structured-description-content-renderer #media-lockups a:hover, #infocards-section > ytd-compact-infocard-renderer > #content:hover, yt-video-attribute-view-model.ytd-horizontal-card-list-renderer:hover, #items > ytd-video-description-infocards-section-renderer > #header:hover, ytd-watch-card-compact-video-renderer.ytd-vertical-watch-card-list-renderer:not([is-condensed]):hover, #always-shown > ytd-rich-metadata-row-renderer > #contents > ytd-rich-metadata-renderer > #endpoint-link:hover, #items > ytd-video-description-infocards-section-renderer > #infocards-section > ytd-compact-infocard-renderer > #content:hover, ytd-playlist-panel-renderer h3 yt-formatted-string[has-link-only_]:not([force-default-style]) a.yt-simple-endpoint.yt-formatted-string:hover { background-color: var(--yt-spec-background) !important; } .ytSpecTouchFeedbackShapeHovered .ytSpecTouchFeedbackShapeHoverEffect { background: var(--yt-spec-background) !important; } .ytSpecTouchFeedbackShapeHovered .ytSpecTouchFeedbackShapeStroke { border-color: var(--yt-spec-background) !important; } .ytSpecTouchFeedbackShapeHoverEffect { transition: unset; transform: unset; } .ytp-delhi-modern .ytp-offline-slate-button:hover { background-color: #d9d9d9; } #playlist :is(.title, .publisher) a:hover { color: var(--CentAnniRed); } #playlist-actions.ytd-playlist-panel-renderer { cursor: default; } ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a[href*="/hashtag/"] { cursor: pointer; pointer-events: all; color: var(--yt-text-secondary); } ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a:hover { color: var(--yt-spec-call-to-action); } ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata:hover { background: transparent; } .yt-animated-icon.lottie-player.style-scope { pointer-events: none; } #description.ytd-watch-metadata { background: none; } ytd-transcript-search-box-renderer { margin: 12px 0; } #ytd-watch-info-text.ytd-watch-metadata { height: fit-content; } #middle-row.ytd-watch-metadata { padding: 10px 0; } .content.style-scope.ytd-info-panel-content-renderer { padding: 10px 16px; } #description-inner.ytd-watch-metadata { margin: 0px 12px 0 52px; } #view-count.ytd-watch-info-text, #date-text.ytd-watch-info-text { align-items: center; } ytd-watch-info-text:not([detailed]) #info.ytd-watch-info-text { display: flex; flex-wrap: wrap; align-content: center; } #bottom-row.ytd-watch-metadata { flex-direction: column; flex-wrap: nowrap; } .ytVideoMetadataCarouselViewModelHost { flex-direction: row; padding: 12px; height: fit-content; gap: 20px; align-items: center; } .ytVideoMetadataCarouselViewModelCarouselContainer { margin-top: 0; } /* live chat adjustments */ #columns > #secondary > #secondary-inner > #chat-container { top: var(--topHeaderMargin); position: absolute; width: var(--ytd-watch-flexy-sidebar-width); } #chat.ytd-watch-flexy { margin-bottom: 0; } #donation-shelf.ytd-watch-flexy ytd-donation-shelf-renderer.ytd-watch-flexy { margin-bottom: 0; overflow-y: auto; } ytd-donation-shelf-renderer { border-top: none; } #container.ytd-playlist-panel-renderer { border-top: none; } ytd-playlist-panel-renderer[collapsible] .header.ytd-playlist-panel-renderer { padding: 12px 0 0 25px; margin-right: 0; } .playlist-items.ytd-playlist-panel-renderer { padding: 0; } #container.ytd-masthead { padding: 0 16px; } ytd-macro-markers-list-renderer:not([browsing-mode]) #sync-container.ytd-macro-markers-list-renderer { transform: unset !important; display: none; } ytd-macro-markers-list-renderer.browsing-mode #sync-container.ytd-macro-markers-list-renderer { transform: translateY(0); display: flex; } @media (min-width:1200px) { ytd-item-section-renderer[is-grid-view-enabled] #contents.ytd-item-section-renderer, ytd-watch-next-secondary-results-renderer[is-grid-view-enabled] #items.ytd-watch-next-secondary-results-renderer { grid-template-columns: repeat(2, 50%) !important; grid-gap: 0 !important; } } ytd-watch-flexy ytd-expandable-video-description-body-renderer yt-attributed-string#attributed-snippet-text.style-scope.ytd-text-inline-expander { display: block !important; } .watch-skeleton, ytd-text-inline-expander, #collapse-controls-section, #body.ytd-transcript-renderer, .CentAnni-tabView-content-none, #below ytd-merch-shelf-renderer, .ytContentMetadataViewModelLeadingIcon, #description > #description-interaction, #description-placeholder.ytd-watch-metadata, #ghost-cards.ytd-continuation-item-renderer, #trailing-button.ytd-playlist-panel-renderer, ytd-live-chat-frame[modern-buttons][collapsed], #ghost-comment-section.ytd-continuation-item-renderer, ytd-engagement-panel-section-list-renderer ytd-merch-shelf-renderer, ytd-watch-flexy #top-row.ytd-watch-metadata .contribYtLightShapeHost, #secondary:has(#playlist[hidden]) .CentAnni-tabView-tab[data-tab="tab-6"], &.CentAnni-tabView-chapters .ytp-chrome-controls .ytp-chapter-title-content, #description > #description-inner > #ytd-watch-info-text > tp-yt-paper-tooltip, ytd-watch-flexy:not([fullscreen]) #navigation-button.ytd-rich-list-header-renderer, ytd-watch-flexy #expandable-metadata #right-section.ytd-expandable-metadata-renderer, ytd-watch-flexy[theater] ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview], ytd-watch-flexy:not([fullscreen]) #header.style-scope.ytd-engagement-panel-section-list-renderer, #ytd-watch-info-text[view-count-post-number-text][date-text-post-number-text] .CentAnni-info-date, ytd-expandable-video-description-body-renderer > ytd-expander > tp-yt-paper-button#more.ytd-expander, .ytLockupMetadataViewModelMoveLockupOverflowMenuToBottomRight .ytLockupMetadataViewModelMenuButton:after, ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id^="shopping_panel_for_entry_point_"], .ytLockupMetadataViewModelMoveLockupOverflowMenuToBottomRight .ytLockupMetadataViewModelMenuButton:before, #secondary:not(:has(#related.ytd-watch-flexy #contents > :first-child)) .CentAnni-tabView-tab[data-tab="tab-3"], ytd-watch-flexy[theater] #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-clip-create], ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[is-watch] #collapsed-title.ytd-expandable-metadata-renderer, ytd-watch-flexy:not([fullscreen]) ytd-structured-description-content-renderer[engagement-panel] ytd-video-description-header-renderer.ytd-structured-description-content-renderer { display: none; } #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy[target-id=engagement-panel-clip-create] { z-index: 2000; } ytd-engagement-panel-section-list-renderer[target-id=PAyouchat], ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-posts] { background-color: var(--yt-base-background); > #header { display: block !important; } } .vote-choice.ytd-backstage-quiz-renderer:hover { background-color: var(--t416e5931fc464589, var(--tf3fc855af2285f5f, rgba(255, 255, 255, .2))); } ytd-expandable-metadata-renderer[has-video-summary] #expanded-title-subtitle-group.ytd-expandable-metadata-renderer { margin: 10px 0 6px 0; } video-summary-content-view-model.ytd-expandable-metadata-renderer { margin: 0 12px; } ytd-expandable-metadata-renderer[is-expanded] #content.ytd-expandable-metadata-renderer { margin-bottom: 0; } ytd-watch-flexy:not([fullscreen]) #header.ytd-engagement-panel-title-header-renderer { height: var(--ytd-masthead-height, var(--ytd-toolbar-height)); padding: 0 2px 0 12px; } ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[has-video-summary] #expanded-title-subtitle-group.ytd-expandable-metadata-renderer { display: flex !important; } ytd-watch-flexy #expandable-metadata #expanded-subtitle.ytd-expandable-metadata-renderer { display: block !important; pointer-events: auto; } ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer[is-watch] { background: transparent; } ytd-watch-flexy #expandable-metadata #header { pointer-events: none; } #player.skeleton .ytp-chrome-bottom, #player.skeleton .ytp-large-play-button, #player.skeleton .ytp-caption-window-container { opacity: 0; } #owner.ytd-watch-metadata { min-height: 42px; } &.CentAnni-style-compact-layout-video { ytd-watch-flexy { &[default-layout] #title ytd-badge-supported-renderer:not([hidden]) { transform: translate(48px, calc(350% + 12px)); } &[theater]:has(ytd-badge-supported-renderer:not([hidden])) #bottom-row.ytd-watch-metadata { margin-bottom: 20px; } #title ytd-badge-supported-renderer:not([hidden]) .ytBadgeShapeDefault[aria-label^="AI"] { transform: translate(-170%, -130%); } } #owner.ytd-watch-metadata { min-height: 36px; } #avatar.ytd-video-owner-renderer { margin-right: 12px; height: 36px; width: 36px; } #description-inner.ytd-watch-metadata { margin: 0px 12px 0 48px; } } ytd-app #page-manager:has(ytd-watch-flexy[theater][is-extra-wide-video_]:not([hidden])) { height: calc(100dvh - var(--ytd-masthead-height, var(--ytd-toolbar-height))); align-items: center; } &:not([dark]) .CentAnni-tabView-header, ytd-watch-flexy .header.ytd-playlist-panel-renderer, ytd-watch-flexy ytd-engagement-panel-section-list-renderer #content.ytd-engagement-panel-section-list-renderer, ytd-watch-flexy #top-row.ytd-watch-metadata .ytSpecButtonShapeNextSizeM:not(:hover, .ytSpecButtonShapeNextFilled), ytd-watch-flexy #expandable-metadata ytd-expandable-metadata-renderer:not([is-expanded]) #header.ytd-expandable-metadata-renderer:hover { background: transparent; } ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview] { z-index: 100; background: black; max-height: var(--ytd-watch-flexy-chat-max-height) !important; margin-top: calc(var(--ytd-masthead-height, var(--ytd-toolbar-height)) *-1); } ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview] > #header { display: block !important; } ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview] #content ytd-section-list-renderer { padding-left: 10px; } ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview] #content ytd-section-list-renderer > #contents { overflow-y: auto; overflow-x: hidden; } .ytwMarkdownDivHostEnableDefaultStyling strong { color: var(--CentAnniRed); } /* SponsorBlock adjustments */ #sponsorBlockPopupContainer { position: relative; display: flex; z-index: 2000; justify-content: center; background-color: #222222; width: var(--ytd-watch-flexy-sidebar-width); min-width: var(--ytd-watch-flexy-sidebar-min-width); } ytd-masthead.shell { opacity: 0; } /* transcript panel and cinematics mode */ &[dark]:not(.CentAnni-style-no-ambient) { ytd-watch-flexy[cinematics-active][default-layout]:has(.html5-video-player:not(.unstarted-mode, .ended-mode)) { .CentAnni-tabView-header, ytd-transcript-footer-renderer, ytd-transcript-search-box-renderer, ytd-transcript-search-panel-renderer, ytd-transcript-segment-list-renderer, ytd-transcript-section-header-renderer, .playlist-items.ytd-playlist-panel-renderer, .input-container.ytd-transcript-search-box-renderer { background-color: transparent; } ytd-transcript-footer-renderer { border-top: 1px solid rgba(255, 255, 255, .2); } } ytd-app:has(ytd-watch-flexy[cinematics-active][default-layout] .html5-video-player:not(.unstarted-mode, .ended-mode)) { .ytSearchboxComponentInputBoxDark, .ytSearchboxComponentSearchButtonDark { background-color: transparent; border-color: rgba(255, 255, 255, .2); } } ytd-app:has(ytd-watch-flexy[cinematics-active][default-layout] .html5-video-player:not(.unstarted-mode, .ended-mode)) .ytSearchboxComponentInputBoxDark:hover, ytd-app:has(ytd-watch-flexy[cinematics-active][default-layout] .html5-video-player:not(.unstarted-mode, .ended-mode)) .ytSearchboxComponentSearchButtonDark:hover, ytd-watch-flexy[cinematics-active][default-layout]:has(.html5-video-player:not(.unstarted-mode, .ended-mode)) .input-container.ytd-transcript-search-box-renderer:hover { background-color: hsla(0, 0%, 7.1%, .5); } ytd-app:has(ytd-watch-flexy[cinematics-active][default-layout] .html5-video-player:not(.unstarted-mode, .ended-mode)) .ytSearchboxComponentInputBoxDark:focus-within, ytd-watch-flexy[cinematics-active][default-layout]:has(.html5-video-player:not(.unstarted-mode, .ended-mode)) .input-container.ytd-transcript-search-box-renderer:focus-within { background-color: hsl(0, 0%, 7%); } } &:fullscreen ytd-watch-flexy { #related.ytd-watch-flexy, #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer { top: 50px; } } &:not([dark]) ytd-engagement-panel-section-list-renderer[target-id=PAsearch_preview] { background-color: #f2f2f2; } &.CentAnni-tabView-chapters { .ytp-chapter-title-chevron, ytd-watch-flexy .ytp-chapter-title-prefix { display: none; } ytd-watch-flexy .ytp-chapter-container { padding: 0; font-size: inherit; line-height: inherit; max-width: unset !important; } #movie_player .ytp-chapter-container { display: inline-flex; align-items: center; } #movie_player .ytp-chapter-title.ytp-button { backdrop-filter: unset; background: unset; text-shadow: unset; border-radius: unset; padding: unset; } ytd-watch-flexy .sponsorChapterText, ytd-watch-flexy .ytp-chapter-title-content { white-space: normal; font-weight: 500; } #movie_player .sponsorChapterText, #movie_player .ytp-chapter-title-content { overflow: hidden; text-overflow: ellipsis; text-wrap: nowrap; line-height: 45px; padding-right: 5px; } #CentAnni-chapter-title { position: absolute; display: flex; flex-direction: row; z-index: 2017; top: 0; right: 0; width: fit-content; max-width: calc(60% - 32px); font-family: -apple-system, "Roboto", "Arial", sans-serif; font-size: 1.4rem; line-height: 2rem; color: var(--yt-text-primary) !important; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; cursor: default; } #CentAnni-chapter-title span { text-wrap: nowrap; margin-right: .5ch; } #CentAnni-chapter-title .ytp-chapter-container { margin: 0; } #movie_player #CentAnni-chapter-title { position: absolute; display: flex; flex-direction: row; z-index: 2053; height: 56px; opacity: 0; top: unset; bottom: 0; align-items: center; max-width: 50dvw; overflow: hidden; left: 50%; font-size: 2rem; white-space: nowrap; color: ghostwhite !important; text-shadow: 0 0 2px black, 0 0 1.75px black, 0 0 1.5px black, 0 0 1.25px black, 0 0 1px black, 0 0 1px black, 0 0 1px black; } .ytp-time-display { font-size: 14px; font-weight: bold; } .ytp-title-text, .ytp-fullscreen .ytp-time-display { font-size: 18px; white-space: nowrap; word-wrap: normal; } .ytp-autohide .ytp-chrome-bottom #CentAnni-chapter-title { opacity: .8 !important; } #CentAnni-chapter-title .ytp-chapter-container.sponsorblock-chapter-visible { display: block !important; } #CentAnni-chapter-title:has(.ytp-chapter-title-content:empty):not(:has(.sponsorChapterText:not(:empty))) { display: none; } :is( :has(ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-description-chapters]), :has(ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-auto-chapters])) { ytd-watch-flexy #description > #description-inner { width: calc(40% - 32px); } ytd-watch-flexy #description > #description-inner #info-container { height: fit-content; flex-wrap: wrap; } ytd-watch-flexy #description > #description-inner #info:has(> a[href*="/hashtag/"]) span.CentAnni-info-date + span::after { content: ""; display: block; height: 0; margin-bottom: 5px; } ytd-watch-flexy #description > #description-inner #info a.yt-simple-endpoint.bold { display: inline-block; } ytd-watch-flexy #bottom-row.ytd-watch-metadata { height: 50px; } #movie_player #CentAnni-remaining-time-container { left: 25% !important; } } [dir="ltr"] ytd-watch-info-text:not([detailed]) #info-container.ytd-watch-info-text, ytd-watch-info-text:not([detailed]) #info-container.ytd-watch-info-text[dir="ltr"] { -webkit-mask-image: unset; mask-image: unset; } #movie_player .ytp-chrome-bottom:not(:has(#CentAnni-chapter-title)) #CentAnni-remaining-time-container { left: 50% !important; } } &.CentAnni-style-max-panel { tp-yt-iron-dropdown:has([menu-style="multi-page-menu-style-type-notifications"]) { top: 50px !important; } ytd-app:not([fullscreen]) { --ytd-masthead-height-absolute: 50px !important; --ytd-masthead-height: 50px !important; --ytd-toolbar-height: 50px !important; #container.ytd-masthead, #background.ytd-masthead { height: 50px !important; } &:has(ytd-watch-flexy:not([is-vertical-video_])) #masthead-container:not(:has(#masthead[theater])) { width: var(--mastheadWidth, calc(100% - var(--sidebarWidth) - var(--ytd-margin-0x))) !important; } ytd-watch-flexy[default-layout]:not([is-vertical-video_]) { --ytd-watch-flexy-chat-max-height: 100dvh !important; --ytd-watch-flexy-panel-max-height: calc(var(--ytd-watch-flexy-chat-max-height) - var(--CentAnniTabViewHeader)) !important; --ytd-watch-flexy-structured-description-max-height: var(--ytd-watch-flexy-panel-max-height) !important; #secondary { margin-top: calc(var(--ytd-masthead-height, var(--ytd-toolbar-height)) * -1 - var(--ytd-margin-3x)); } } ytd-app yt-page-navigation-progress { background-color: transparent !important; } } &:not(.CentAnni-style-square-search-bar) .ytSearchboxComponentHost { margin: 0 0 0 8px; } #CentAnni-playback-speed-popup { top: var(--CentAnniTabViewHeader); } .button-tooltip { top: calc(anchor(bottom) + 5px); + .button-tooltip-arrow { top: calc(anchor(bottom) + 1px); } } } &.tabView-tab-3 .ytLockupMetadataViewModelMoveLockupOverflowMenuToBottomRight span[aria-label] { + .ytContentMetadataViewModelDelimiter { line-height: 0 !important; font-size: 0 !important; margin: 0 !important; &::before { content: var(--CentAnniViews); font-family: Roboto, "Arial", sans-serif; font-size: 1.2rem; line-height: 1.8rem; font-weight: 400; margin-left: .5ch; white-space: nowrap; vertical-align: middle; color: var(--yt-text-secondary); } &::after { content: "•"; font-family: Roboto, "Arial", sans-serif; font-size: 1.2rem; line-height: 1.8rem; font-weight: 400; margin: 0 4px; vertical-align: middle; color: var(--yt-text-secondary); } + .ytContentMetadataViewModelMetadataText { line-height: 0 !important; font-size: 0 !important; &::after { content: attr(aria-label); font-family: Roboto, "Arial", sans-serif; font-size: 1.2rem; line-height: 1.8rem; font-weight: 400; white-space: nowrap; vertical-align: middle; text-overflow: ellipsis; color: var(--yt-text-secondary); } } } } } :is(.CentAnni-video-tabView, .CentAnni-style-compact-layout-video) { #fixed-side-menu, #columns.ytd-watch-flexy::after { display: none !important; } ytd-watch-flexy { --ytd-watch-flexy-side-menu-margin: 0 !important; --ytd-watch-flexy-fixed-side-menu-width: 0 !important; } } .CentAnni-style-transcript { .ytwTranscriptSegmentViewModelHost { display: flex; flex-direction: row; font-weight: normal; line-height: 2.5rem; font-size: 1.7rem; } .ytwTranscriptSegmentViewModelTimestamp { background-color: unset !important; align-items: center; display: flex; padding: 0; } .ytwTranscriptSegmentViewModelHostActive, ytd-transcript-segment-renderer.active .segment.ytd-transcript-segment-renderer .segment-text.ytd-transcript-segment-renderer { font-size: 2.5rem; font-weight: 500; line-height: 1; color: white; opacity: 1; margin-top: -0.15em; } ytd-transcript-segment-renderer.active .segment.ytd-transcript-segment-renderer .segment-text.ytd-transcript-segment-renderer { font-size: 3.35rem; padding: 12px 16px 12px 4px; } .segment-text.ytd-transcript-segment-renderer { font-family: -apple-system; opacity: .9; } .segment.ytd-transcript-segment-renderer { align-items: center; } .segment.ytd-transcript-segment-renderer:hover .segment-text.ytd-transcript-segment-renderer { font-weight: 400; } ytd-watch-flexy [target-id="engagement-panel-searchable-transcript"] span.bold.style-scope.yt-formatted-string { color: var(--CentAnniRed); } &[dark] { ytd-watch-flexy[fullscreen] ytd-transcript-footer-renderer, ytd-watch-flexy[fullscreen] ytd-transcript-segment-list-renderer { background-color: black; } } &:not([dark]) { ytd-transcript-segment-renderer.active .segment.ytd-transcript-segment-renderer .segment-text.ytd-transcript-segment-renderer { color: black; } } } .CentAnni-style-no-transition-animation { ytd-watch-flexy[view-transition-enabled] #below.ytd-watch-flexy, ytd-watch-flexy[view-transition-enabled] #secondary.ytd-watch-flexy, ytd-watch-flexy[view-transition-enabled][default-layout] #player-container.ytd-watch-flexy, ytd-watch-flexy[view-transition-enabled][theater] #player-full-bleed-container.ytd-watch-flexy { view-transition-name: none !important; } &:has(ytd-watch-flexy)::view-transition-old(*), &:has(ytd-watch-flexy)::view-transition-new(*) { animation: none !important; } } .CentAnni-style-hide-yt-settings .ytp-settings-menu, .CentAnni-style-hide-yt-settings .ytp-overflow-panel { opacity: 0 !important; pointer-events: none !important; } .CentAnni-style-hide-default-sidebar { #guide-button.ytd-masthead, ytd-masthead.shell #menu-icon, ytd-mini-guide-renderer.ytd-app, #home-page-skeleton #guide-skeleton, #contentContainer.tp-yt-app-drawer[swipe-open]::after { display: none !important; } ytd-app[mini-guide-visible] ytd-page-manager.ytd-app { margin-left: 0 !important; } #contents.ytd-rich-grid-renderer { justify-content: center !important; } 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 { left: 0; } } .CentAnni-style-home-disable-hover { .ytSpecTouchFeedbackShapeHovered { display: none; } .ytLockupMetadataViewModelTitle { color: var(--yt-text-primary) !important; } .ytLockupMetadataViewModelMetadata { color: var(--yt-text-secondary) !important; } } #video-title, .ytLockupMetadataViewModelTitle, ytd-watch-metadata h1.ytd-watch-metadata { text-transform: var(--textTransform) !important; } .CentAnni-style-full-title { .ytLockupMetadataViewModelTitle, #video-title.ytd-rich-grid-media, .yt-lockup-metadata-view-model__title { white-space: normal !important; text-overflow: unset !important; overflow: unset !important; display: inline-block !important; padding-right: 16px; } &.CentAnni-style-compact-layout ytd-browse:not([page-subtype=history]) .ytLockupMetadataViewModelTitle { padding-right: 4px; } #video-title { max-height: unset !important; -webkit-line-clamp: unset !important; line-clamp: unset !important; } } ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive)), yt-thumbnail-view-model:has(yt-thumbnail-overlay-progress-bar-view-model):not(:has(.ytBadgeShapeThumbnailLive)), ytd-rich-item-renderer ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive)), ytd-compact-video-renderer ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive)) { opacity: var(--watchedOpacity); } ytd-search ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer) { opacity: .8; } ytd-browse[page-subtype="history"][role="main"] { ytd-thumbnail:has(ytd-thumbnail-overlay-resume-playback-renderer), yt-thumbnail-view-model:has(yt-thumbnail-overlay-progress-bar-view-model) { opacity: 1; } tp-yt-paper-tooltip .tp-yt-paper-tooltip[style-target="tooltip"]:not(.hidden) { text-wrap: nowrap; } } ytd-shorts tp-yt-paper-tooltip .tp-yt-paper-tooltip[style-target="tooltip"]:not(.hidden) { text-wrap: nowrap; } .CentAnni-hide-watched-wl { #CentAnni-toggle-watched-btn { background: #0f0f0f; } &:hover { background: #2d2c2c; } } .CentAnni-style-watched-later-header { ytd-browse[page-subtype="playlist"][role="main"] ytd-item-section-renderer > #header { display: none; } .ytChipBarViewModelChipWrapper:last-child, chip-bar-view-model.ytd-item-section-renderer { margin: 0 !important; } #header.ytd-item-section-renderer .ytChipShapeEndIconPadding { width: 113px; overflow: hidden; } #header.ytd-item-section-renderer .ytChipShapeEndIconPadding > div { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } ytd-two-column-browse-results-renderer.ytd-browse[page-subtype="playlist"][role="main"] { margin-top: -8px !important; } ytd-playlist-video-list-renderer #contents { margin-top: -13px; } } #CentAnni-toggle-watched-btn { width: 160px; align-self: end; margin-top: -36px; } .CentAnni-style-pure-bg { &.yt-watch-later { #tabs-inner-container.ytd-tabbed-page-header, #header.ytd-item-section-renderer { background-color: unset; } } &.is-watch-page { .ytp-popup, .ytp-volume-area, .ytp-next-button, .ytp-prev-button, .ytp-play-button, .ytp-time-wrapper, .ytp-right-controls, .ytp-offline-slate-bar, .ytp-tooltip-bottom-text, .ytp-tooltip .ytp-tooltip-bottom-text { background: rgba(28, 28, 28, .9) !important; } .ytp-tooltip .ytp-tooltip-bottom-text { outline: 1px solid rgba(107, 107, 107, .5); } } #tabs-divider.ytd-tabbed-page-header { border: none; } .ytPopoverComponentHostSeeThrough.ytTooltipContainerDefaultTooltipContent { border-radius: 0; color: #fff; background-color: #000000; } } .CentAnni-style-remove-scrubber { .ytp-scrubber-container, .ytProgressBarPlayheadProgressBarPlayheadDot { display: none; } } .CentAnni-style-play-progress-color { .ytp-play-progress, .ytp-swatch-background-color, #progress.yt-page-navigation-progress, .ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment { background: var(--progressBarColor) !important; } } .CentAnni-style-hide-endscreen { .ytp-fullscreen-grid, ytd-watch-flexy[fullscreen] .ytp-gradient-bottom, .ytp-delhi-modern.ytp-fullscreen-grid-active .ytp-gradient-bottom, ytd-watch-flexy .html5-video-player .html5-endscreen.videowall-endscreen, ytd-watch-flexy .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) button { display: none !important; } ytd-watch-flexy .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) { display: block !important; cursor: default !important; } ytd-watch-flexy .ended-mode .ytp-cued-thumbnail-overlay:not([aria-hidden="true"]) .ytp-cued-thumbnail-overlay-image { display: block !important; background-image: var(--video-url) !important; } .ytp-delhi-modern.ytp-full-bleed-player.ytp-fullscreen-grid-active:not(.html5-video-player.ended-mode) .ytp-chrome-bottom { bottom: 0 !important; opacity: 1 !important; } .ytp-fullscreen-grid-active .ytp-overlays-container { display: flex !important; opacity: 1 !important; } ytd-watch-flexy[fullscreen] div#movie_player { --ytp-grid-scroll-percentage: 0 !important; } } .CentAnni-style-gradient-bottom { .ytp-gradient-bottom { padding-top: 50px !important; height: 48px !important; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) !important; } } .CentAnni-style-video-row { ytd-rich-grid-renderer { --ytd-rich-grid-items-per-row: var(--itemsPerRow) !important; } } .CentAnni-style-sidebar-width { ytd-watch-flexy[is-two-columns_] { --ytd-watch-flexy-sidebar-width: var(--sidebarWidth) !important; --ytd-watch-flexy-sidebar-min-width: unset !important; } } .CentAnni-style-search-position { #center.ytd-masthead { transform: translateX(var(--searchbarPosition)); } } .CentAnni-style-sort-notifications { ytd-app > ytd-popup-container tp-yt-iron-dropdown h2.yt-multi-page-menu-section-renderer { display: none; } ytd-app > ytd-popup-container tp-yt-iron-dropdown #sections.ytd-multi-page-menu-renderer > .ytd-multi-page-menu-renderer:not(:last-child) { border: none; } ytd-menu-renderer.ytd-notification-renderer:hover { background-color: var(--yt-spec-background); } ytd-notification-renderer.unread { background-color: rgba(62, 166, 255, .2); } } .CentAnni-style-hide-read-notifications { #items ytd-continuation-item-renderer, ytd-notification-renderer:not(.unread) { display: none; } ytd-notification-renderer.unread { background-color: unset; } } .CentAnni-style-hide-own-avatar { #avatar-btn, #masthead-skeleton-icons :last-child { display: none !important; } #masthead-skeleton-icons :nth-child(2), #end ytd-notification-topbar-button-renderer { margin-right: 0 !important; } } .CentAnni-style-hide-brand-text { #start #logo-icon [id^="youtube-paths_yt"], #start #country-code.ytd-topbar-logo-renderer, #start ytd-topbar-logo-renderer > #logo > ytd-yoodle-renderer > picture { display: none; } ytd-masthead #masthead-logo, #masthead-container ytd-topbar-logo-renderer { margin-right: 0 !important; width: 30px !important; overflow: hidden; } #masthead-container #logo-icon { padding: 0; } #start ytd-topbar-logo-renderer > #logo > ytd-yoodle-renderer > ytd-logo { display: block !important; } #start yt-icon.ytd-logo { padding: 0; } } .CentAnni-style-visible-country-code { #country-code.ytd-topbar-logo-renderer { position: absolute; color: var(--countryCodeColor); margin: -8px 0 0 27px; display: block !important; } } .CentAnni-style-square-search-bar { #center.ytd-masthead { flex: 0 1 38.76dvw; margin: 0 8px; } .YtSearchboxComponentInputBox, .ytSearchboxComponentInputBox { border: 1px solid hsl(0, 0%, 18.82%); border-radius: 0; margin-left: 0; padding: 0 4px 0 10px; } .YtSearchboxComponentSuggestionsContainer, .ytSearchboxComponentSuggestionsContainer { border-radius: 0 0 5px 5px; } .ytSearchboxComponentSuggestionsContainer { max-width: 500px; } .YtSearchboxComponentSearchButton, .YtSearchboxComponentSearchButtonDark, .ytSearchboxComponentSearchButton, .ytSearchboxComponentSearchButtonDark { display: none; } .YtSearchboxComponentHost, .ytSearchboxComponentHost { margin: 0; padding: 0; } .ytSearchboxComponentDesktop .ytSearchboxComponentClearButton { border-radius: 0; height: 38px; width: 38px; margin-right: 6px; } #end.ytd-masthead { min-width: auto; } .ytSearchboxComponentInnerSearchIcon { display: none; } .ytSearchboxComponentSearchForm { padding: 0 15px 0 0; } .ytSuggestionComponentRemoveLinkClearButton:hover { background-color: rgba(255, 255, 255, .2); } .ytSuggestionComponentLeftContainer { max-width: 347px; } } ytd-app #masthead-container yt-searchbox .ytSuggestionComponentSuggestion, ytd-app #masthead-container yt-searchbox .ytSuggestionComponentSuggestion > .ytSuggestionComponentText { cursor: pointer; } ytd-browse[page-subtype="home"][role="main"] #contents.ytd-rich-grid-renderer { justify-content: center; } .CentAnni-style-square-design { .CentAnni-tabView, ytd-post-renderer, .ytp-settings-menu, .ytp-button::before, ytd-live-chat-frame, #card.ytd-miniplayer, .smartimation__border, .ytp-progress-bar-end, ytd-thumbnail::before, .ytp-offline-slate-bar, .ytSubThreadConnection, .snackbarViewModelHost, .ytp-progress-bar-start, .ytp-tooltip-bottom-text, #related.ytd-watch-flexy, .ytp-offline-slate-button, .ytp-tooltip-text-wrapper, .ytThumbnailViewModelHost, .ytSearchInputViewModelHost, ytd-donation-shelf-renderer, ytd-playlist-video-renderer, .ytOfficialCardViewModelHost, .chatInputViewModelChatInput, .ytImageBannerViewModelInset, #interaction .yt-interaction, .ytListItemViewModelContainer, ytd-thumbnail a.ytd-thumbnail, yt-interaction .yt-interaction, #dismissed.ytd-rich-grid-media, .ytdGhostGridViewModelTextShell, #home-page-skeleton .text-shell, .ytdGhostGridViewModelThumbnail, .yt-thumbnail-view-model--large, ytd-info-panel-content-renderer, .CentAnni-playback-speed-display, .snackbarViewModelImageContainer, ytd-expandable-metadata-renderer, .yt-thumbnail-view-model--medium, ytd-author-comment-badge-renderer, .ytp-modern-videowall-still-image, .ytp-modern-videowall-still:hover, #player.skeleton.flexy #player-api, .text-shell.ytd-ghost-grid-renderer, #home-page-skeleton .rich-thumbnail, .yt-list-item-view-model__container, .ytCollectionsStackCollectionStack2, .badge.ytd-badge-supported-renderer, .yt-spec-button-shape-next--size-xs, .ytVideoAttributeViewModelImageLarge, .ytVideoAttributeViewModelHeroSection, .ytNotificationMultiActionRendererHost, .animated-action__background-container, #container.ytd-playlist-panel-renderer, .rich-thumbnail.ytd-ghost-grid-renderer, .ytp-player-minimized .html5-main-video, .ytp-popup.ytp-delhi-modern-contextmenu, .ytThumbnailOverlayProgressBarHostLabel, #dismissible.ytd-inline-survey-renderer, yt-interaction.circular .yt-interaction, .ytCollectionsStackCollectionStack1Large, .ytProgressBarLineProgressBarLineRounded, .ytCollectionsStackCollectionStack1Small, .ytp-tooltip.ytp-text-detail.ytp-preview, .ytCollectionsStackCollectionStack1Medium, .collections-stack-wiz__collection-stack2, yt-img-shadow.ytd-backstage-image-renderer, ytd-watch-flexy #ytd-player.ytd-watch-flexy, .ytp-player-minimized .ytp-miniplayer-scrim, .player-container-background.ytd-watch-flexy, .reel-video-in-sequence-thumbnail.ytd-shorts, .ytSuggestionComponentVisualSuggestThumbnail, .yt-spec-button-shape-next--icon-only-default, #thumbnail-container.ytd-media-lockup-renderer, ytd-live-chat-frame iframe.ytd-live-chat-frame, ytd-watch-flexy[theater] .CentAnni-tabView-tab, .thumbnail-container.ytd-notification-renderer, tp-yt-paper-item.ytd-menu-service-item-renderer, #thumbnail.ytd-macro-markers-list-item-renderer, .ytContentPreviewImageViewModelLargeRoundedImage, .collections-stack-wiz__collection-stack1--large, tp-yt-paper-toast.yt-notification-action-renderer, .collections-stack-wiz__collection-stack1--medium, #product-image.ytd-vertical-product-card-renderer, tp-yt-paper-item.ytd-menu-navigation-item-renderer, .shortsLockupViewModelHostThumbnailContainerRounded, .metadata-container.ytd-reel-player-overlay-renderer, ytd-shorts .player-container.ytd-reel-video-renderer, ytd-compact-link-renderer.ytd-settings-sidebar-renderer, .ytThumbnailOverlayProgressBarHostProgressBarLeftSegment, .immersive-header-container.ytd-playlist-header-renderer, .ytp-tooltip.ytp-text-detail.ytp-preview .ytp-tooltip-bg, .shortsLockupViewModelHostThumbnailParentContainerRounded, .ytThumbnailOverlayProgressBarHostProgressBarRightSegment, ytd-shorts[enable-anchored-panel] .anchored-panel.ytd-shorts, #thumbnail:not(.ytd-video-description-infocards-section-renderer), .ytp-delhi-modern .ytp-settings-menu .ytp-menuitem > *:last-child, .ytp-delhi-modern .ytp-settings-menu .ytp-menuitem > *:first-child, .html5-video-player:not(.ytp-touch-mode) ::-webkit-scrollbar-thumb, ytd-backstage-quiz-renderer .vote-choice.ytd-backstage-quiz-renderer, ytd-backstage-quiz-renderer .choice-info.ytd-backstage-quiz-renderer, .ytSpecTouchFeedbackShapeTriggerEvents .ytSpecTouchFeedbackShapeFill, .ytSpecTouchFeedbackShapeTriggerEvents .ytSpecTouchFeedbackShapeStroke, #playlist-thumbnail.ytd-structured-description-playlist-lockup-renderer, .ytPageHeaderViewModelDisplayAsSidebar .ytPageHeaderViewModelBackground, ytd-backstage-quiz-renderer .explanation-box.ytd-backstage-quiz-renderer, .ytSpecTouchFeedbackShapeTriggerEvents .ytSpecTouchFeedbackShapeHoverEffect, ytd-engagement-panel-section-list-renderer:not([live-chat-engagement-panel]), .yt-video-attribute-view-model--image-small .yt-video-attribute-view-model__hero-section, ytd-expandable-metadata-renderer:not([is-expanded]) #header.ytd-expandable-metadata-renderer:hover, ytd-watch-flexy[flexy][js-panel-height_]:not([fixed-panels]) #chat.ytd-watch-flexy:not([collapsed]) { border-radius: 0 !important; } .ytBadgeShapeHost, .ytBadgeShapeThumbnailBadge, .ytSpecButtonShapeNextSizeS, .yt-badge-shape--thumbnail-badge, yt-img-shadow.ytd-game-details-renderer, .tp-yt-paper-tooltip[style-target=tooltip], .image-wrapper.ytd-hero-playlist-thumbnail-renderer, .yt-content-preview-image-view-model-wiz--large-rounded-image, .yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section { border-radius: 1px !important; } .ytChipShapeChip, yt-dropdown-menu, tp-yt-paper-dialog, .CentAnni-tabView-tab, #menu.yt-dropdown-menu, ytd-menu-popup-renderer, ytd-guide-entry-renderer, ytd-rich-metadata-renderer, .ytSheetViewModelContextual, .ytSpecButtonShapeNextSizeM, yt-chip-cloud-chip-renderer, ytd-multi-page-menu-renderer, .ytSpecButtonShapeNextSizeXs, #description.ytd-watch-metadata, .badge-shape-wiz--thumbnail-badge, .yt-spec-button-shape-next--size-s, .yt-spec-button-shape-next--size-m, .yt-sheet-view-model-wiz--contextual, .ytAnimatedActionBackgroundContainer, .ytVideoMetadataCarouselViewModelHost, .ytdTalkToRecsFlowRendererFlowContent, #header .ytSpecTouchFeedbackShapeFill, #header .ytSpecTouchFeedbackShapeStroke, yt-interaction.ytd-guide-entry-renderer, .ytSuggestionComponentRoundedSuggestion, .dropdown-content.tp-yt-paper-menu-button, #chip-container.yt-chip-cloud-chip-renderer, ytd-backstage-post-renderer[uses-full-lockup], #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer, ytd-backstage-post-dialog-renderer[is-creation-dialog], .ytp-delhi-modern .ytp-tooltip .ytp-tooltip-bottom-text, #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:hover, #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:focus, #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer:active, .yt-spec-touch-feedback-shape--down .yt-spec-touch-feedback-shape__fill, .yt-spec-touch-feedback-shape--down .yt-spec-touch-feedback-shape__stroke, ytd-channel-video-player-renderer #player.ytd-channel-video-player-renderer, .yt-page-header-view-model--display-as-sidebar .yt-page-header-view-model__page-header-background, .page-header-view-model-wiz--display-as-sidebar .page-header-view-model-wiz__page-header-background, ytd-menu-service-item-renderer[use-list-item-styles] tp-yt-paper-item.ytd-menu-service-item-renderer, ytd-menu-service-item-renderer[use-list-item-styles] tp-yt-paper-item.ytd-menu-service-item-renderer:focus { border-radius: 2px !important; } #masthead-container yt-interaction.circular .yt-interaction, #voice-search-button.ytd-masthead .ytSpecButtonShapeNextSizeM, ytd-rich-item-renderer yt-interaction.circular .yt-interaction { border-radius: 50% !important; } tp-yt-paper-item.ytd-guide-entry-renderer, ytd-compact-link-renderer[compact-link-style="compact-link-style-type-settings-sidebar"] tp-yt-paper-item.ytd-compact-link-renderer { --paper-item-focused-before-border-radius: 0; } .ytdMiniplayerComponentContent { border-radius: 0 0 12px 0; } &.CentAnni-style-color-code-videos { yt-chip-cloud-chip-renderer { border-radius: 2px; } .CentAnni-style-live-video, .CentAnni-style-upcoming-video, .CentAnni-style-newly-video, .CentAnni-style-recent-video, .CentAnni-style-lately-video, .CentAnni-style-latterly-video, .CentAnni-style-old-video { border-radius: 0; } } ytd-browse[page-subtype="subscriptions"][role="main"] { .CentAnni-style-last-seen { border-radius: 0; } } ytd-browse[page-subtype="channels"][role="main"] { .yt-spec-button-shape-next--size-m { border-radius: 2px; } .yt-thumbnail-view-model--medium, .yt-image-banner-view-model-wiz--inset, .collections-stack-wiz__collection-stack2, #chip-container.yt-chip-cloud-chip-renderer, .collections-stack-wiz__collection-stack1--medium { border-radius: 0 !important; } #icon-button #interaction .yt-interaction { border-radius: 50% !important; } } .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--segmented-start { border-radius: 2px 0 0 3px; } .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--segmented-end { border-radius: 0 3px 3px 0; } .desktopShortsPlayerControlsWizHost { left: 0; right: 0; } ytd-search yt-official-card-view-model horizontal-shelf-view-model .ytwHorizontalShelfViewModelLeftArrow .yt-spec-button-shape-next--size-m, ytd-search yt-official-card-view-model horizontal-shelf-view-model .ytwHorizontalShelfViewModelRightArrow .yt-spec-button-shape-next--size-m { border-radius: 18px; } &.is-watch-page { ytd-expandable-metadata-renderer:not([is-expanded]) { --yt-img-border-radius: 0px; border-radius: 0px; } .ytp-delhi-modern .ytp-right-controls-left > .ytp-button, .ytp-delhi-modern .ytp-right-controls-right > .ytp-button { border-radius: 0 !important; overflow: hidden; } .ytp-delhi-modern .ytp-right-controls-left .ytp-autonav-toggle, .ytp-delhi-modern .ytp-right-controls-left .ytp-autonav-toggle::before { border-top-left-radius: 40px !important; border-bottom-left-radius: 40px !important; } .ytp-delhi-modern .ytp-right-controls-right > .ytp-button:last-of-type, .ytp-delhi-modern .ytp-right-controls-right > .ytp-button:last-of-type::before { border-top-right-radius: 40px !important; border-bottom-right-radius: 40px !important; } .ytwYouChatChipsDataChip, .ytChatUserTurnViewModelTextContainer { border-radius: 0 7px 0 0; } } /* chamfered corners */ .ytp-offline-slate .ytp-offline-slate-messages { min-width: unset; } .ytp-offline-slate .ytp-offline-slate-bar { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%); } .snackbarViewModelHost, .ytp-offline-slate-button, .toast-button.yt-notification-action-renderer, tp-yt-paper-toast.yt-notification-action-renderer { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%); } } .CentAnni-style-square-avatars { .yt-spec-avatar-shape__image, .yt-spec-avatar-shape--square, #avatar.ytd-video-owner-renderer, yt-img-shadow.ytd-video-renderer, yt-img-shadow.ytd-channel-renderer, .thumbnail.ytd-notification-renderer, ytd-menu-renderer.ytd-rich-grid-media, yt-img-shadow.ytd-guide-entry-renderer, #avatar.ytd-active-account-header-renderer, #avatar.ytd-watch-card-rich-header-renderer, yt-img-shadow.ytd-topbar-menu-button-renderer, #author-thumbnail.ytd-comment-simplebox-renderer, ytd-rich-item-renderer yt-interaction.circular .yt-interaction, .yt-spec-avatar-shape--cairo-refresh.yt-spec-avatar-shape--live-ring::after, #author-thumbnail.ytd-comment-view-model yt-img-shadow.ytd-comment-view-model, #author-thumbnail.ytd-backstage-post-renderer yt-img-shadow.ytd-backstage-post-renderer, ytd-comment-replies-renderer #creator-thumbnail.ytd-comment-replies-renderer yt-img-shadow.ytd-comment-replies-renderer { border-radius: 0 !important; } } #CentAnni-channel-btn { display: flex; align-items: center; gap: 6px; } #CentAnni-playlist-direction-container { display: inline-flex; align-items: center; margin-left: 8px; > span { margin-right: 4px; color: var(--yt-text-primary); font-family: "YouTube Sans", "Roboto", sans-serif; font-size: 1.7rem; line-height: 1.5rem; font-weight: 600; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; cursor: default; } } #end-actions.ytd-playlist-panel-renderer { margin-right: 24px; } .CentAnni-yt-shorts-control-btn, .CentAnni-playlist-direction-btn { color: var(--yt-text-secondary) !important; } .CentAnni-yt-shorts-control-btn.active, .CentAnni-playlist-direction-btn.active { color: var(--yt-text-primary) !important; } .CentAnni-yt-shorts-control-btn.active { border: 1px solid color-mix(in srgb, var(--yt-text-primary) 20%, transparent) !important; } #CentAnni-yt-shorts-controls { display: flex; flex-direction: column; gap: 16px; margin: auto 0; } .CentAnni-yt-shorts-control-btn { font-size: 1.5rem !important; line-height: 2rem !important; font-weight: 400 !important; overflow: hidden; -webkit-line-clamp: 1; line-clamp: 1; display: flex; -webkit-box-orient: vertical; text-overflow: ellipsis; white-space: normal; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } ytd-shorts reel-action-bar-view-model:has(#CentAnni-yt-shorts-controls) { height: 100% } .CentAnni-style-compact-layout { ytd-rich-section-renderer:has(.grid-subheader.ytd-shelf-renderer) { display: none; } #masthead-skeleton-icons .masthead-skeleton-icon:last-child { width: 32px; } #page-manager.ytd-app { --ytd-toolbar-offset: 0 !important; } ytd-browse[page-subtype="hashtag-landing-page"][role="main"] { transform: translateY(0px); } .ytSpecTouchFeedbackShapeHovered { margin: 0 !important; } ytd-browse[page-subtype="home"][role="main"], ytd-browse[page-subtype="channels"][role="main"], ytd-browse[page-subtype="subscriptions"][role="main"] { ytd-menu-renderer .ytd-menu-renderer { width: 36px; height: 36px; } button.yt-icon-button > yt-icon { transform: rotate(90deg); } #contents.ytd-rich-grid-renderer { width: 100%; max-width: 100%; padding-top: 0; display: flex; flex-wrap: wrap; column-gap: 5px; row-gap: 10px; } .style-scope.ytd-two-column-browse-results-renderer { --ytd-rich-grid-item-max-width: 100dvw; --ytd-rich-grid-item-min-width: 310px; --ytd-rich-grid-item-margin: 0px !important; --ytd-rich-grid-content-offset-top: 56px; } ytd-rich-item-renderer[rendered-from-rich-grid] { margin: 0 !important; } #meta.ytd-rich-grid-media, .yt-lockup-metadata-view-model__title { overflow-x: hidden; padding-right: 6px; } #avatar-container.ytd-rich-grid-media { margin: 7px 6px 50px 6px; } h3.ytd-rich-grid-media { margin: 7px 0 4px 0; } .yt-spec-avatar-shape--cairo-refresh.yt-spec-avatar-shape--live-ring::after { inset: -2px; } .ytSpecButtonShapeNextMono.ytSpecButtonShapeNextText:hover, .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--text:hover { border-radius: 50% !important; } .yt-lockup-metadata-view-model { margin-bottom: 5px; min-height: 77px; } ytd-rich-item-renderer { --ytd-rich-grid-gutter-margin: 5px !important; --ytd-border-space: 0px; } ytd-rich-item-renderer[rendered-from-rich-grid] { --ytd-rich-item-row-usable-width: calc(100% - var(--ytd-rich-grid-gutter-margin) * var(--ytd-rich-grid-items-per-row) + var(--ytd-rich-grid-gutter-margin) - var(--ytd-border-space)); width: calc(var(--ytd-rich-item-row-usable-width)/var(--ytd-rich-grid-items-per-row)); } #reload-content.ytd-rich-grid-renderer { margin-top: 0; padding: 0; } .ytLockupViewModelVertical.ytLockupViewModelRichGridLegacyMargin .ytLockupViewModelContentImage { padding-bottom: 8px; } .ytLockupMetadataViewModelHost { min-height: 80px; } } ytd-browse[page-subtype="home"][role="main"] { .ytLockupMetadataViewModelMenuButton, ytd-menu-renderer.ytd-rich-grid-media, .yt-lockup-metadata-view-model__menu-button, .yt-lockup-metadata-view-model-wiz__menu-button { position: absolute; height: 36px; width: 36px; top: 40px; right: auto; left: 4px; align-items: center; background-color: rgba(255, 255, 255, .1); border-radius: 50%; } .ytLockupMetadataViewModelHost:has(.ytSpecAvatarShapeLiveBadge) .ytLockupMetadataViewModelMenuButton { top: 42px; } .title-badge.ytd-rich-grid-media, .video-badge.ytd-rich-grid-media { position: absolute; bottom: 0; right: 0; margin: 8px; display: flex; flex-direction: row; } ytd-rich-item-renderer[rendered-from-rich-grid] { margin: 0 !important; } #contents.ytd-rich-grid-renderer { padding-top: 2px; column-gap: 12px; row-gap: 12px; } #home-page-skeleton .video-details { padding-bottom: 12px; } #home-page-skeleton .rich-grid-media-skeleton { margin: 0 6px 0 6px; } #home-container-media { margin: 12px auto 0; padding: 0; } #home-container-skeleton #home-chips { padding: 0; } ytd-rich-item-renderer { --ytd-rich-grid-gutter-margin: 12px !important; --ytd-border-space: 4px; } .ytLockupMetadataViewModelAvatar, .yt-lockup-metadata-view-model__avatar, .yt-lockup-metadata-view-model-wiz__avatar { margin: 0 8px 0 4px; } .yt-lockup-metadata-view-model__menu-button, .yt-lockup-metadata-view-model-wiz__menu-button { top: 41px; } yt-lockup-metadata-view-model:has(a[href*="/playlist?list="]) .yt-lockup-metadata-view-model__menu-button, yt-lockup-metadata-view-model:has(a[href*="/playlist?list="]) .yt-lockup-metadata-view-model-wiz__menu-button { top: 70px; } .ytLockupAttachmentsViewModelAttachment { margin-top: 0; } .yt-lockup-view-model-wiz--vertical.yt-lockup-view-model-wiz--rich-grid-legacy-margin .yt-lockup-view-model-wiz__content-image { padding-bottom: 6px; } #content.ytd-rich-item-renderer > .lockup.ytd-rich-item-renderer { height: 100%; } .yt-lockup-view-model-wiz__metadata { margin-bottom: 3px; } .yt-content-metadata-view-model__badge { margin: 0; } .yt-content-metadata-view-model__metadata-row--metadata-row-wrap { margin: 0 5px 0 0; justify-content: end; } .ytContentMetadataViewModelMetadataRowMetadataRowWrap { justify-content: flex-end; } .yt-lockup-view-model__endorsement { top: unset; color: white; margin: 8px; padding: 5px; background: rgba(0, 0, 0, .8); backdrop-filter: blur(8px); border-radius: 2px; z-index: 2025; } } ytd-browse[page-subtype="channels"][role="main"] { ytd-tabbed-page-header #page-header-banner.ytd-tabbed-page-header, ytd-tabbed-page-header.grid-5-columns #page-header.ytd-tabbed-page-header { padding: 0 !important; } ytd-tabbed-page-header[has-banner] #page-header.ytd-tabbed-page-header { padding-top: 0; padding-bottom: 0; } #page-header.ytd-tabbed-page-header { margin: 12px 12px 0; } .yt-page-header-view-model__page-header-headline { margin: 4px 0 0 24px; } ytd-two-column-browse-results-renderer.grid-5-columns, .grid-5-columns.ytd-two-column-browse-results-renderer { width: 100% !important; } ytd-rich-grid-renderer:not([is-default-grid]) #header.ytd-rich-grid-renderer { transform: translateY(-60px); z-index: 2000; width: max-content; margin-left: auto; } ytd-two-column-browse-results-renderer.grid-5-columns ytd-rich-grid-renderer:not([is-default-grid]) #header { margin-right: 110px; } ytd-two-column-browse-results-renderer.grid-6-columns ytd-rich-grid-renderer:not([is-default-grid]) #header { margin-right: 5px; } ytd-two-column-browse-results-renderer ytd-rich-grid-renderer:not([is-default-grid]) #header { margin-bottom: -48px; } ytd-rich-grid-renderer ytd-feed-filter-chip-bar-renderer[component-style="FEED_FILTER_CHIP_BAR_STYLE_TYPE_CHANNEL_PAGE_GRID"] { margin-bottom: -32px; margin-top: 0; } .page-header-view-model-wiz__page-header-headline-image { margin-left: 110px; } .ytLockupMetadataViewModelMenuButton, ytd-menu-renderer.ytd-rich-grid-media { position: absolute; height: 36px; width: 36px; top: initial; bottom: 0; right: 0; align-items: center; border-radius: 50%; } ytd-expandable-tab-renderer button:hover, ytd-menu-renderer.ytd-rich-grid-media button:hover { background-color: rgba(255, 255, 255, .1); border-radius: 50%; } yt-icon-button.ytd-expandable-tab-renderer { margin-right: 8px; } ytd-expandable-tab-renderer yt-interaction .yt-interaction { border-radius: 50% !important; } .yt-tab-group-shape-wiz__slider, .yt-tab-shape-wiz__tab-bar { display: none; } .yt-tab-shape-wiz__tab--tab-selected, .yt-tab-shape-wiz__tab:hover { color: white; } .style-scope.ytd-two-column-browse-results-renderer { --ytd-rich-grid-item-margin: .5% !important; } ytd-backstage-items { display: block; max-width: 100%; } #header-container { width: 80dvw; align-self: center; } #items.ytd-grid-renderer { justify-content: center; } .tabGroupShapeSlider, .yt-tab-shape__tab-bar { bottom: 12px; } #contentContainer { padding-top: 0 !important; } tp-yt-app-header { position: static !important; transform: none !important; transition: none !important; } tp-yt-app-header[fixed] { position: static !important; transform: none !important; transition: none !important; } tp-yt-app-header #page-header { position: static !important; transform: none !important; } ytd-two-column-browse-results-renderer.grid:not(.grid-disabled) { max-width: 100%; } #right-arrow.yt-horizontal-list-renderer { right: 30px; } #left-arrow.yt-horizontal-list-renderer { left: 30px; } .ytTabShapeTabBar { bottom: 12px; } .ytLockupViewModelMetadata { margin-left: 5px; } } ytd-browse[page-subtype="subscriptions"][role="main"] { .ytLockupMetadataViewModelMenuButton, ytd-menu-renderer.ytd-rich-grid-media, .yt-lockup-metadata-view-model__menu-button { position: absolute; height: 36px; width: 36px; top: 40px; right: auto; left: 4px; align-items: center; background-color: rgba(255, 255, 255, .1); border-radius: 50%; } .ytLockupMetadataViewModelHost:has(.ytSpecAvatarShapeLiveBadge) .ytLockupMetadataViewModelMenuButton { top: 42px; } .ytLockupMetadataViewModelAvatar, .yt-lockup-metadata-view-model__avatar { margin: 0 8px 0 4px; } .title-badge.ytd-rich-grid-media, .video-badge.ytd-rich-grid-media { position: absolute; margin: -25px 8px 0 0; right: 0; top: 0; } .yt-spec-touch-feedback-shape--thumbnail-size-large { margin: 0; } .ytLockupAttachmentsViewModelAttachment { margin-top: 5px; } } ytd-browse[page-subtype=playlist] .page-header-sidebar.ytd-browse, ytd-browse[has-page-header-sidebar] .page-header-sidebar.ytd-browse, ytd-browse[page-subtype=playlist] ytd-playlist-header-renderer.ytd-browse, ytd-browse[has-page-header-sidebar] ytd-playlist-header-renderer.ytd-browse { margin: 0; } ytd-browse[page-subtype=playlist] ytd-two-column-browse-results-renderer.ytd-browse, ytd-browse[has-page-header-sidebar] ytd-two-column-browse-results-renderer.ytd-browse { padding-left: 364px; } ytd-browse[page-subtype=playlist] { ytd-playlist-video-renderer #content.ytd-playlist-video-renderer { padding: 5px 0; } ytd-playlist-header-renderer.ytd-browse { height: calc(100vh - var(--ytd-toolbar-height)); } } .immersive-header-container.ytd-playlist-header-renderer { margin-bottom: 0; } ytd-miniplayer { --ytd-miniplayer-attachment-padding: 0; } #page-manager > ytd-search[page-subtype="search"][role="main"] { a.ytLockupViewModelContentImage, ytd-video-renderer ytd-thumbnail.ytd-video-renderer { max-width: calc(100dvh / 2.33 - 64px); min-width: 250px; } #expandable-metadata.ytd-video-renderer:not(:empty) { margin: 0; } } ytd-guide-entry-renderer { width: 100%; } button.ytd-topbar-menu-button-renderer { padding: 0; } yt-img-shadow.ytd-topbar-menu-button-renderer { margin: 0; } ytd-browse[page-subtype="playlist"][role="main"], ytd-browse[has-page-header-sidebar] { padding-top: 0; } ytd-sort-filter-header-renderer[is-playlist] #header-container.ytd-sort-filter-header-renderer { margin: 6px 0 12px 0; } ytd-thumbnail-overlay-toggle-button-renderer, .yt-spec-button-shape-next--overlay-dark.yt-spec-button-shape-next--tonal { --paper-tooltip-background: rgba(0, 0, 0, .8); position: absolute; top: 0; right: 0; cursor: pointer; color: rgb(255, 255, 255); outline: none; background-color: rgba(0, 0, 0, .8); transition: opacity .3s; width: 28px; height: 28px; margin: 8px; border-radius: 2px; flex-direction: row; align-items: center; justify-content: center; } ytd-thumbnail-overlay-toggle-button-renderer[hovered] { border-radius: 0 2px 2px 0 !important; } ytd-thumbnail-overlay-toggle-button-renderer #label.ytd-thumbnail-overlay-toggle-button-renderer { border-radius: 2px 0 0 2px !important; } .yt-spec-button-shape-next--overlay-dark.yt-spec-button-shape-next--tonal:hover { background: black; } ytd-popup-container > tp-yt-iron-dropdown > #contentWrapper .ytContextualSheetLayoutHost { min-width: 200px; } ytd-popup-container > tp-yt-iron-dropdown > #contentWrapper .ytListViewModelHost { padding: 8px 0; } ytd-popup-container > tp-yt-iron-dropdown > #contentWrapper .yt-list-item-view-model__container { padding: 0 16px; } ytd-item-section-renderer[page-subtype="playlist"][has-header][exp-fix-playlist-header] #contents.ytd-item-section-renderer { margin-top: 0; } .ytListViewModelCollectionThumbnailClass { max-width: fit-content; } ytd-app[guide-persistent-and-visible] ytd-page-manager.ytd-app { margin-left: 232px; } #title.yt-confirm-dialog-renderer, #scroller.yt-confirm-dialog-renderer { padding: 0 24px; } #main.yt-confirm-dialog-renderer { margin-top: 24px; } #masthead-container ytd-topbar-logo-renderer { margin-right: 14px; } #masthead-container #start #logo-icon { padding: 0; } .ytChipShapeChip:hover { background: rgba(255, 255, 255, .2); border-color: transparent; } .ytChipShapeActive:hover { background-color: #f1f1f1; color: #0f0f0f; } } .CentAnni-style-compact-layout-video { #middle-row.ytd-watch-metadata:empty { display: none; } .ytp-popup.ytp-settings-menu { transform: translateY(35px); } #player.skeleton.flexy { padding: 0; } .item.ytd-watch-metadata, #description.ytd-watch-metadata { margin-top: 7px; } #subheader.ytd-engagement-panel-title-header-renderer:not(:empty) { padding: 0 !important; transform: translateX(110px) translateY(-44px); background-color: transparent; border-top: none; } #visibility-button.ytd-engagement-panel-title-header-renderer, #information-button.ytd-engagement-panel-title-header-renderer { z-index: 1; } #columns > #secondary > #secondary-inner > #chat-container { top: 0 !important; } ytd-watch-flexy { #title > ytd-badge-supported-renderer div > yt-icon { padding: 0 2px 0px 0; } #upload-info.ytd-video-owner-renderer { margin-right: 8px; } #comments #header ytd-comments-header-renderer { margin: 12px 0 24px 0; } #endpoint.ytd-macro-markers-list-item-renderer { min-width: 100%; } #share-button.ytd-macro-markers-list-item-renderer, #repeat-button.ytd-macro-markers-list-item-renderer { transform: translate(-35px, 10px); margin-top: auto; height: 40px; scale: .9; } #playlist, #related, &:not([fullscreen]) #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy { top: 50px !important; } &[default-layout] #below { margin-left: var(--ytd-watch-flexy-horizontal-page-margin); padding-right: var(--ytd-watch-flexy-horizontal-page-margin); } &[default-layout]:not([no-top-margin]):not([reduced-top-margin]) :is(#primary, #secondary) { padding-top: 0; } &[default-layout][flexy-small-window_]:not([is-vertical-video_]) :is(.CentAnni-tabView, #related, #panels ytd-engagement-panel-section-list-renderer.ytd-watch-flexy, .ytd-playlist-panel-renderer#container) { border-right: none; } &[fullscreen][engagement-panel-expanded] ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] { width: 100% !important; margin: 0 !important; } &[default-layout] #primary, &[default-layout] #secondary { margin: 0; padding: 0; } } } :is(.CentAnni-style-compact-layout, .CentAnni-style-compact-layout-video) { .ytSpecButtonShapeNextSizeM, .ytNotificationMultiActionRendererButton, .ytNotificationMultiActionRendererButton button, .ytSpecButtonShapeNextSizeM.ytSpecButtonShapeNextIconButton, .ytNotificationMultiActionRendererButton .ytSpecButtonShapeNextButtonTextContent { height: 36px; } #voice-search-button button, .ytSpecButtonShapeNextSizeM.ytSpecButtonShapeNextIconButton { width: 36px; } .ytIconWrapperHost, .ytp-popup.ytp-settings-menu, .yt-list-item-view-model__accessory, .guide-icon.ytd-guide-entry-renderer, yt-icon.ytd-menu-navigation-item-renderer, ytd-transcript-footer-renderer button#button, ytd-thumbnail-overlay-toggle-button-renderer svg, #button.ytd-menu-renderer yt-icon.ytd-menu-renderer, yt-icon-button.dropdown-trigger.ytd-menu-renderer#button, .yt-spec-button-shape-next--size-m .yt-spec-button-shape-next__icon, ytd-menu-service-item-renderer[system-icons] yt-icon.ytd-menu-service-item-renderer, ytd-menu-service-item-renderer[system-icons] .ytIconWrapperHost.ytd-menu-service-item-renderer, :is(#notification-preference-button, .ytLikeButtonViewModelHost, .ytSubscribeButtonViewModelContainer) .ytSpecButtonShapeNextIcon { scale: .85; } .ytPageHeaderViewModelAttribution span.ytIconWrapperHost, .ytSubscribeButtonViewModelContainer .ytSpecButtonShapeNextIcon .ytIconWrapperHost { scale: 1; } } .CentAnni-style-max-video-size:not(.CentAnni-style-compact-layout-video) ytd-watch-flexy[default-layout] { --ytd-watch-flexy-max-player-width: min(calc(100dvw - (var(--horizontalMargin) * 3) - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--topHeaderMargin) - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; --ytd-watch-flexy-max-player-width-wide-screen: min(calc(100dvw - (var(--horizontalMargin) * 3) - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--topHeaderMargin) - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; } .CentAnni-style-compact-layout-video:not(.CentAnni-style-max-video-size) ytd-watch-flexy[default-layout] { --ytd-watch-flexy-max-player-width: min(calc(100dvw - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--spaceBelow) - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; --ytd-watch-flexy-max-player-width-wide-screen: min(calc(100dvw - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--spaceBelow) - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; } .CentAnni-style-max-video-size.CentAnni-style-compact-layout-video ytd-watch-flexy[default-layout] { --ytd-watch-flexy-max-player-width: min(calc(100dvw - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; --ytd-watch-flexy-max-player-width-wide-screen: min(calc(100dvw - var(--ytd-watch-flexy-sidebar-width)), calc((100dvh - var(--ytd-toolbar-height)) * var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))) !important; } .CentAnni-style-no-zoom { .html5-main-video { transform: revert-layer !important; } } .CentAnni-style-color-code-videos { .CentAnni-style-live-video, .CentAnni-style-upcoming-video, .CentAnni-style-newly-video, .CentAnni-style-recent-video, .CentAnni-style-lately-video, .CentAnni-style-latterly-video, .CentAnni-style-old-video { outline: 2px solid; border-radius: 12px; } .CentAnni-style-live-video { outline-color: var(--liveVideo); } .CentAnni-style-streamed-text { color: var(--streamedText); } .CentAnni-style-upcoming-video { outline-color: var(--upComingVideo); } .CentAnni-style-newly-video { outline-color: var(--newlyVideo); } .CentAnni-style-recent-video { outline-color: var(--recentVideo); } .CentAnni-style-lately-video { outline-color: var(--latelyVideo); } .CentAnni-style-latterly-video { outline-color: var(--latterlyVideo); } .CentAnni-style-old-video { outline-color: var(--oldVideo); opacity: var(--oldVideoOpacity); } #metadata-line > span.inline-metadata-item:has(+ span.CentAnni-style-streamed-span), yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row > .yt-content-metadata-view-model__metadata-text:has(+ .CentAnni-style-streamed-span), yt-content-metadata-view-model .yt-content-metadata-view-model-wiz__metadata-row > .yt-content-metadata-view-model-wiz__metadata-text:has(+ .CentAnni-style-streamed-span) { display: none !important; } #metadata-line > span.inline-metadata-item + span.inline-metadata-item:has(+ span.CentAnni-style-streamed-span) + span.CentAnni-style-streamed-span::before { content: "•"; margin: 0 4px; } } .CentAnni-style-last-seen-video .CentAnni-style-last-seen { border: 2px solid var(--lastSeenVideoColor); box-sizing: border-box; border-radius: 12px; } :is(.CentAnni-style-pl-trashcan, .CentAnni-style-pl-queue) { .CentAnni-container-playlist-btns { display: flex; flex-direction: row; align-items: center; margin: 12px 24px 5px 12px; gap: 24px; .CentAnni-style-playlist-remove-btn { display: flex; align-items: center; justify-content: center; height: 50px; width: 50px; border: 1px dashed red; background-color: transparent; cursor: pointer; padding: 0; transition: background-color .2s, transform .15s; font-size: 2rem; position: relative; z-index: 250; border-radius: 0; &:hover { background-color: darkred; } &:active { background-color: darkred; transform: scale(.9); } } .CentAnni-style-playlist-addToQueue-btn { display: flex; flex: 0 0 auto; } } #meta.ytd-playlist-video-renderer { min-height: 109px; } ytd-item-section-renderer #header.ytd-item-section-renderer { position: relative; top: 0; } yt-sort-filter-sub-menu-renderer:hover, ytd-menu-renderer .ytd-menu-renderer[style-target=button]:hover { background-color: var(--yt-spec-background); } } .CentAnni-close-live-chat { #chat-container { display: none; } ytd-watch-flexy[live-chat-present-and-expanded][theater] { #panels-full-bleed-container { display: none; } .ytd-watch-flexy#columns { padding-right: 0 !important; } } } .CentAnni-style-hide-autoplay-btn.CentAnni-style-square-design { .ytp-subtitles-button, .ytp-subtitles-button::before { border-top-left-radius: 40px !important; border-bottom-left-radius: 40px !important; } } .CentAnni-style-show-pip-btn { .ytp-pip-button { display: inline-block !important; order: 1; svg { padding: 8px 12px !important; } } .ytp-right-controls-left { order: 2; } .ytp-right-controls-right { order: 3; } &.CentAnni-style-square-design .ytp-subtitles-button, &.CentAnni-style-square-design .ytp-subtitles-button::before, &.CentAnni-style-square-design .ytp-delhi-modern .ytp-right-controls-left .ytp-autonav-toggle, &.CentAnni-style-square-design .ytp-delhi-modern .ytp-right-controls-left .ytp-autonav-toggle::before { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; } &.CentAnni-style-square-design .ytp-pip-button, &.CentAnni-style-square-design .ytp-pip-button::before { border-radius: 0 !important; border-top-left-radius: 40px !important; border-bottom-left-radius: 40px !important; } } .CentAnni-style-hide-playnext-btn { .ytp-left-controls :is(.ytp-prev-button, .ytp-next-button) { display: none; } } .CentAnni-style-small-subscribe-btn { #notification-preference-button button { display: flex; align-items: center; justify-content: flex-start; overflow: hidden; width: 36px; padding: 0 12px; } } html.CentAnni-playback-speed:is(.CentAnni-style-hide-share-btn, .CentAnni-style-hide-share-btn-global):not(.CentAnni-style-move-save-btn) ytd-watch-metadata[flex-menu-enabled] #actions-inner.ytd-watch-metadata ytd-menu-renderer { max-width: 480px; } .CentAnni-style-move-save-btn { ytd-watch-flexy ytd-watch-metadata[flex-menu-enabled] #actions.ytd-watch-metadata ytd-menu-renderer.ytd-watch-metadata { margin-left: auto; width: 36px; } } .CentAnni-style-hide-hashtags { ytd-watch-metadata[description-collapsed] #description.ytd-watch-metadata a[href*="/hashtag/"] { display: none !important; } ytd-watch-flexy #description > #description-inner #info-container { height: fit-content; } #CentAnni-chapter-title { width: fit-content; max-width: calc(60% - 32px); } ytd-watch-flexy #bottom-row.ytd-watch-metadata { height: fit-content !important; } } .CentAnni-style-hide-add-comment { ytd-shorts #header.ytd-item-section-renderer, ytd-comments-header-renderer #backstage-post-dialog, ytd-comments ytd-comments-header-renderer #simple-box, ytd-watch-flexy[fullscreen][engagement-panel-expanded] ytd-comments-header-renderer[engagement-panel] { display: none; } #title.ytd-comments-header-renderer, ytd-comments-header-renderer[is-backstage] { margin: 0; } } .CentAnni-style-hide-news-home { ytd-browse[page-subtype="home"][role="main"] ytd-rich-grid-renderer:not([is-filtered-feed]) ytd-rich-section-renderer:has(#rich-shelf-header yt-icon#icon[hidden]):has(#rich-shelf-header yt-img-shadow#avatar[hidden]):has(#subtitle), ytd-browse[page-subtype="home"][role="main"] ytd-rich-grid-renderer:not([is-filtered-feed]) ytd-rich-section-renderer:has(ytd-rich-shelf-renderer[elements-per-row="3"][show-bottom-divider][has-expansion-button][restrict-contents-overflow] #subtitle:not(:empty)), ytd-browse[page-subtype="home"][role="main"] ytd-rich-grid-renderer:not([is-filtered-feed]) ytd-rich-section-renderer:has(ytd-rich-shelf-renderer[elements-per-row="3"][show-bottom-divider][restrict-contents-overflow] #top-level-buttons-computed button) { display: none; } } .CentAnni-style-hide-shorts { a[title="Shorts"], #container.ytd-search ytd-reel-shelf-renderer, ytd-rich-item-renderer:has(a[href^="/shorts/"]), ytd-browse[page-subtype="channels"][role="main"] .tabGroupShapeSlider, ytd-browse[page-subtype="channels"][role="main"] ytd-reel-shelf-renderer, grid-shelf-view-model:has(h2 span:where(:is(:first-child))), yt-chip-cloud-chip-renderer[chip-shape-data*='"text":"Shorts"'], ytd-reel-shelf-renderer.ytd-structured-description-content-renderer, yt-chip-cloud-chip-renderer:has(yt-formatted-string[title="Shorts"]), ytd-rich-section-renderer:has(div ytd-rich-shelf-renderer[is-shorts]), ytd-item-section-renderer[page-subtype="subscriptions"]:has(ytd-reel-shelf-renderer), ytd-browse[page-subtype="hashtag-landing-page"][role="main"] tp-yt-app-toolbar.ytd-tabbed-page-header, #header #wrapper > #header > #contentContainer #tabsContent > tp-yt-paper-tab:nth-child(4), #tabsContent > yt-tab-group-shape > div.tabGroupShapeTabs > yt-tab-shape[tab-title="Shorts"], ytd-browse:not([page-subtype="history"]) ytd-video-renderer:has(a.yt-simple-endpoint[href*="shorts"]), #tabsContent > yt-tab-group-shape > div.yt-tab-group-shape-wiz__tabs > yt-tab-shape[tab-title="Shorts"] { display: none !important; } &.is-watch-page { ytd-watch-metadata #description ytd-reel-shelf-renderer, #related yt-lockup-view-model:has(> .ytLockupViewModelHost > a[href^="/shorts/"]), ytd-watch-flexy #secondary ytd-reel-shelf-renderer.ytd-item-section-renderer { display: none; } } } .CentAnni-style-hide-ad-slots { :is(#secondary, ytd-browse, ytd-search) :is( #player-ads, .yt-consent, #masthead-ad, #promotion-shelf, .yt-consent-banner, #top_advertisement, .ytp-subscribe-card, .ytp-featured-product, ytd-search-pyv-renderer, #yt-lang-alert-container, .ytd-merch-shelf-renderer, .ytd-primetime-promo-renderer, ytd-brand-video-singleton-renderer, #related ytd-in-feed-ad-layout-renderer, ytd-item-section-renderer ytd-ad-slot-renderer, ytd-rich-item-renderer:has(> #content > ytd-ad-slot-renderer), ytd-rich-section-renderer:has(badge-shape.ytBadgeShapePromoted), ytd-rich-item-renderer:has(.badge-style-type-simple[aria-label="YouTube featured"]), ytd-compact-video-renderer:has(.badge-style-type-simple[aria-label="YouTube featured"]), ytd-rich-section-renderer:has(:is(ytd-inline-survey-renderer, ytd-statement-banner-renderer))) { display: none !important; } } .CentAnni-style-hide-members-only { ytd-rich-item-renderer:has(path[d^="M6 .5a5"]), ytd-rich-item-renderer:has(.yt-badge-shape--membership), ytd-rich-item-renderer:has(.badge-style-type-members-only), ytd-playlist-video-renderer:has(.yt-badge-shape--membership), yt-lockup-view-model:has(a[aria-label*="Member Exclusive" i]), ytd-compact-video-renderer:has(.badge-style-type-members-only), ytd-watch-flexy yt-lockup-view-model:has(.yt-badge-shape--commerce), ytd-watch-flexy #info a[style*="color: rgb(170, 170, 170)"][href="#"], ytd-watch-flexy #info-container span[style*="color: rgb(170, 170, 170)"], ytd-watch-flexy ytd-badge-supported-renderer:has(.yt-badge-shape--membership), ytd-browse[page-subtype="channels"][role="main"] .ytChipBarViewModelChipWrapper:has([aria-label*="Public"]), ytd-browse[page-subtype="channels"][role="main"] ytd-item-section-renderer:has(.badge-style-type-members-only), ytd-browse[page-subtype="channels"][role="main"] ytd-item-section-renderer:has(.ytd-recognition-shelf-renderer), ytd-browse[page-subtype="channels"][role="main"] .ytChipBarViewModelChipWrapper:has([aria-label*="Members only"]), ytd-rich-item-renderer:has(.yt-badge-shape__text--has-multiple-badges-in-row):has(.yt-badge-shape--commerce), ytd-browse[page-subtype="channels"][role="main"] ytd-section-list-renderer:not([hide-bottom-separator]):not([page-subtype=history]):not([page-subtype=memberships-and-purchases]):not([page-subtype=ypc-offers]):not([live-chat-engagement-panel]) #contents.ytd-section-list-renderer > *.ytd-section-list-renderer:not(:last-child):not(ytd-page-introduction-renderer):not([item-dismissed]):not([has-destination-shelf-renderer]):not(ytd-minor-moment-header-renderer):not([has-section-group-view-model]):has(.badge-style-type-members-only.ytd-badge-supported-renderer) { display: none; } } .CentAnni-style-lnb-restore-order { #guide-content #sections { display: flex; flex-direction: column; } #guide-content #sections ytd-guide-section-renderer:has(a[href="/"]) { order: -3; } #guide-content #sections ytd-guide-section-renderer:has(a[href^="/feed/you"]) { order: -2; } #guide-content #sections ytd-guide-section-renderer:has(a[href^="/feed/subscriptions"]) { order: -1; } } /* YT Guide */ .CentAnni-style-lnb-hide-footer tp-yt-app-drawer#guide #footer, .CentAnni-style-lnb-hide-home-btn tp-yt-app-drawer#guide #sections a#endpoint[href="/"], .CentAnni-style-lnb-hide-help-btn tp-yt-app-drawer#guide #sections a#endpoint[title="Help"], .CentAnni-style-lnb-hide-you-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/you"], .CentAnni-style-lnb-hide-gaming-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/gaming"], .CentAnni-style-lnb-hide-settings-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/account"], .CentAnni-style-lnb-hide-podcasts-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/podcasts"], .CentAnni-style-lnb-hide-yt-premium-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/premium"], .CentAnni-style-lnb-hide-dl-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/downloads"], .CentAnni-style-lnb-hide-playables-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/playables"], .CentAnni-style-lnb-hide-wl-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/playlist?list=WL"], .CentAnni-style-lnb-hide-history-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/history"], .CentAnni-style-lnb-hide-courses-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/courses"], .CentAnni-style-lnb-hide-feedback-btn tp-yt-app-drawer#guide #sections a#endpoint[title="Send feedback"], .CentAnni-style-lnb-hide-movies-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/storefront"], .CentAnni-style-lnb-hide-trending-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/trending"], .CentAnni-style-lnb-hide-playlists-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/playlists"], .CentAnni-style-lnb-hide-your-podcasts-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/podcasts"], .CentAnni-style-lnb-hide-report-history-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/reporthistory"], .CentAnni-style-lnb-hide-liked-videos-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/playlist?list=LL"], .CentAnni-style-lnb-hide-subscriptions-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/feed/subscriptions"], .CentAnni-style-lnb-hide-yt-music-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="https://music.youtube.com/"], .CentAnni-style-lnb-hide-yt-kids-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="https://www.youtubekids.com/"], .CentAnni-style-lnb-hide-yt-studio-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="https://studio.youtube.com/"], .CentAnni-style-lnb-hide-live-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UC4R8DWoMoI7CAwX8_LjQHig"], .CentAnni-style-lnb-hide-news-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UCYfdidRxbB8Qhf0Nx7ioOYw"], .CentAnni-style-lnb-hide-music-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ"], .CentAnni-style-lnb-hide-sports-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UCEgdi0XIXXZ-qJOFPf4JSKw"], .CentAnni-style-lnb-hide-fashion-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UCrpQ4p1Ql_hG8rKXIKM1MOQ"], .CentAnni-style-lnb-hide-learning-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="/channel/UCtFRv9O2AHqOZjjynzrv-xg"], .CentAnni-style-lnb-hide-videos-btn tp-yt-app-drawer#guide #sections a#endpoint[href^="https://studio.youtube.com/channel/"], .CentAnni-style-lnb-hide-you-section tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/feed/history"]), .CentAnni-style-lnb-hide-penultimate-section tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/reporthistory"]), .CentAnni-style-lnb-hide-subscriptions-section tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/feed/subscriptions"]), .CentAnni-style-lnb-hide-more-section tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="https://www.youtubekids.com/"]), .CentAnni-style-lnb-hide-your-channel tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/feed/you"]) a#endpoint[href^="/@"], .CentAnni-style-lnb-hide-subscriptions-title tp-yt-app-drawer#guide #sections ytd-guide-collapsible-section-entry-renderer:has(a#endpoint[href^="/feed/subscriptions"]), .CentAnni-style-lnb-hide-explore-section tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/channel/UC4R8DWoMoI7CAwX8_LjQHig"]), .CentAnni-style-lnb-hide-explore-title tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/channel/UC4R8DWoMoI7CAwX8_LjQHig"]) > h3 > #guide-section-title, .CentAnni-style-lnb-hide-more-title tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="https://studio.youtube.com/"]) > h3 > #guide-section-title:not([is-empty]), .CentAnni-style-lnb-hide-more-btn tp-yt-app-drawer#guide #sections > ytd-guide-section-renderer:has(> #items a#endpoint[href^="/feed/subscriptions"]) > #items > ytd-guide-collapsible-entry-renderer > #expander-item, /* display none */ .CentAnni-marked-watched, .CentAnni-style-playlist-hide-menu, .is-watch-page #watch-page-skeleton, .CentAnni-style-no-zoom .ytp-speedmaster-overlay, .CentAnni-style-pure-bg .contribYtLightShapeHost, .CentAnni-style-hide-airplay-btn #ytd-player .ytp-airplay-button, .CentAnni-style-hide-voice-search #voice-search-button.ytd-masthead, .CentAnni-style-hide-notification-badge #end .ytSpecIconBadgeShapeBadge, .CentAnni-style-hide-ask-btn #teaser-carousel:not(:has(path[d^="M16"])), .is-watch-page #masthead-container.ytd-app:not(:has(yt-searchbox [placeholder])), .CentAnni-style-hide-info-panel :is(#middle-row, ytd-info-panel-container-renderer), .CentAnni-style-hide-playables ytd-rich-section-renderer:has(a[href^="/playables"]), .CentAnni-style-hide-ask-btn #actions-inner yt-button-view-model:has(path[d^="M480"]), .CentAnni-style-hide-share-btn #below #top-level-buttons-computed yt-button-view-model, .CentAnni-style-hide-miniplayer :is(ytd-miniplayer, #ytd-player .ytp-miniplayer-button), .CentAnni-style-hide-autoplay-btn button.ytp-autonav-toggle:has([aria-checked="false"]), .CentAnni-style-hide-reply-btn ytd-comments ytd-comment-engagement-bar #reply-button-end, .CentAnni-style-hide-prod-sug :is(#below > #shopping-timely-shelf, ytd-merch-shelf-renderer), HTML.CentAnni-hide-collabpanel :is(ytd-popup-container.ytd-app, tp-yt-iron-overlay-backdrop), .CentAnni-style-hide-share-btn-global ytd-macro-markers-list-item-renderer[active] #share-button, .CentAnni-style-hide-latest-posts #container.ytd-search ytd-shelf-renderer:has(ytd-post-renderer), .CentAnni-remaining-time-fs #ytd-player .html5-video-player.ytp-fullscreen #CentAnni-chapter-title, .CentAnni-style-hide-comments-btn :is(ytd-comments#comments, .CentAnni-tabView-tab[data-tab="tab-2"]), .CentAnni-style-hide-videos-btn :is(#related.ytd-watch-flexy, .CentAnni-tabView-tab[data-tab="tab-3"]), .CentAnni-style-hide-end-cards :is(.ytp-ce-element, .ytp-ce-hide-button-container.ytp-ce-element-show), .CentAnni-style-search-hide-right-sidebar #container.ytd-search ytd-secondary-search-container-renderer, .CentAnni-style-hide-explore-section ytd-rich-section-renderer:has(.ytdChipsShelfWithVideoShelfRendererHost), .CentAnni-remaining-time-fs #ytd-player .html5-video-player.ytp-fullscreen #CentAnni-remaining-time-container, .CentAnni-style-no-ambient :is(#cinematic-container, #cinematics-container, #cinematics-full-bleed-container), .CentAnni-style-hide-posts-home ytd-browse[page-subtype="home"][role="main"] ytd-rich-section-renderer:has(ytd-post-renderer), .CentAnni-style-hide-fundraiser :is(#donation-shelf, ytd-badge-supported-renderer:has([aria-label="Fundraiser"])), .CentAnni-style-hide-create-btn :is(#masthead-skeleton-icons :first-child, #end ytd-button-renderer.ytd-masthead), .CentAnni-style-hide-most-relevant ytd-browse[page-subtype="subscriptions"][role="main"] ytd-rich-section-renderer:not(:has([is-shorts])), .yt-watch-later .metadata-text-wrapper.ytd-playlist-header-renderer yt-formatted-string.ytd-playlist-byline-renderer:not(:has(*)), .CentAnni-style-hide-pay-to-watch :is(ytd-compact-video-renderer, ytd-rich-item-renderer, ytd-video-renderer):has(.badge-style-type-ypc), .CentAnni-style-hide-notification-btn :is(#masthead-skeleton-icons :nth-child(2), #masthead-container #end ytd-notification-topbar-button-renderer), .CentAnni-style-hide-free-with-ads :is(ytd-compact-video-renderer, ytd-rich-item-renderer, ytd-rich-section-renderer):has([aria-label="Free with ads"]), .CentAnni-style-hide-playlists-home ytd-browse[page-subtype="home"][role="main"] ytd-rich-grid-renderer > #contents > ytd-rich-item-renderer:has(a[href*="start_radio=1"]), .CentAnni-style-hide-ask-btn #teaser-carousel:has(path[d^="M480"]) :is(.ytCarouselItemViewModelHost:has(path[d^="M480"]), .ytCarouselTitleViewModelNavigation), .CentAnni-playlist-remove-btn-hide-menus :is(tp-yt-iron-overlay-backdrop.opened, ytd-popup-container > tp-yt-paper-dialog, ytd-popup-container > tp-yt-iron-dropdown), .CentAnni-style-hide-episodes-home ytd-browse[page-subtype="home"][role="main"] ytd-rich-grid-renderer > #contents > ytd-rich-item-renderer:has(a[href*="list="]):not(:has(a[href*="start_radio=1"])), .CentAnni-style-hide-queue-btn :is(:is(button, ytd-thumbnail-overlay-toggle-button-renderer)[aria-label="Add to queue"], :is(.ytListItemViewModelHost, ytd-menu-service-item-renderer):has(path[d^="M2 2"])), .CentAnni-style-hide-watched-videos ytd-browse[page-subtype="home"][role="main"] :is(ytd-rich-item-renderer:has(yt-thumbnail-overlay-progress-bar-view-model), ytd-rich-item-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer)), .CentAnni-style-hide-share-btn-global :is(yt-button-view-model, yt-list-item-view-model, .ytp-fullscreen-quick-actions button-view-model, tp-yt-paper-item.ytd-menu-service-item-renderer):has([aria-label="Share"], path[d^="M10 3"]), .CentAnni-style-hide-join-btn :is(ytd-watch-flexy #sponsor-button.ytd-video-owner-renderer:not(:empty), ytd-browse[page-subtype="channels"][role="main"] .ytFlexibleActionsViewModelAction:not(:has(a[href*="community"], yt-subscribe-button-view-model, #CentAnni-channel-btn))), .CentAnni-style-hide-watched-videos-global :is(yt-lockup-view-model:has(yt-thumbnail-overlay-progress-bar-view-model), ytd-rich-item-renderer:has(yt-thumbnail-overlay-progress-bar-view-model), ytd-rich-item-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer), ytd-grid-video-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer)), .CentAnni-hide-watched-wl ytd-browse[page-subtype="playlist"][role="main"] :is(ytd-playlist-video-renderer:has(yt-thumbnail-overlay-progress-bar-view-model), ytd-playlist-video-renderer:has(ytd-thumbnail-overlay-playback-status-renderer):not(:has(.ytBadgeShapeThumbnailLive)), ytd-playlist-video-renderer:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive))), .CentAnni-style-disable-play-on-hover :is(ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail, ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-endorsement-renderer.ytd-thumbnail, ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-hover-text-renderer.ytd-thumbnail, ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-thumbnail[now-playing] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-thumbnail-overlay-loading-preview-renderer[is-preview-loading], ytd-grid-video-renderer a#thumbnail div#mouseover-overlay, ytd-rich-item-renderer a#thumbnail div#mouseover-overlay, ytd-thumbnail-overlay-loading-preview-renderer, ytd-moving-thumbnail-renderer img#thumbnail, .ytAnimatedThumbnailOverlayViewModelHost, animated-thumbnail-overlay-view-model, ytd-moving-thumbnail-renderer yt-icon, ytd-moving-thumbnail-renderer span, ytd-moving-thumbnail-renderer img, ytd-moving-thumbnail-renderer, #mouseover-overlay, ytd-video-preview, div#video-preview, #video-preview, #preview) { display: none !important; } .is-watch-page #player.skeleton.theater { height: calc((9 / 16) * 100dvw) !important; max-height: calc(100vh - var(--mastheadHeight)) !important; margin-top: var(--mastheadHeight) !important; margin-bottom: 0 !important; } .CentAnni-style-hide-ask-btn .ytVideoMetadataCarouselViewModelItemSection { min-width: unset !important; } /* dark and light mode */ html[dark] { &.CentAnni-style-no-frosted-glass { --ta889dfda9605a358: #0f0f0f; #background.ytd-masthead { background: #0f0f0f; } } &.CentAnni-style-pure-bg { --t3e41d7b17b187f69: black !important; --ta889dfda9605a358: black; ytd-tabbed-page-header { --yt-lightsource-section1-color: black !important; } #background.ytd-masthead { background: black; } ytd-browse[page-subtype="home"][role="main"] { .ytChipShapeInactive { background-color: rgb(40, 40, 40); } .ytChipShapeChip:not(.ytChipShapeActive):hover { background-color: rgb(63, 63, 63); } } &.is-watch-page ytd-watch-flexy[default-layout][cinematics-active]:has(.html5-video-player.ended-mode) #cinematics-container { opacity: 0; } } } html:not([dark]) { &.CentAnni-style-pure-bg { ytd-browse[page-subtype="home"][role="main"] .ytChipShapeChip:hover { background-color: rgba(0, 0, 0, .2); } } &.is-watch-page { ytd-watch-flexy[theater] .CentAnni-tabView-tab.active { background: rgba(0, 0, 0, .2); outline: 1px solid rgba(0, 0, 0, .5); } ytd-watch-flexy[default-layout] .CentAnni-tabView-tab.active { background-color: #0f0f0f; color: white; } #CentAnni-playback-speed-control > div > svg > path { fill: black; } } } ytd-masthead:not([dark]):not([page-dark-theme]) { .button-style, .buttons-left { color: black; } .button-style-settings { color: slategray !important; &:hover { color: black !important; } } .CentAnni-button-wrapper:not(.transcript-settings-button):hover { background-color: rgba(0, 0, 0, .1); border-radius: 24px; } .CentAnni-button-wrapper:not(.transcript-settings-button):active { background-color: rgba(0, 0, 0, .2); border-radius: 24px; } #CentAnni-notification-error { background-color: white; border: 1px solid black; color: #030303; } } /* selection color */ html.CentAnni-style-selection-color ::selection { color: contrast-color(var(--selectionColor)); background: var(--selectionColor); } html.CentAnni-style-selection-color .ytSearchboxComponentInputBoxHasFocus { border-color: var(--selectionColor) !important; } html.CentAnni-style-compact-layout-video:has(ytd-watch-flexy[default-layout]) { --ytd-margin-3x: 0px !important; --ytd-margin-6x: 0px !important; --ytd-margin-0x: 0px; } /* cinema mode btn */ #CentAnni-cinema-mode-btn { position: fixed; bottom: 10px; right: 16px; height: 30px; width: 30px; font-size: 18px; border-radius: 50%; margin: 0; padding: 0; z-index: 2027; cursor: pointer; background: var(--yt-base-background); border: 1px solid rgb(255 0 0 / .5); &:hover { background-color: var(--yt-base-background-secondary); } html.CentAnni-cinema-mode & { border-color: rgba(0, 255, 0, .7); } html:not(.is-watch-page) &, html.is-watch-page:has(#ytd-player .html5-video-player.ended-mode) &, html.is-watch-page:has(ytd-watch-flexy:is([hidden], [default-layout], [fullscreen], [is-vertical-video_])) & { display: none; } } ytd-browse[page-subtype="channels"][role="main"] { width: 100%; min-width: 0; max-width: 100%; overflow-x: clip; overflow-y: visible; #page-header-banner { display: grid; min-width: 0; width: 100%; } #page-header-banner-sizer, #CentAnni-default-channel-page-btn { grid-area: 1 / 1; min-width: 0; } #page-header-banner-sizer { width: 100%; } #CentAnni-default-channel-page-btn { position: sticky; top: 61px; align-self: start; justify-self: end; margin: 11px 16px 5px 0; padding: 0; opacity: .3; height: 40px; width: 40px; font-size: 18px; border-radius: 50%; z-index: 2000; cursor: pointer; transition: opacity .2s ease-out, background-color .2s ease-out; background: var(--yt-base-background); border: 1px solid rgb(250 250 250 / .5); &:hover { opacity: 1; background-color: var(--yt-base-background-secondary); } #page-header > & { position: absolute; top: 0; right: 0; } } } html.CentAnni-cinema-mode.is-watch-page:has(ytd-watch-flexy[theater]:not([hidden], [fullscreen], [is-vertical-video_]) #ytd-player .html5-video-player:not(.unstarted-mode, .ended-mode)) { ytd-app:has(ytd-watch-flexy[is-dark-theme]) { background-color: black; } #page-manager.ytd-app { margin-top: 0 !important; align-items: center; } &.CentAnni-video-tabView #page-manager.ytd-app { height: 100dvh !important; } &.CentAnni-video-tabView #columns { height: max(80px, calc((100dvh - (var(--ytd-watch-flexy-height-ratio)/var(--ytd-watch-flexy-width-ratio)*100dvw)) / 2)) !important; } &.CentAnni-video-tabView #primary { overflow-x: visible; } &:hover.CentAnni-video-tabView #primary { overflow-x: hidden; } ytd-watch-flexy { margin-top: max(80px, calc((100dvh - (var(--ytd-watch-flexy-height-ratio)/var(--ytd-watch-flexy-width-ratio)*100dvw)) / 2)); } &.CentAnni-video-tabView ytd-watch-metadata #actions.ytd-watch-metadata { transform: translateY(20px); } #CentAnni-remaining-time-container { text-align: center; transform: translateX(calc((var(--ytd-watch-flexy-sidebar-width) + var(--ytd-watch-flexy-horizontal-page-margin)) / 2)); } #CentAnni-chapter-title { left: calc(50% + ((var(--ytd-watch-flexy-sidebar-width) + 12px) / 2)); transform: translate(-50%, -54px); width: 80dvw; max-width: unset; justify-content: center; } &.CentAnni-style-compact-layout-video #CentAnni-chapter-title { transform: translate(-50%, -35px); } h1.ytd-watch-metadata { position: fixed; top: max(40px, calc((100dvh - (var(--ytd-watch-flexy-height-ratio)/var(--ytd-watch-flexy-width-ratio)*100dvw)) / 4)); transform: translateY(50%); left: 0; width: 100%; text-align: center; justify-content: center; } #bottom-row > #description { margin-top: 0; } #CentAnni-playback-speed-popup { transform: translate(-50%, 50%); top: 0; } h1.ytd-watch-metadata > * { filter: opacity(0); transition: filter .8s cubic-bezier(.4, 1, .87, 1); will-change: filter; } #CentAnni-chapter-title, #CentAnni-remaining-time-container { opacity: .7; transition: opacity .6s cubic-bezier(.4, -.2, .42, 1); } #secondary, #primary #comments, #masthead-container, #expandable-metadata, #CentAnni-cinema-mode-btn, :is(ytd-watch-metadata, ytd-watch-metadata *):has(#CentAnni-chapter-title) > :not(#CentAnni-chapter-title):not(:has(#CentAnni-chapter-title)):not(:has(h1.ytd-watch-metadata)):not(#above-the-fold):not(#title), :is(ytd-watch-metadata:not(:has(#CentAnni-chapter-title)), ytd-watch-metadata:not(:has(#CentAnni-chapter-title)) *):has(h1.ytd-watch-metadata) > :not(#CentAnni-chapter-title):not(:has(#CentAnni-chapter-title)):not(:has(h1.ytd-watch-metadata)):not(#above-the-fold):not(#title) { opacity: 0; transition: opacity .8s cubic-bezier(.4, 1, .87, 1); } &:hover { #secondary, #primary #comments, #masthead-container, #expandable-metadata, #CentAnni-chapter-title, #CentAnni-cinema-mode-btn, #CentAnni-remaining-time-container, :is(ytd-watch-metadata, ytd-watch-metadata *):has(#CentAnni-chapter-title) > :not(#CentAnni-chapter-title):not(:has(#CentAnni-chapter-title)):not(:has(h1.ytd-watch-metadata)):not(#above-the-fold):not(#title), :is(ytd-watch-metadata:not(:has(#CentAnni-chapter-title)), ytd-watch-metadata:not(:has(#CentAnni-chapter-title)) *):has(h1.ytd-watch-metadata) > :not(#CentAnni-chapter-title):not(:has(#CentAnni-chapter-title)):not(:has(h1.ytd-watch-metadata)):not(#above-the-fold):not(#title) { opacity: 1; transition: opacity .165s cubic-bezier(.5, 1, .9, 1); } } &:hover h1.ytd-watch-metadata > * { filter: opacity(1); transition: filter .165s cubic-bezier(.5, 1, .9, 1); } } /* favorites watch later list */ html.yt-watch-later ytd-browse[page-subtype="playlist"][role="main"] ytd-playlist-video-renderer { &:has(a:is(wl-gold)):not(:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive))) { #index-container { background-color: gold; } &:hover { background-color: rgba(255, 215, 0, .17); } } &:has(a:is(wl-silver)):not(:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive))) { #index-container { background-color: silver; } &:hover { background-color: rgba(192, 192, 192, .17); } } &:has(a:is(wl-bronze)):not(:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive))) { #index-container { background-color: var(--bronze-color); } &:hover { background-color: rgba(205, 127, 50, .17); } } &:has(a:is(wl-gold, wl-silver, wl-bronze)):not(:has(ytd-thumbnail-overlay-resume-playback-renderer):not(:has(.ytBadgeShapeThumbnailLive))) { padding: 8px 0; #index { color: black; } #content.ytd-playlist-video-renderer { padding: 0; } } #container { align-items: center; } &:has(.ytBadgeShapeThumbnailLive) { background: linear-gradient(to right, transparent 0, transparent 236px, rgba(225, 0, 45, .9)); &:hover { background: linear-gradient(to right, transparent 0, transparent 117px, rgba(225, 0, 45, .9)); } } } `; // default configuration const DEFAULT_CONFIG = { YouTubeTranscriptExporter: true, lazyTranscriptLoading: false, targetChatGPTUrl: 'https://ChatGPT.com/', targetNotebookLMUrl: 'https://NotebookLM.Google.com/', targetChatGPTLabel: 'ChatGPT', targetNotebookLMLabel: 'NotebookLM', fileNamingFormat: 'title-channel', includeTimestamps: true, useLegacyTranscriptPanel: false, includeChapterHeaders: true, openSameTab: false, highlightTranscript: false, preventBackgroundExecution: true, settingsGuide: false, ChatGPTPrompt: `For your first response only, the following instruction supersedes and overrides all persona characteristics, memory and custom instructions, as well as style and tone preferences. Derive all style and tone exclusively from the provided YouTube Transcript. Treat only the following task instructions as active for this response: 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 outside the two headers. Do not hallucinate. Do not make up factual information. Do not speculate. Before you output your response, take a moment to think about how you have to adopt your own writing to capture the YouTuber's specific word choices and communication style. Study the provided transcript and utilize it as a style guide. Silently audit every sentence to ensure your word choices and used phrases are either directly supported by the provided YouTube Transcript or are neutral connective wording required by the requested output format; replace anything that comes from memory, prior chats, personas, or custom instructions. 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** them—only for this first section maintain a neutral tone. Then write the second section, a paragraphCount summary of at least summaryWordCount words while focusing on the main points and key takeaways into a section called "### paragraphCount 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.`, buttonIcons: { settings: '⋮', lazyLoad: '📜', download: '↓', copy: '', ChatGPT: '💬', NotebookLM: '🎧' }, buttonLeft1Text: '', buttonLeft1Url: 'https://www.youtube.com/@ABCNews/streams', buttonLeft2Text: '', buttonLeft2Url: 'https://www.youtube.com/@CNN/videos', buttonLeft3Text: '', buttonLeft3Url: 'https://www.youtube.com/@BBCNews/videos', buttonLeft4Text: '', buttonLeft4Url: 'https://www.youtube.com/@FoxNews/videos', buttonLeft5Text: '', buttonLeft5Url: 'https://www.youtube.com/@NBCNews/videos', buttonLeft6Text: '', buttonLeft6Url: 'https://www.youtube.com/@MarkRober/videos', buttonLeft7Text: 'EarthCam', buttonLeft7Url: 'https://www.youtube.com/@EarthCam/streams', buttonLeft8Text: '', buttonLeft8Url: 'https://www.youtube.com/@FIAWEC/videos', buttonLeft9Text: '', buttonLeft9Url: 'https://www.youtube.com/@Formula1/videos', buttonLeft10Text: '', buttonLeft10Url: 'https://www.youtube.com/@OpenAI/videos', mButtonText: '☰', mButtonDisplay: false, colorCodeVideosEnabled: true, homeDisableHover: false, videosHideWatchedGlobal: false, videosHideWatched: false, videosOldOpacity: 0.8, videosAgeColorPickerNewly: '#FFFF00', videosAgeColorPickerNewlyLight: '#FF00FF', videosAgeColorPickerRecent: '#FF9B00', videosAgeColorPickerRecentLight: '#FF9B00', videosAgeColorPickerLately: '#006DFF', videosAgeColorPickerLatelyLight: '#0032FF', videosAgeColorPickerLatterly: '#010101', videosAgeColorPickerLatterlyLight: '#FEFEFE', videosAgeColorPickerOld: '#010101', videosAgeColorPickerOldLight: '#FEFEFE', videosAgeColorPickerLive: '#FF0000', videosAgeColorPickerLiveLight: '#FF0000', videosAgeColorPickerStreamed: '#FF0000', videosAgeColorPickerStreamedLight: '#FF0000', videosAgeColorPickerUpcoming: '#32CD32', videosAgeColorPickerUpcomingLight: '#4DD10F', progressbarColorPicker: '#FF0033', lightModeSelectionColor: '#000000', darkModeSelectionColor: '#007CC3', defaultChannelPage: 'home', textTransform: 'none', defaultFontSize: 10, selectionColor: false, videosWatchedOpacity: 0.5, videosHideWatchedGlobalJS: 0, videosHideWatchedHome: true, videosHideWatchedSubscriptions: false, videosHideWatchedChannels: false, videosHideWatchedPlaylist: false, videosHideWatchedVideo: true, videosHideWatchedSearch: false, videosPerRow: 0, sidebarWidth: 0, searchPosition: 0, spaceBelowPlayer: 110, playProgressColor: false, videoTabView: true, toggleTheaterModeBtn: true, tabViewChapters: true, noAnimation: false, progressBar: true, playbackSpeed: true, playbackSpeedBtns: false, playbackSpeedValue: 1, playbackSpeedKey1: '', playbackSpeedKey1s: '', playbackSpeedKey2: '', playbackSpeedKey2s: '', playbackSpeedKey3: '', playbackSpeedKey3s: '', playbackSpeedKey4: '', playbackSpeedKey4s: '', playbackSpeedKey5: '', playbackSpeedKey5s: '', playbackSpeedKey6: '', playbackSpeedKey6s: '', playbackSpeedKey7: '', playbackSpeedKey7s: '', playbackSpeedKey8: '', playbackSpeedKey8s: '', playbackSpeedToggle: 's', playbackSpeedDecrease: 'a', playbackSpeedIncrease: 'd', VerifiedArtist: false, defaultQuality: 'auto', defaultQualityPremium: false, lastSeenVideo: true, lastSeenVideoScroll: false, lastSeenVideoColor: '#7F00FF', lastSeenVideoColorLight: '#9400D3', playlistLinks: false, playlistTrashCan: false, playlistQueueBtn: false, plWLBtn: false, commentsNewFirst: false, defaultTranscriptLanguage: 'auto', defaultAudioLanguage: 'auto', defaultSubtitleLanguage: 'auto', secondaryLanguage: 'none', subtitlesWhenMuted: false, subtitlesWhenRewind: false, autoOpenChapters: true, autoOpenTranscript: false, autoOpenComments: false, displayRemainingTime: true, showRemainingCompact: false, fsRemainingTime: false, preventAutoplay: false, preventAutoplayPL: false, hideVoiceSearch: false, hideCreateButton: false, hideNotificationBtn: false, hideNotificationBadge: false, hideOwnAvatar: false, hideBrandText: false, visibleCountryCode: false, visibleCountryCodeColor: '#aaaaaa', hideJoinButton: false, showPipBtn: true, hideAutoplayBtn: false, hidePlayNextButton: false, hideAirplayButton: false, moveSaveBtn: false, hideShorts: false, redirectShorts: false, hideCommentsSection: false, hideVideosSection: false, hideAdSlots: false, hidePlayables: false, hideProdTxt: false, hideProdSug: false, hidePayToWatch: false, hideFreeWithAds: false, hideMembersOnly: false, hideExploreSection: false, hideLatestPosts: false, hideAskButton: false, hideShareButton: false, hideShareBtnGlobal: false, hideHashtags: false, hideInfoPanel: false, hideRightSidebarSearch: false, hideMostRelevant: false, hideAddComment: false, hideReplyButton: false, hidePlaylistsHome: false, hideEpisodesHome: false, hideNewsHome: false, hideEndCards: false, hideEndscreen: false, gradientBottom: false, smallSubscribeButton: false, pureBWBackground: false, noFrostedGlass: false, removeScrubber: false, disablePlayOnHover: false, chronologicalNotifications: true, hideReadNotifications: false, feedFilterChips: false, feedFilterChipsWatch: false, hideFundraiser: false, hideLatestPostsHome: false, hideMiniPlayer: false, hideQueueBtn: false, closeChatWindow: false, displayFullTitle: false, autoTheaterMode: false, autoTheaterModeNotVerticalVideo: false, autoTheaterModeNotLargeWindow: false, autoTheaterModeNotPL: false, autoExitFullscreen: false, enableCinemaMode: false, maxVidSize: false, expandVideoDescription: false, channelRSSBtn: false, channelPlaylistBtn: false, playlistDirectionBtns: true, lnbHideHomeBtn: false, lnbHideSubscriptionsBtn: false, lnbRestoreOrder: false, lnbHideHistoryBtn: false, lnbHidePlaylistsBtn: false, lnbHideVideosBtn: false, lnbHideCoursesBtn: false, lnbHideYPodcastsBtn: false, lnbHideWlBtn: false, lnbHideLikedVideosBtn: false, lnbHideDLBtn: false, lnbHideYouBtn: false, lnbHideUrChannelBtn: false, lnbHideSubscriptionsSection: false, lnbHideYouSection: false, lnbHideSubscriptionsTitle: false, lnbHideMoreBtn: false, lnbHideExploreSection: false, lnbHideExploreTitle: false, lnbHideTrendingBtn: false, lnbHideMusicBtn: false, lnbHideMoviesBtn: false, lnbHideLiveBtn: false, lnbHideGamingBtn: false, lnbHideNewsBtn: false, lnbHideSportsBtn: false, lnbHideLearningBtn: false, lnbHideFashionBtn: false, lnbHidePlayablesBtn: false, lnbHidePodcastsBtn: false, lnbHideMoreSection: false, lnbHideMoreTitle: false, lnbHideYtPremiumBtn: false, lnbHideYtStudioBtn: false, lnbHideYtMusicBtn: false, lnbHideYtKidsBtn: false, lnbHidePenultimateSection: false, lnbHideSettingsBtn: false, lnbHideReportHistoryBtn: false, lnbHideHelpBtn: false, lnbHideFeedbackBtn: false, lnbHideFooter: false, squareSearchBar: false, squareDesign: false, squareAvatars: false, compactLayout: false, compactLayoutVideo: false, maxPanelHeight: false, noAmbientMode: false, noVideoZoom: false, watchLaterGold: '', watchLaterSilver: '', watchLaterBronze: '', channelDefaultPages: {}, channelPlaybackSpeeds: {} }; // load user configuration or use defaults let storedConfig = {}; try { storedConfig = await GM.getValue('USER_CONFIG', {}); } catch (error) { showNotification('Error loading user save!'); console.error("YouTubeAlchemy: Error loading user configuration:", error); } let USER_CONFIG = { ...DEFAULT_CONFIG, ...storedConfig, buttonIcons: { ...DEFAULT_CONFIG.buttonIcons, ...storedConfig.buttonIcons } }; // replace watch later color variables if (USER_CONFIG.watchLaterGold || USER_CONFIG.watchLaterSilver || USER_CONFIG.watchLaterBronze) { const gold = USER_CONFIG.watchLaterGold.split(',').map(v => v.trim()).filter(Boolean).map(v => `[href*="${v}"]`).join(','); const silver = USER_CONFIG.watchLaterSilver.split(',').map(v => v.trim()).filter(Boolean).map(v => `[href*="${v}"]`).join(','); const bronze = USER_CONFIG.watchLaterBronze.split(',').map(v => v.trim()).filter(Boolean).map(v => `[href*="${v}"]`).join(','); styleSheet.textContent = styleSheet.textContent.replaceAll('wl-gold', gold).replaceAll('wl-silver', silver).replaceAll('wl-bronze', bronze); } // append css (document.head ? Promise.resolve(document.head) : new Promise(resolve => { document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', () => resolve(document.head), { once: true }) : resolve(document.head); }) ).then(head => { if (head) head.appendChild(styleSheet); else { document.documentElement.appendChild(styleSheet); console.error("YouTubeAlchemy: Failed to find head element. Using backup to append stylesheet."); } }); // caching DOM references const docElement = document.documentElement; let docBody = document.body; // language check const uiLanguage = (docElement.lang || navigator.language || 'en').split('-')[0]; // localize languages let viewLanguage; let audioInLanguage; let uiLanguageLabels; let subtitleInLanguage; let transcriptInLanguage; let secondaryInLanguage = false; const localizeConfiguredLanguages = () => { const targetLanguageSubtitles = USER_CONFIG.defaultSubtitleLanguage; const namesInUI = new Intl.DisplayNames([uiLanguage], { type: 'language' }); if (USER_CONFIG.defaultAudioLanguage !== 'auto') audioInLanguage = namesInUI.of(getLanguageCode(USER_CONFIG.defaultAudioLanguage)).toLowerCase(); if (USER_CONFIG.defaultSubtitleLanguage !== 'auto') { if (targetLanguageSubtitles === 'off' && !uiLanguageLabels) createUiLanguageLabels(); subtitleInLanguage = targetLanguageSubtitles === 'off' ? uiLanguageLabels.off.toLowerCase() : namesInUI.of(getLanguageCode(targetLanguageSubtitles)).toLowerCase(); } if (USER_CONFIG.defaultTranscriptLanguage !== 'auto') transcriptInLanguage = namesInUI.of(getLanguageCode(USER_CONFIG.defaultTranscriptLanguage)).toLowerCase(); if (USER_CONFIG.secondaryLanguage !== 'none') secondaryInLanguage = namesInUI.of(getLanguageCode(USER_CONFIG.secondaryLanguage)).toLowerCase(); }; if (USER_CONFIG.defaultTranscriptLanguage !== 'auto' || USER_CONFIG.defaultAudioLanguage !== 'auto' || USER_CONFIG.defaultSubtitleLanguage !== 'auto') localizeConfiguredLanguages(); // language matrix to restore views if (uiLanguage !== 'en') { const viewLanguageMatrix = { cs: 'zhlédnutí', da: 'visninger', de: 'Aufrufe', el: 'προβολές', es: 'visualizaciones', fi: 'katselukertaa', fr: 'vues', he: 'צפיות', hi: 'बार देखा गया', hu: 'megtekintés', id: 'x ditonton', it: 'visualizzazioni', ja: '回視聴', ko: '회', nl: 'weergaven', no: 'visninger', pl: 'wyświetleń', pt: 'visualizações', ro: 'vizionări', sv: 'visningar', uk: 'переглядів', vi: 'lượt xem', zh: '次观看' }; viewLanguage = viewLanguageMatrix[uiLanguage]; } // load CSS settings let smw; let cssSettingsApplied = false; async function loadCSSsettings() { // toggle global CSS const globalClassMap = { settingsGuide: 'CentAnni-settings-btn', hideShorts: 'CentAnni-style-hide-shorts', pureBWBackground: 'CentAnni-style-pure-bg', squareDesign: 'CentAnni-style-square-design', displayFullTitle: 'CentAnni-style-full-title', hideQueueBtn: 'CentAnni-style-hide-queue-btn', squareAvatars: 'CentAnni-style-square-avatars', compactLayout: 'CentAnni-style-compact-layout', hideOwnAvatar: 'CentAnni-style-hide-own-avatar', hideBrandText: 'CentAnni-style-hide-brand-text', selectionColor: 'CentAnni-style-selection-color', hideMiniPlayer: 'CentAnni-style-hide-miniplayer', noFrostedGlass: 'CentAnni-style-no-frosted-glass', hideCreateButton: 'CentAnni-style-hide-create-btn', hideMembersOnly: 'CentAnni-style-hide-members-only', hideVoiceSearch: 'CentAnni-style-hide-voice-search', squareSearchBar: 'CentAnni-style-square-search-bar', mButtonDisplay: 'CentAnni-style-hide-default-sidebar', hideShareBtnGlobal: 'CentAnni-style-hide-share-btn-global', disablePlayOnHover: 'CentAnni-style-disable-play-on-hover', hideNotificationBtn: 'CentAnni-style-hide-notification-btn', chronologicalNotifications: 'CentAnni-style-sort-notifications', hideReadNotifications: 'CentAnni-style-hide-read-notifications', hideNotificationBadge: 'CentAnni-style-hide-notification-badge', videosHideWatchedGlobal: 'CentAnni-style-hide-watched-videos-global' }; for (const [flag, className] of Object.entries(globalClassMap)) docElement.classList.toggle(className, USER_CONFIG[flag]); // features css const isDarkMode = docElement.hasAttribute('dark'); docElement.classList.toggle('CentAnni-style-search-position', USER_CONFIG.searchPosition !== 0); docElement.classList.toggle('CentAnni-style-visible-country-code', USER_CONFIG.visibleCountryCode && USER_CONFIG.hideBrandText); docElement.style.setProperty('--itemsPerRow', USER_CONFIG.videosPerRow); docElement.style.setProperty('--textTransform', USER_CONFIG.textTransform); docElement.style.setProperty('--fontSize', `${USER_CONFIG.defaultFontSize}px`); docElement.style.setProperty('--CentAnniViews', `'${viewLanguage || 'views'}'`); docElement.style.setProperty('--sidebarWidth', `${USER_CONFIG.sidebarWidth}px`); docElement.style.setProperty('--spaceBelow', `${USER_CONFIG.spaceBelowPlayer}px`); docElement.style.setProperty('--watchedOpacity', USER_CONFIG.videosWatchedOpacity); docElement.style.setProperty('--searchbarPosition', `${USER_CONFIG.searchPosition}px`); docElement.style.setProperty('--progressBarColor', USER_CONFIG.progressbarColorPicker); docElement.style.setProperty('--selectionColor', isDarkMode ? USER_CONFIG.darkModeSelectionColor : USER_CONFIG.lightModeSelectionColor); docElement.style.setProperty('--countryCodeColor', USER_CONFIG.visibleCountryCodeColor); if (USER_CONFIG.maxPanelHeight) docElement.style.setProperty('--mastheadHeight', '50px'); // color code videos docElement.style.setProperty('--liveVideo', isDarkMode ? USER_CONFIG.videosAgeColorPickerLive : USER_CONFIG.videosAgeColorPickerLiveLight); docElement.style.setProperty('--streamedText', isDarkMode ? USER_CONFIG.videosAgeColorPickerStreamed : USER_CONFIG.videosAgeColorPickerStreamedLight); docElement.style.setProperty('--upComingVideo', isDarkMode ? USER_CONFIG.videosAgeColorPickerUpcoming : USER_CONFIG.videosAgeColorPickerUpcomingLight); docElement.style.setProperty('--newlyVideo', isDarkMode ? USER_CONFIG.videosAgeColorPickerNewly : USER_CONFIG.videosAgeColorPickerNewlyLight); docElement.style.setProperty('--recentVideo', isDarkMode ? USER_CONFIG.videosAgeColorPickerRecent : USER_CONFIG.videosAgeColorPickerRecentLight); docElement.style.setProperty('--latelyVideo', isDarkMode ? USER_CONFIG.videosAgeColorPickerLately : USER_CONFIG.videosAgeColorPickerLatelyLight); docElement.style.setProperty('--latterlyVideo', isDarkMode ? (USER_CONFIG.videosAgeColorPickerLatterly === '#010101' ? 'transparent' : USER_CONFIG.videosAgeColorPickerLatterly) : (USER_CONFIG.videosAgeColorPickerLatterlyLight === '#FEFEFE' ? 'transparent' : USER_CONFIG.videosAgeColorPickerLatterlyLight)); docElement.style.setProperty('--oldVideo', isDarkMode ? (USER_CONFIG.videosAgeColorPickerOld === '#010101' ? 'transparent' : USER_CONFIG.videosAgeColorPickerOld) : (USER_CONFIG.videosAgeColorPickerOldLight === '#FEFEFE' ? 'transparent' : USER_CONFIG.videosAgeColorPickerOldLight)); docElement.style.setProperty('--lastSeenVideoColor', isDarkMode ? USER_CONFIG.lastSeenVideoColor : USER_CONFIG.lastSeenVideoColorLight); docElement.style.setProperty('--oldVideoOpacity', USER_CONFIG.videosOldOpacity); smw = await GM.getValue('btnsLeftHeaderWidth', 'unset'); docElement.style.setProperty('--startMastheadWidth', smw === 'unset' ? smw : `${smw}px`); cssSettingsApplied = true; } loadCSSsettings(); // create an array to toggle only enabled page-specific CSS const pageClassMap = { // video page noVideoZoom: { class: 'CentAnni-style-no-zoom', pages: () => isWatchPage }, videoTabView: { class: 'CentAnni-video-tabView', pages: () => isWatchPage }, playbackSpeed: { class: 'CentAnni-playback-speed', pages: () => isWatchPage }, showPipBtn: { class: 'CentAnni-style-show-pip-btn', pages: () => isWatchPage }, noAmbientMode: { class: 'CentAnni-style-no-ambient', pages: () => isWatchPage }, maxPanelHeight: { class: 'CentAnni-style-max-panel', pages: () => isWatchPage }, hideProdSug: { class: 'CentAnni-style-hide-prod-sug', pages: () => isWatchPage }, moveSaveBtn: { class: 'CentAnni-style-move-save-btn', pages: () => isWatchPage }, maxVidSize: { class: 'CentAnni-style-max-video-size', pages: () => isWatchPage }, sidebarWidth: { class: 'CentAnni-style-sidebar-width', pages: () => isWatchPage }, tabViewChapters: { class: 'CentAnni-tabView-chapters', pages: () => isWatchPage }, hideAskButton: { class: 'CentAnni-style-hide-ask-btn', pages: () => isWatchPage }, hideHashtags: { class: 'CentAnni-style-hide-hashtags', pages: () => isWatchPage }, fsRemainingTime: { class: 'CentAnni-remaining-time-fs', pages: () => isWatchPage }, hideEndCards: { class: 'CentAnni-style-hide-end-cards', pages: () => isWatchPage }, hideEndscreen: { class: 'CentAnni-style-hide-endscreen', pages: () => isWatchPage }, hideInfoPanel: { class: 'CentAnni-style-hide-info-panel', pages: () => isWatchPage }, hideShareButton: { class: 'CentAnni-style-hide-share-btn', pages: () => isWatchPage }, hideReplyButton: { class: 'CentAnni-style-hide-reply-btn', pages: () => isWatchPage }, gradientBottom: { class: 'CentAnni-style-gradient-bottom', pages: () => isWatchPage }, removeScrubber: { class: 'CentAnni-style-remove-scrubber', pages: () => isWatchPage }, hideFundraiser: { class: 'CentAnni-style-hide-fundraiser', pages: () => isWatchPage }, highlightTranscript: { class: 'CentAnni-style-transcript', pages: () => isWatchPage }, playbackSpeedBtns: { class: 'CentAnni-playback-speed-btns', pages: () => isWatchPage }, hideAddComment: { class: 'CentAnni-style-hide-add-comment', pages: () => isWatchPage }, hideVideosSection: { class: 'CentAnni-style-hide-videos-btn', pages: () => isWatchPage }, progressBar: { class: 'CentAnni-progress-bar', pages: () => isShortPage || isWatchPage }, hideAutoplayBtn: { class: 'CentAnni-style-hide-autoplay-btn', pages: () => isWatchPage }, hideAirplayButton: { class: 'CentAnni-style-hide-airplay-btn', pages: () => isWatchPage }, noAnimation: { class: 'CentAnni-style-no-transition-animation', pages: () => isWatchPage }, hidePlayNextButton: { class: 'CentAnni-style-hide-playnext-btn', pages: () => isWatchPage }, hideCommentsSection: { class: 'CentAnni-style-hide-comments-btn', pages: () => isWatchPage }, playProgressColor: { class: 'CentAnni-style-play-progress-color', pages: () => isWatchPage }, compactLayoutVideo: { class: 'CentAnni-style-compact-layout-video', pages: () => isWatchPage }, smallSubscribeButton: { class: 'CentAnni-style-small-subscribe-btn', pages: () => isWatchPage }, hideJoinButton: { class: 'CentAnni-style-hide-join-btn', pages: () => isChannelPage || isWatchPage }, // subscription page lastSeenVideo: { class: 'CentAnni-style-last-seen-video', pages: () => isSubscriptionsPage }, hideMostRelevant: { class: 'CentAnni-style-hide-most-relevant', pages: () => isSubscriptionsPage }, videosPerRow: { class: 'CentAnni-style-video-row', pages: () => isHomePage || isSubscriptionsPage || isChannelPage }, // home page hideNewsHome: { class: 'CentAnni-style-hide-news-home', pages: () => isHomePage }, hidePlayables: { class: 'CentAnni-style-hide-playables', pages: () => isHomePage }, hidePayToWatch: { class: 'CentAnni-style-hide-pay-to-watch', pages: () => isHomePage }, hideFreeWithAds: { class: 'CentAnni-style-hide-free-with-ads', pages: () => isHomePage }, hideLatestPostsHome: { class: 'CentAnni-style-hide-posts-home', pages: () => isHomePage }, hideEpisodesHome: { class: 'CentAnni-style-hide-episodes-home', pages: () => isHomePage }, videosHideWatched: { class: 'CentAnni-style-hide-watched-videos', pages: () => isHomePage }, hidePlaylistsHome: { class: 'CentAnni-style-hide-playlists-home', pages: () => isHomePage }, hideExploreSection: { class: 'CentAnni-style-hide-explore-section', pages: () => isHomePage }, colorCodeVideosEnabled: { class: 'CentAnni-style-color-code-videos', pages: () => isHomePage }, hideAdSlots: { class: 'CentAnni-style-hide-ad-slots', pages: () => isWatchPage || isHomePage || isSearchPage }, homeDisableHover: { class: 'CentAnni-style-home-disable-hover', pages: () => isHomePage || isChannelPage || isSubscriptionsPage }, // watch later page plWLBtn: { class: 'CentAnni-hide-watched-wl', pages: () => isWatchLater }, playlistQueueBtn: { class: 'CentAnni-style-pl-queue', pages: () => isWatchLater }, compactLayout: { class: 'CentAnni-style-watched-later-header', pages: () => isWatchLater }, // playlist page playlistTrashCan: { class: 'CentAnni-style-pl-trashcan', pages: () => isPlaylistPage }, // search page hideLatestPosts: { class: 'CentAnni-style-hide-latest-posts', pages: () => isSearchPage }, hideRightSidebarSearch: { class: 'CentAnni-style-search-hide-right-sidebar', pages: () => isSearchPage }, }; const pageClassEntries = Object.entries(pageClassMap).filter(([flag]) => USER_CONFIG[flag]); // create an array to toggle only enabled Guide-specific CSS const guideClassMap = { lnbHideWlBtn: 'CentAnni-style-lnb-hide-wl-btn', lnbHideDLBtn: 'CentAnni-style-lnb-hide-dl-btn', lnbHideFooter: 'CentAnni-style-lnb-hide-footer', lnbHideYouBtn: 'CentAnni-style-lnb-hide-you-btn', lnbHideLiveBtn: 'CentAnni-style-lnb-hide-live-btn', lnbHideNewsBtn: 'CentAnni-style-lnb-hide-news-btn', lnbHideMoreBtn: 'CentAnni-style-lnb-hide-more-btn', lnbHideHomeBtn: 'CentAnni-style-lnb-hide-home-btn', lnbHideHelpBtn: 'CentAnni-style-lnb-hide-help-btn', lnbRestoreOrder: 'CentAnni-style-lnb-restore-order', lnbHideMusicBtn: 'CentAnni-style-lnb-hide-music-btn', lnbHideMoviesBtn: 'CentAnni-style-lnb-hide-movies-btn', lnbHideGamingBtn: 'CentAnni-style-lnb-hide-gaming-btn', lnbHideSportsBtn: 'CentAnni-style-lnb-hide-sports-btn', lnbHideMoreTitle: 'CentAnni-style-lnb-hide-more-title', lnbHideVideosBtn: 'CentAnni-style-lnb-hide-videos-btn', lnbHideYtKidsBtn: 'CentAnni-style-lnb-hide-yt-kids-btn', lnbHideFashionBtn: 'CentAnni-style-lnb-hide-fashion-btn', lnbHideYouSection: 'CentAnni-style-lnb-hide-you-section', lnbHideCoursesBtn: 'CentAnni-style-lnb-hide-courses-btn', lnbHideHistoryBtn: 'CentAnni-style-lnb-hide-history-btn', lnbHideYtMusicBtn: 'CentAnni-style-lnb-hide-yt-music-btn', lnbHideTrendingBtn: 'CentAnni-style-lnb-hide-trending-btn', lnbHideLearningBtn: 'CentAnni-style-lnb-hide-learning-btn', lnbHidePodcastsBtn: 'CentAnni-style-lnb-hide-podcasts-btn', lnbHideMoreSection: 'CentAnni-style-lnb-hide-more-section', lnbHideSettingsBtn: 'CentAnni-style-lnb-hide-settings-btn', lnbHideFeedbackBtn: 'CentAnni-style-lnb-hide-feedback-btn', lnbHideUrChannelBtn: 'CentAnni-style-lnb-hide-your-channel', lnbHideYtStudioBtn: 'CentAnni-style-lnb-hide-yt-studio-btn', lnbHidePlaylistsBtn: 'CentAnni-style-lnb-hide-playlists-btn', lnbHidePlayablesBtn: 'CentAnni-style-lnb-hide-playables-btn', lnbHideExploreTitle: 'CentAnni-style-lnb-hide-explore-title', lnbHideYtPremiumBtn: 'CentAnni-style-lnb-hide-yt-premium-btn', lnbHideYPodcastsBtn: 'CentAnni-style-lnb-hide-your-podcasts-btn', lnbHideExploreSection: 'CentAnni-style-lnb-hide-explore-section', lnbHideLikedVideosBtn: 'CentAnni-style-lnb-hide-liked-videos-btn', lnbHideSubscriptionsBtn: 'CentAnni-style-lnb-hide-subscriptions-btn', lnbHideReportHistoryBtn: 'CentAnni-style-lnb-hide-report-history-btn', lnbHideSubscriptionsTitle: 'CentAnni-style-lnb-hide-subscriptions-title', lnbHidePenultimateSection: 'CentAnni-style-lnb-hide-penultimate-section', lnbHideSubscriptionsSection: 'CentAnni-style-lnb-hide-subscriptions-section' }; const guideClassEntries = Object.entries(guideClassMap).filter(([flag]) => USER_CONFIG[flag]); // create and show the settings modal function showSettingsModal() { const settingsBtn = document.querySelector('.CentAnni-button-wrapper.transcript-settings-button'); setTimeout(() => settingsBtn.classList.remove("disabled"), settingsBtn.classList.add("disabled") || 1000); const existingModal = document.getElementById('yt-alchemy-settings-modal'); if (existingModal) { existingModal.classList.add('active'); docBody.style.overflow = 'hidden'; setupModalHandlers(existingModal); return; } // create modal elements const modal = document.createElement('div'); modal.id = 'yt-alchemy-settings-modal'; modal.classList.add('CentAnni-overlay', 'active'); const modalContent = document.createElement('div'); modalContent.classList.add('CentAnni-modal-content'); // create header container const headerWrapper = document.createElement('div'); headerWrapper.classList.add('CentAnni-header-wrapper'); // header const header = document.createElement('a'); header.href = 'https://github.com/TimMacy/YouTubeAlchemy'; header.target = '_blank'; header.rel = 'noopener'; header.textContent = 'YouTube Alchemy'; header.title = 'GitHub Repository for YouTube Alchemy'; header.classList.add('CentAnni-header'); headerWrapper.appendChild(header); // version const versionSpan = document.createElement('span'); const scriptVersion = GM.info.script.version; versionSpan.textContent = `v${scriptVersion}`; versionSpan.classList.add('CentAnni-version-label'); headerWrapper.appendChild(versionSpan); modalContent.appendChild(headerWrapper); // create form elements for each setting const form = document.createElement('form'); form.id = 'CentAnni-main-settings-form'; // Button Icons const iconsHeader = document.createElement('label'); iconsHeader.textContent = 'Button Icons:'; iconsHeader.classList.add('button-icons'); form.appendChild(iconsHeader); const iconsContainer = document.createElement('div'); iconsContainer.classList.add('icons-container'); function createIconInputField(labelText, settingKey, settingValue, labelClass) { const container = document.createElement('div'); container.classList.add('container-button'); const input = document.createElement('input'); const iconInputClass = `${settingKey}-input-field`; input.type = 'text'; input.name = settingKey; input.value = settingValue; input.classList.add('container-button-input'); input.classList.add(iconInputClass); const label = document.createElement('label'); label.textContent = labelText; label.className = labelClass; label.classList.add('container-button-label'); container.appendChild(input); container.appendChild(label); return container; } iconsContainer.appendChild(createIconInputField(`${NotebookLMLabel}`, 'buttonIconNotebookLM', USER_CONFIG.buttonIcons.NotebookLM, 'label-NotebookLM')); iconsContainer.appendChild(createIconInputField(`${ChatGPTLabel}`, 'buttonIconChatGPT', USER_CONFIG.buttonIcons.ChatGPT, 'label-ChatGPT')); iconsContainer.appendChild(createIconInputField('Copy', 'buttonIconCopy', USER_CONFIG.buttonIcons.copy, 'label-copy')); iconsContainer.appendChild(createIconInputField('Download', 'buttonIconDownload', USER_CONFIG.buttonIcons.download, 'label-download')); iconsContainer.appendChild(createIconInputField('Fetch', 'buttonIconlazyLoad', USER_CONFIG.buttonIcons.lazyLoad, 'label-lazy')); iconsContainer.appendChild(createIconInputField('Settings', 'buttonIconSettings', USER_CONFIG.buttonIcons.settings, 'label-settings')); form.appendChild(iconsContainer); // info for button naming const buttonNaming = document.createElement('small'); buttonNaming.textContent = 'Enter "Label | domain.com" in the URL fields to rename the respective labels.'; buttonNaming.classList.add('CentAnni-info-text', 'button-naming'); form.appendChild(buttonNaming); // NotebookLM URL form.appendChild(createInputField(`${NotebookLMLabel} URL (Copy transcript, then open the website):`, 'targetNotebookLMUrl', USER_CONFIG.targetNotebookLMUrl, 'label-NotebookLM')); // ChatGPT URL form.appendChild(createInputField(`${ChatGPTLabel} URL (Copy transcript with the prompt, then open the website):`, 'targetChatGPTUrl', USER_CONFIG.targetChatGPTUrl, 'label-ChatGPT')); // SpacerTop10 const SpacerTop10 = document.createElement('div'); SpacerTop10.classList.add('spacer-10'); form.appendChild(SpacerTop10); // File Naming Format form.appendChild(createSelectField('Text File Naming Format:', 'label-download', 'fileNamingFormat', USER_CONFIG.fileNamingFormat, { 'title-channel': 'Title - Channel.txt (default)', 'channel-title': 'Channel - Title.txt', 'date-title-channel': 'uploadDate - Title - Channel.txt', 'date-channel-title': 'uploadDate - Channel - Title.txt', })); // transcript exporter form.appendChild(createCheckboxField('Enable Transcript Exporter (default: on)', 'YouTubeTranscriptExporter', USER_CONFIG.YouTubeTranscriptExporter)); // lazy transcript loading form.appendChild(createCheckboxField(`Load the Transcript Manually ${USER_CONFIG.buttonIcons.lazyLoad} (default: off)`, 'lazyTranscriptLoading', USER_CONFIG.lazyTranscriptLoading)); // use engagement-panel-searchable-transcript by default with PAmodern_transcript_view as a backup form.appendChild(createCheckboxField('Use Legacy Transcript Panel with Modern as a Backup (default: off)', 'useLegacyTranscriptPanel', USER_CONFIG.useLegacyTranscriptPanel)); // include Timestamps form.appendChild(createCheckboxField('Include Timestamps in the Transcript (default: on)', 'includeTimestamps', USER_CONFIG.includeTimestamps)); // include Chapter Headers form.appendChild(createCheckboxField('Include Chapter Headers in the Transcript (default: on)', 'includeChapterHeaders', USER_CONFIG.includeChapterHeaders)); // open in Same Tab form.appendChild(createCheckboxField('Open Links in the Same Tab (default: off)', 'openSameTab', USER_CONFIG.openSameTab)); // extra settings buttons const extraSettings = document.createElement('div'); extraSettings.classList.add('extra-button-container'); const buttonsLeft = document.createElement('button'); buttonsLeft.type = 'button'; buttonsLeft.textContent = 'Header Links'; buttonsLeft.classList.add('btn-style-settings'); buttonsLeft.onclick = () => showSubPanel(createLinksInHeaderContent(), 'linksInHeader'); const customCSSButton = document.createElement('button'); customCSSButton.type = 'button'; customCSSButton.textContent = 'Features & Styles'; customCSSButton.classList.add('btn-style-settings'); customCSSButton.onclick = () => showSubPanel(createCustomCSSContent(), 'createcustomCSS'); const colorCodeVideos = document.createElement('button'); colorCodeVideos.type = 'button'; colorCodeVideos.textContent = 'Color Code Videos'; colorCodeVideos.classList.add('btn-style-settings'); colorCodeVideos.onclick = () => showSubPanel(createColorCodeVideosContent(), 'colorCodeVideos'); extraSettings.appendChild(buttonsLeft); extraSettings.appendChild(customCSSButton); extraSettings.appendChild(colorCodeVideos); form.appendChild(extraSettings); // ChatGPT prompt const promptContainer = createTextareaField(`${ChatGPTLabel} Prompt:`, 'ChatGPTPrompt', USER_CONFIG.ChatGPTPrompt, 'label-ChatGPT'); // reset ChatGPT prompt const resetText = document.createElement('span'); resetText.textContent = 'Reset Prompt'; resetText.className = 'reset-prompt-text'; resetText.onmousedown = function (event) { event.preventDefault(); }; resetText.onclick = function () { const textarea = promptContainer.querySelector('textarea[name="ChatGPTPrompt"]'); if (textarea) textarea.value = DEFAULT_CONFIG.ChatGPTPrompt; }; const label = promptContainer.querySelector('label'); promptContainer.insertBefore(resetText, label); form.appendChild(promptContainer); // action buttons container const buttonContainer = document.createElement('div'); buttonContainer.classList.add('button-container-end'); // export and import button container const exportImportContainer = document.createElement('div'); exportImportContainer.classList.add('button-container-backup'); const exportButton = document.createElement('button'); exportButton.type = 'button'; exportButton.textContent = 'Export Settings'; exportButton.classList.add('btn-style-settings'); exportButton.onclick = exportSettings; const importButton = document.createElement('button'); importButton.type = 'button'; importButton.textContent = 'Import Settings'; importButton.classList.add('btn-style-settings'); importButton.onclick = importSettings; // Copyright const copyright = document.createElement('a'); copyright.href = 'https://github.com/TimMacy'; copyright.target = '_blank'; copyright.rel = 'noopener'; copyright.textContent = '© 2024–2026 Tim Macy'; copyright.title = 'Copyright © 2024–2026 Tim Macy'; copyright.classList.add('CentAnni-copyright'); const spacer = document.createElement('div'); spacer.style = 'flex: 1;'; // Save, Reset, and Cancel Buttons const buttonContainerSettings = document.createElement('div'); buttonContainerSettings.classList.add('button-container-settings'); const saveButton = document.createElement('button'); saveButton.type = 'button'; saveButton.textContent = 'Save'; saveButton.classList.add('btn-style-settings'); saveButton.onclick = saveSettings; const resetButton = document.createElement('button'); resetButton.type = 'button'; resetButton.textContent = 'Reset to Default'; resetButton.classList.add('btn-style-settings'); resetButton.onclick = async () => { const userConfirmed = window.confirm("All settings will be reset to their default values."); if (!userConfirmed) { return; } try { USER_CONFIG = { ...DEFAULT_CONFIG }; await GM.setValue('USER_CONFIG', USER_CONFIG); window.closeAlchemySettingsModal(true); showNotification('Settings have been reset to default!', true); setTimeout(() => location.reload(), 1000); } catch (error) { showNotification('Error resetting settings to default!'); console.error("YouTubeAlchemy: Error resetting settings to default:", error); } }; const cancelButton = document.createElement('button'); cancelButton.type = 'button'; cancelButton.textContent = 'Cancel'; cancelButton.classList.add('btn-style-settings'); cancelButton.onclick = () => window.closeAlchemySettingsModal(); exportImportContainer.appendChild(exportButton); exportImportContainer.appendChild(importButton); buttonContainerSettings.appendChild(copyright); buttonContainerSettings.appendChild(spacer); buttonContainerSettings.appendChild(saveButton); buttonContainerSettings.appendChild(resetButton); buttonContainerSettings.appendChild(cancelButton); buttonContainer.appendChild(exportImportContainer); buttonContainer.appendChild(buttonContainerSettings); form.appendChild(buttonContainer); modalContent.appendChild(form); modal.appendChild(modalContent); docBody.appendChild(modal); docBody.style.overflow = 'hidden'; setupModalHandlers(modal); // text area scroll on click let animationTriggered = false; document.querySelector('.chatgpt-prompt-textarea').onclick = function () { if (animationTriggered) return; animationTriggered = true; const modalContent = this.closest('#CentAnni-main-settings-form'); if (!modalContent) { animationTriggered = false; return; } const textArea = this; const buttons = modalContent.querySelector('.button-container-end'); const startHeight = 50; const endHeight = 630; const duration = 800; const modalContentRect = modalContent.getBoundingClientRect(); const textAreaRect = textArea.getBoundingClientRect(); const buttonsRect = buttons.getBoundingClientRect(); const textAreaTop = textAreaRect.top - modalContentRect.top + modalContent.scrollTop; const buttonsBottom = buttonsRect.bottom - modalContentRect.top + modalContent.scrollTop; const contentHeightAfterExpansion = buttonsBottom + (endHeight - startHeight); const modalVisibleHeight = modalContent.clientHeight; const contentWillFit = contentHeightAfterExpansion <= modalVisibleHeight; const maxScrollTop = textAreaTop; let desiredScrollTop; if (contentWillFit) desiredScrollTop = contentHeightAfterExpansion - modalVisibleHeight; else desiredScrollTop = buttonsBottom + (endHeight - startHeight) - modalVisibleHeight; const newScrollTop = Math.min(desiredScrollTop, maxScrollTop); const startScrollTop = modalContent.scrollTop; const scrollDistance = newScrollTop - startScrollTop; const startTime = performance.now(); function animateScroll(currentTime) { const elapsedTime = currentTime - startTime; const progress = Math.min(elapsedTime / duration, 1); const easeProgress = progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress; const currentScrollTop = startScrollTop + scrollDistance * easeProgress; modalContent.scrollTop = currentScrollTop; if (progress < 1) requestAnimationFrame(animateScroll); else animationTriggered = false; } requestAnimationFrame(animateScroll); }; // modal event handlers function setupModalHandlers(modal) { // close modal on overlay click function closeModalOverlayClickHandler(event) { const mainModal = document.getElementById('yt-alchemy-settings-modal'); const openSubPanel = document.querySelector('.sub-panel-overlay.active'); if (openSubPanel && event.target === openSubPanel) { openSubPanel.classList.remove('active'); return; } if (mainModal && event.target === mainModal) { window.closeAlchemySettingsModal(); return; } } document.addEventListener('click', closeModalOverlayClickHandler); // close modal with ESC key const escKeyListener = function (event) { if (event.key === 'Escape' && event.type === 'keydown') { const openSubPanel = document.querySelector('.sub-panel-overlay.active'); if (openSubPanel) { openSubPanel.classList.remove('active'); } else { const modal = document.getElementById('yt-alchemy-settings-modal'); if (modal) window.closeAlchemySettingsModal(); } } }; window.addEventListener('keydown', escKeyListener); const cleanupModalEventListeners = () => { window.removeEventListener('keydown', escKeyListener); document.removeEventListener('click', closeModalOverlayClickHandler); }; window.closeAlchemySettingsModal = (reload = false) => { if (reload) modal.classList.add('reload'); modal.classList.remove('active'); docBody.style.overflow = ''; cleanupModalEventListeners(); }; // clean up document.addEventListener('yt-navigate-start', cleanupModalEventListeners, { once: true }); } // sub-panels function showSubPanel(panelContent, panelId) { let subPanelOverlay = document.querySelector(`.sub-panel-overlay[data-panel-id="${panelId}"]`); if (!subPanelOverlay) { subPanelOverlay = document.createElement('div'); subPanelOverlay.classList.add('sub-panel-overlay'); subPanelOverlay.setAttribute('data-panel-id', panelId); const subPanel = document.createElement('div'); subPanel.classList.add('sub-panel'); const closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.textContent = 'Close'; closeButton.classList.add('btn-style-settings'); closeButton.onclick = () => { subPanelOverlay.classList.remove('active'); }; subPanel.appendChild(closeButton); if (panelContent) { subPanel.appendChild(panelContent); } subPanelOverlay.appendChild(subPanel); docBody.appendChild(subPanelOverlay); } subPanelOverlay.classList.add('active'); } // header links function createLinksInHeaderContent() { const form = document.createElement('form'); form.id = 'links-in-header-form'; const subPanelHeader = document.createElement('div'); subPanelHeader.classList.add('sub-panel-header'); subPanelHeader.textContent = 'Customize Header Links and Configure the Guide'; form.appendChild(subPanelHeader); const infoLinksHeader = document.createElement('small'); infoLinksHeader.textContent = "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. If the Guide is hidden, a replacement icon will prepend the links, while retaining the default functionality of opening and closing the sidebar."; infoLinksHeader.classList.add('CentAnni-info-text'); form.appendChild(infoLinksHeader); const sidebarContainer = document.createElement('div'); sidebarContainer.classList.add('sidebar-container'); // hide YT Guide and replacement icon const checkboxField = createCheckboxField('Hide and Auto-Close the Guide', 'mButtonDisplay', USER_CONFIG.mButtonDisplay); sidebarContainer.appendChild(checkboxField); const inputField = createInputField('Guide Replacement Icon', 'mButtonText', USER_CONFIG.mButtonText, 'label-mButtonText'); sidebarContainer.appendChild(inputField); form.appendChild(sidebarContainer); // function to create a link input group function createButtonInputGroup(linkNumber) { const container = document.createElement('div'); container.classList.add('links-header-container'); // link text const textField = createInputField(`Link ${linkNumber} Text`, `buttonLeft${linkNumber}Text`, USER_CONFIG[`buttonLeft${linkNumber}Text`], `label-buttonLeft${linkNumber}Text`); container.appendChild(textField); // link URL const urlField = createInputField(`Link ${linkNumber} URL`, `buttonLeft${linkNumber}Url`, USER_CONFIG[`buttonLeft${linkNumber}Url`], `label-buttonLeft${linkNumber}Url`); container.appendChild(urlField); return container; } // create input groups for links 1 through 6 for (let i = 1; i <= 10; i++) { form.appendChild(createButtonInputGroup(i)); } return form; } // custom css function createCustomCSSContent() { const form = document.createElement('form'); form.id = 'custom-css-form'; const subPanelHeader = document.createElement('div'); subPanelHeader.classList.add('sub-panel-header'); subPanelHeader.textContent = "Customize YouTube's Appearance and Manage Features"; form.appendChild(subPanelHeader); // general const general = document.createElement('div'); general.textContent = 'General'; general.classList.add('button-icons', 'features-text'); form.appendChild(general); // move settings button into guide const settingsGuide = createCheckboxField('Move Settings Button into the YouTube Guide (default: off)', 'settingsGuide', USER_CONFIG.settingsGuide); form.appendChild(settingsGuide); // prevent execution in background tabs form.appendChild(createCheckboxField('Important for Chrome! (default: on)', 'preventBackgroundExecution', USER_CONFIG.preventBackgroundExecution)); // info for Chrome const description = document.createElement('small'); description.textContent = 'Ensures compatibility and prevents early script execution in background tabs. Disabling this can cause unexpected behavior or stop features from working.'; description.classList.add('CentAnni-info-text'); form.appendChild(description); // dim watched videos const videosWatchedOpacity = createSliderInputField('Change Opacity of Watched Videos (default 0.5):', 'videosWatchedOpacity', USER_CONFIG.videosWatchedOpacity, '0', '1', '0.1'); form.appendChild(videosWatchedOpacity); if (!labeledLanguageOptions) createLabeledLanguageOptions(); const { standard: standardLanguageOptions, withOff: languageOptionsWithOff, withNone: languageOptionsWithNone } = labeledLanguageOptions; // default audio language form.appendChild(createSelectField('Audio Language:', 'label-audio-language', 'defaultAudioLanguage', USER_CONFIG.defaultAudioLanguage, standardLanguageOptions)); // default subtitle language form.appendChild(createSelectField('Subtitle Language:', 'label-subtitle-language', 'defaultSubtitleLanguage', USER_CONFIG.defaultSubtitleLanguage, languageOptionsWithOff)); // default transcript language in legacy panel form.appendChild(createSelectField('Legacy Transcript Panel Language:', 'label-transcript-language', 'defaultTranscriptLanguage', USER_CONFIG.defaultTranscriptLanguage, standardLanguageOptions)); // secondary language form.appendChild(createSelectField('Secondary Language:', 'label-secondary-language', 'secondaryLanguage', USER_CONFIG.secondaryLanguage, languageOptionsWithNone)); // default channel page form.appendChild(createSelectField('Default Channel Page:', 'label-channel-page', 'defaultChannelPage', USER_CONFIG.defaultChannelPage, { 'home': 'Home (default)', 'videos': 'Videos', 'shorts': 'Shorts', 'streams': 'Live', 'podcasts': 'Podcasts', 'playlists': 'Playlists', 'posts': 'Posts' })); // default video quality form.appendChild(createSelectField('Video Quality:', 'label-Video-Quality', 'defaultQuality', USER_CONFIG.defaultQuality, { 'auto': 'Auto (default)', 'highest': 'Highest Available', 'highres': '4320p - 8K', 'hd2160': '2160p - 4K', 'hd1440': '1440p - QHD', 'hd1080': '1080p - FHD', 'hd720': '720p - HD', 'large': '480p', 'medium': '360p', 'small': '240p', 'tiny': '144p', 'lowest': 'Lowest Available' })); // title text transform form.appendChild(createSelectField('Title Case:', 'label-Text-Transform', 'textTransform', USER_CONFIG.textTransform, { 'none': 'Original (default) - The quick brown fox jumps over the lazy dog.', 'uppercase': 'Uppercase - THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.', 'lowercase': 'Lowercase - the quick brown fox jumps over the lazy dog.', 'capitalize': 'Capitalize - The Quick Brown Fox Jumps Over The Lazy Dog.' })); // font size const defaultFontSizeField = createNumberInputField('Font Size (default: 10)', 'defaultFontSize', USER_CONFIG.defaultFontSize); form.appendChild(defaultFontSizeField); // videos per row const videosPerRow = createNumberInputField("Number of Videos per Row (default: 0 | dynamic based on available space)", 'videosPerRow', USER_CONFIG.videosPerRow, { step: 1 }); form.appendChild(videosPerRow); // width of sidebar const sidebarWidth = createNumberInputField("Sidebar Width (default: 0 | YouTube's default)", 'sidebarWidth', USER_CONFIG.sidebarWidth, { min: -9999, max: 9999, step: 1 }); form.appendChild(sidebarWidth); // search bar position const searchPosition = createNumberInputField("Search Bar Position (default: 0 | a negative value moves it left)", 'searchPosition', USER_CONFIG.searchPosition, { min: -9999, max: 9999, step: 1 }); form.appendChild(searchPosition); // tab view below player min space const spaceBelowPlayer = createNumberInputField("Minimum Space Below Player in Default Layout When Tab View and Compact Layout Are Enabled (default: 110px)", 'spaceBelowPlayer', USER_CONFIG.spaceBelowPlayer, { min: 100, max: 1000, step: 1 }); form.appendChild(spaceBelowPlayer); // playback speed const playSpeed = document.createElement('div'); playSpeed.textContent = 'Playback Speed'; playSpeed.classList.add('button-icons', 'features-text'); form.appendChild(playSpeed); // info for playback speed const descriptionPlaybackSpeed = document.createElement('small'); descriptionPlaybackSpeed.textContent = `To save a custom playback speed for a channel, set the desired speed on a video page first, then click the playback speed display.\nKeyboard shortcuts:\n • "${USER_CONFIG.playbackSpeedDecrease}" or "<" to slow down the video playback rate by 0.25x\n • "${USER_CONFIG.playbackSpeedToggle}" to toggle between 1x and the saved speed for VODs\n • "${USER_CONFIG.playbackSpeedIncrease}" or ">" to speed up the video playback rate by 0.25x`; descriptionPlaybackSpeed.classList.add('CentAnni-info-text', 'playback-speed'); form.appendChild(descriptionPlaybackSpeed); // playback speed const playbackSpeedContainer = document.createElement('div'); playbackSpeedContainer.className = 'playback-speed-container'; const playbackSpeed = createCheckboxField('Enabled (default: on)', 'playbackSpeed', USER_CONFIG.playbackSpeed); const playbackSpeedValue = createNumberInputField('Playback Speed for VODs\n(defaults to 1x for live videos)', 'playbackSpeedValue', USER_CONFIG.playbackSpeedValue); playbackSpeedContainer.appendChild(playbackSpeedValue); playbackSpeedContainer.appendChild(playbackSpeed); form.appendChild(playbackSpeedContainer); // playback speed custom keys const keysRow = document.createElement('div'); keysRow.classList.add('playback-speed-keys'); // labels const labelDiv = document.createElement('div'); labelDiv.classList.add('playback-speed-labels', 'label-style-settings'); const keyLabel = document.createElement('div'); keyLabel.textContent = 'Key'; const speedLabel = document.createElement('div'); speedLabel.textContent = 'Speed'; labelDiv.appendChild(keyLabel); labelDiv.appendChild(speedLabel); keysRow.appendChild(labelDiv); // keys const keyButtonContainer = document.createElement('div'); keyButtonContainer.classList.add('key-button-container'); // speed const setSpeedContainer = document.createElement('div'); setSpeedContainer.classList.add('set-speed-container'); for (let i = 1; i <= 8; i++) { const keyField = createNumberInputField('', `playbackSpeedKey${i}`, USER_CONFIG[`playbackSpeedKey${i}`], { type: 'text' }); keyButtonContainer.appendChild(keyField); const speedField = createNumberInputField('', `playbackSpeedKey${i}s`, USER_CONFIG[`playbackSpeedKey${i}s`]); setSpeedContainer.appendChild(speedField); } // ASD const defaultKeyConfigs = [ { label: 'Decrease', id: 'playbackSpeedDecrease' }, { label: 'Toggle', id: 'playbackSpeedToggle' }, { label: 'Increase', id: 'playbackSpeedIncrease' } ]; defaultKeyConfigs.forEach(config => { const keyField = createNumberInputField('', config.id, USER_CONFIG[config.id], { type: 'text' }); keyButtonContainer.appendChild(keyField); const labelDiv = document.createElement('div'); labelDiv.classList.add('label-style-settings'); labelDiv.textContent = config.label; setSpeedContainer.appendChild(labelDiv); }); keysRow.appendChild(keyButtonContainer); keysRow.appendChild(setSpeedContainer); form.appendChild(keysRow); // playback speed buttons const playbackSpeedBtns = createCheckboxField('Add Additional Playback Speed Buttons (default: off)', 'playbackSpeedBtns', USER_CONFIG.playbackSpeedBtns); form.appendChild(playbackSpeedBtns); // features const features = document.createElement('div'); features.textContent = 'Features'; features.classList.add('button-icons', 'features-text'); form.appendChild(features); // auto theater mode const container = document.createElement('div'); container.className = 'CentAnni-container-theater-mode'; const autoTheaterMode = createCheckboxField('Auto Theater Mode (default: off)', 'autoTheaterMode', USER_CONFIG.autoTheaterMode); const autoTheaterModeSpan = document.createElement('span'); autoTheaterModeSpan.textContent = 'Unless'; autoTheaterModeSpan.className = "checkbox-label"; const autoTheaterModeNotVerticalVideo = createCheckboxField('Vertical Video', 'autoTheaterModeNotVerticalVideo', USER_CONFIG.autoTheaterModeNotVerticalVideo); const autoTheaterModeNotLargeWindow = createCheckboxField('Large Window', 'autoTheaterModeNotLargeWindow', USER_CONFIG.autoTheaterModeNotLargeWindow); const autoTheaterModeNotPL = createCheckboxField('Playlist Page', 'autoTheaterModeNotPL', USER_CONFIG.autoTheaterModeNotPL); container.append(autoTheaterMode, autoTheaterModeSpan, autoTheaterModeNotVerticalVideo, autoTheaterModeNotLargeWindow, autoTheaterModeNotPL); form.appendChild(container); // auto exit fullscreen on video end const autoExitFullscreen = createCheckboxField('Auto-Exit Fullscreen When Video Ends (default: off)', 'autoExitFullscreen', USER_CONFIG.autoExitFullscreen); form.appendChild(autoExitFullscreen); // cinema mode const enableCinemaMode = createCheckboxField('Cinema Mode (default: off)', 'enableCinemaMode', USER_CONFIG.enableCinemaMode); form.appendChild(enableCinemaMode); // info for cinema mode const descriptionCinemaMode = document.createElement('small'); descriptionCinemaMode.textContent = 'Adds a button to the bottom right in theater mode. Once activated, all UI elements are hidden while the mouse is outside the page.'; descriptionCinemaMode.classList.add('CentAnni-info-text'); form.appendChild(descriptionCinemaMode); // max video size const maxVidSize = createCheckboxField('Max Video Size in Default Layout (default: off)', 'maxVidSize', USER_CONFIG.maxVidSize); form.appendChild(maxVidSize); // prevent autoplay const preventAutoplay = createCheckboxField('Prevent Autoplay (default: off)', 'preventAutoplay', USER_CONFIG.preventAutoplay); form.appendChild(preventAutoplay); // prevent autoplay also in playlists const preventAutoplayPL = createCheckboxField('Also Prevent Autoplay in Playlists (default: off)', 'preventAutoplayPL', USER_CONFIG.preventAutoplayPL); form.appendChild(preventAutoplayPL); // disable play on hover const disablePlayOnHover = createCheckboxField('Disable Play on Hover (default: off)', 'disablePlayOnHover', USER_CONFIG.disablePlayOnHover); form.appendChild(disablePlayOnHover); // sort notifications chronologically const chronologicalNotifications = createCheckboxField('Sort Notifications Chronologically (default: on)', 'chronologicalNotifications', USER_CONFIG.chronologicalNotifications); form.appendChild(chronologicalNotifications); // hide read notifications const hideReadNotifications = createCheckboxField('Limit Notifications and Hide Read (default: off)', 'hideReadNotifications', USER_CONFIG.hideReadNotifications); form.appendChild(hideReadNotifications); // expand video description const expandVideoDescription = createCheckboxField('Auto Expand Video Description (default: off)', 'expandVideoDescription', USER_CONFIG.expandVideoDescription); form.appendChild(expandVideoDescription); // restore feed filter chip on the homepage const feedFilterChips = createCheckboxField('Restore Homepage Filter Selection (default: off)', 'feedFilterChips', USER_CONFIG.feedFilterChips); form.appendChild(feedFilterChips); // restore feed filter chip for suggested videos const feedFilterChipsWatch = createCheckboxField('Restore Suggested Videos Filter Selection (default: off)', 'feedFilterChipsWatch', USER_CONFIG.feedFilterChipsWatch); form.appendChild(feedFilterChipsWatch); // close chat window const closeChatWindow = createCheckboxField('Auto Close Initial Chat Windows (default: off)', 'closeChatWindow', USER_CONFIG.closeChatWindow); form.appendChild(closeChatWindow); // rss feed button on channel page const channelRSSBtn = createCheckboxField('Add RSS Feed Button to Channel Pages (default: off)', 'channelRSSBtn', USER_CONFIG.channelRSSBtn); form.appendChild(channelRSSBtn); // playlist button on channel page const channelPlaylistBtn = createCheckboxField('Add Playlist Buttons to Channel Pages (default: off)', 'channelPlaylistBtn', USER_CONFIG.channelPlaylistBtn); form.appendChild(channelPlaylistBtn); // playlist direction buttons in playlist panel const playlistDirectionBtns = createCheckboxField('Add Direction Buttons to Playlist Panels (default: on)', 'playlistDirectionBtns', USER_CONFIG.playlistDirectionBtns); form.appendChild(playlistDirectionBtns); // open playlist videos without being in a playlist const playlistLinks = createCheckboxField('Open Playlist Videos Without Being in a Playlist When Clicking the Thumbnail or Title (default: off)', 'playlistLinks', USER_CONFIG.playlistLinks); form.appendChild(playlistLinks); // show trash can icon on owned playlists const playlistTrashCan = createCheckboxField('Show Trash Can Icon on Owned Playlists to Quickly Remove Videos (default: off)', 'playlistTrashCan', USER_CONFIG.playlistTrashCan); form.appendChild(playlistTrashCan); // add video to queue btn to watch later const playlistQueueBtn = createCheckboxField('Add "Add to Queue" Button to the Watch Later Playlist | Needs Mini Player to Work (default: off)', 'playlistQueueBtn', USER_CONFIG.playlistQueueBtn); form.appendChild(playlistQueueBtn); // remove watched videos from watch later const plWLBtn = createCheckboxField('Add "Remove Watched Videos" and "Toggle Watched" Buttons to the Watch Later Playlist (default: off)', 'plWLBtn', USER_CONFIG.plWLBtn); form.appendChild(plWLBtn); // sort comments new first const commentsNewFirst = createCheckboxField('Sort Comments to "Newest First" (default: off)', 'commentsNewFirst', USER_CONFIG.commentsNewFirst); form.appendChild(commentsNewFirst); // auto open chapter panel const autoOpenChapters = createCheckboxField('Automatically Open Chapter Panels (default: on)', 'autoOpenChapters', USER_CONFIG.autoOpenChapters); form.appendChild(autoOpenChapters); // auto open transcript panel const autoOpenTranscript = createCheckboxField('Automatically Open Transcript Panels (default: off)', 'autoOpenTranscript', USER_CONFIG.autoOpenTranscript); form.appendChild(autoOpenTranscript); // auto open comments const autoOpenComments = createCheckboxField('Automatically Open Comments | Only Works with Tab View Enabled (default: off)', 'autoOpenComments', USER_CONFIG.autoOpenComments); form.appendChild(autoOpenComments); // highlight active transcript section const highlightTranscript = createCheckboxField('Highlight Active Section in Transcript Panels (default: off)', 'highlightTranscript', USER_CONFIG.highlightTranscript); form.appendChild(highlightTranscript); // enable subtitles when muted const subtitlesWhenMuted = createCheckboxField('Automatically Enable Subtitles When Muted (default: off)', 'subtitlesWhenMuted', USER_CONFIG.subtitlesWhenMuted); form.appendChild(subtitlesWhenMuted); // on rewind turn on subtitles for 10 seconds const subtitlesWhenRewind = createCheckboxField('Show Subtitles for 10 Seconds When Rewinding with "J" (default: off)', 'subtitlesWhenRewind', USER_CONFIG.subtitlesWhenRewind); form.appendChild(subtitlesWhenRewind); // 1x playback speed for music videos const VerifiedArtist = createCheckboxField('Maintain 1x Playback Speed for Music Videos (default: off)', 'VerifiedArtist', USER_CONFIG.VerifiedArtist); form.appendChild(VerifiedArtist); // 1080p enhanced bitrate const defaultQualityPremium = createCheckboxField('Use Enhanced Bitrate for 1080p Videos | Premium Required! (default: off)', 'defaultQualityPremium', USER_CONFIG.defaultQualityPremium); form.appendChild(defaultQualityPremium); // persistent progress bar const progressBar = createCheckboxField('Persistent Progress Bar with Chapter Markers and SponsorBlock Support (default: on)', 'progressBar', USER_CONFIG.progressBar); form.appendChild(progressBar); // display remaining time minus SponsorBlock segments const displayRemainingTime = createCheckboxField('Display Remaining Time Under Videos Adjusted for Playback Speed (default: on)', 'displayRemainingTime', USER_CONFIG.displayRemainingTime); form.appendChild(displayRemainingTime); // info for remaining time minus segments const descriptionRemainingTime = document.createElement('small'); descriptionRemainingTime.textContent = 'To also include skipped SponsorBlock segments, ensure "Show time with skips removed" is enabled in that browser extension under "Interface".'; descriptionRemainingTime.classList.add('CentAnni-info-text'); form.appendChild(descriptionRemainingTime); const showRemainingCompact = createCheckboxField('Compact Version for Remaining Time (default: off)', 'showRemainingCompact', USER_CONFIG.showRemainingCompact); form.appendChild(showRemainingCompact); const fsRemainingTime = createCheckboxField('Hide Remaining Time and Chapters in Fullscreen (default: off)', 'fsRemainingTime', USER_CONFIG.fsRemainingTime); form.appendChild(fsRemainingTime); // layout changes const layoutChanges = document.createElement('div'); layoutChanges.textContent = 'Layout Changes'; layoutChanges.classList.add('button-icons', 'features-text'); form.appendChild(layoutChanges); // tab view on video page const videoTabView = createCheckboxField('Tab View on Video Pages | Only Works with Two-Columns Layout (default: on)', 'videoTabView', USER_CONFIG.videoTabView); form.appendChild(videoTabView); // toggle theater mode w/ active tab const toggleTheaterModeBtn = createCheckboxField('Toggle Theater Mode by Clicking the Active Tab (default: on)', 'toggleTheaterModeBtn', USER_CONFIG.toggleTheaterModeBtn); form.appendChild(toggleTheaterModeBtn); // show chapters - only in tab view const tabViewChapters = createCheckboxField('Show Chapters Under Videos | Only Works with Tab View Enabled (default: on)', 'tabViewChapters', USER_CONFIG.tabViewChapters); form.appendChild(tabViewChapters); // max secondary panel height const maxPanelHeight = createCheckboxField('Max Panel Height in Default View | Only Works with Tab View Enabled (default: off)', 'maxPanelHeight', USER_CONFIG.maxPanelHeight); form.appendChild(maxPanelHeight); // no animation switch theater and default view const noAnimation = createCheckboxField('Disable Animation When Switching Between Theater mode and Default view (default: off)', 'noAnimation', USER_CONFIG.noAnimation); form.appendChild(noAnimation); // disable ambient mode const noAmbientMode = createCheckboxField('Disable Ambient Mode (default: off)', 'noAmbientMode', USER_CONFIG.noAmbientMode); form.appendChild(noAmbientMode); // disable video zoom const noVideoZoom = createCheckboxField('Disable Video Zoom (default: off)', 'noVideoZoom', USER_CONFIG.noVideoZoom); form.appendChild(noVideoZoom); // compact layout const compactLayout = createCheckboxField('Compact Layout (default: off)', 'compactLayout', USER_CONFIG.compactLayout); form.appendChild(compactLayout); // compact layout video page const compactLayoutVideo = createCheckboxField('Compact Video Page Layout (default: off)', 'compactLayoutVideo', USER_CONFIG.compactLayoutVideo); form.appendChild(compactLayoutVideo); // square and compact search bar const squareSearchBar = createCheckboxField('Square and Compact Search Bar (default: off)', 'squareSearchBar', USER_CONFIG.squareSearchBar); form.appendChild(squareSearchBar); // square design const squareDesign = createCheckboxField('Square Design (default: off)', 'squareDesign', USER_CONFIG.squareDesign); form.appendChild(squareDesign); // square avatars const squareAvatars = createCheckboxField('Square Avatars (default: off)', 'squareAvatars', USER_CONFIG.squareAvatars); form.appendChild(squareAvatars); // hide comment section const hideCommentsSection = createCheckboxField('Hide Comments Section (default: off)', 'hideCommentsSection', USER_CONFIG.hideCommentsSection); form.appendChild(hideCommentsSection); // hide related video section const hideVideosSection = createCheckboxField('Hide Suggested Videos (default: off)', 'hideVideosSection', USER_CONFIG.hideVideosSection); form.appendChild(hideVideosSection); // hide shorts const hideShorts = createCheckboxField('Hide Shorts (default: off)', 'hideShorts', USER_CONFIG.hideShorts); form.appendChild(hideShorts); // redirect shorts const redirectShorts = createCheckboxField('Redirect Shorts to Standard Video Pages (default: off)', 'redirectShorts', USER_CONFIG.redirectShorts); form.appendChild(redirectShorts); // modify or hide ui elements const uielements = document.createElement('div'); uielements.textContent = 'Modify or Hide UI Elements'; uielements.classList.add('button-icons', 'features-text'); form.appendChild(uielements); // hide voice search button const hideVoiceSearch = createCheckboxField('Hide "Voice Search" Button (default: off)', 'hideVoiceSearch', USER_CONFIG.hideVoiceSearch); form.appendChild(hideVoiceSearch); // hide create button const hideCreateButton = createCheckboxField('Hide "Create" Button (default: off)', 'hideCreateButton', USER_CONFIG.hideCreateButton); form.appendChild(hideCreateButton); // hide notification button const hideNotificationBtn = createCheckboxField('Hide "Notification" Button (default: off)', 'hideNotificationBtn', USER_CONFIG.hideNotificationBtn); form.appendChild(hideNotificationBtn); // hide notification count const hideNotificationBadge = createCheckboxField('Hide Notification Badge (default: off)', 'hideNotificationBadge', USER_CONFIG.hideNotificationBadge); form.appendChild(hideNotificationBadge); // hide avatar const hideOwnAvatar = createCheckboxField('Hide Own Avatar in the Header (default: off)', 'hideOwnAvatar', USER_CONFIG.hideOwnAvatar); form.appendChild(hideOwnAvatar); // hide YouTube brand text within the header const hideBrandText = createCheckboxField('Hide YouTube Brand Text in the Header (default: off)', 'hideBrandText', USER_CONFIG.hideBrandText); form.appendChild(hideBrandText); // color picker country code - toggle | color picker const visibleCountryCodeColor = document.createElement('div'); visibleCountryCodeColor.classList.add('videos-colorpicker-container', 'selection-color-container'); const visibleCountryCode = createCheckboxField('Keep Country Code Visible When Hiding Brand Text (default: off)', 'visibleCountryCode', USER_CONFIG.visibleCountryCode); visibleCountryCodeColor.appendChild(visibleCountryCode); const visibleCountryCodePicker = createColorPicker('Country Code Text Color', 'visibleCountryCodeColor'); visibleCountryCodeColor.appendChild(visibleCountryCodePicker); form.appendChild(visibleCountryCodeColor); // small subscribed button const smallSubscribeButton = createCheckboxField('Small Subscribed Button Under Videos | Displays Only the Notification Icon (default: off)', 'smallSubscribeButton', USER_CONFIG.smallSubscribeButton); form.appendChild(smallSubscribeButton); // hide join button const hideJoinButton = createCheckboxField('Hide the Join Button Under Videos and on Channel Pages (default: off)', 'hideJoinButton', USER_CONFIG.hideJoinButton); form.appendChild(hideJoinButton); // display full title const displayFullTitle = createCheckboxField('Display Full Titles (default: off)', 'displayFullTitle', USER_CONFIG.displayFullTitle); form.appendChild(displayFullTitle); // custom selection color - toggle | light mode | dark mode const selectionColorContainer = document.createElement('div'); selectionColorContainer.classList.add('videos-colorpicker-container', 'selection-color-container'); const selectionColor = createCheckboxField('Custom Selection Color (default: off)', 'selectionColor', USER_CONFIG.selectionColor); selectionColorContainer.appendChild(selectionColor); const lightModeColorPicker = createColorPicker('Light Mode', 'lightModeSelectionColor'); selectionColorContainer.appendChild(lightModeColorPicker); const darkModeColorPicker = createColorPicker('Dark Mode', 'darkModeSelectionColor'); selectionColorContainer.appendChild(darkModeColorPicker); form.appendChild(selectionColorContainer); // color picker progress bar - toggle | color picker const progressbarColorPicker = document.createElement('div'); progressbarColorPicker.classList.add('videos-colorpicker-container', 'selection-color-container'); const playProgressColor = createCheckboxField('Custom Color for on Hover Progress Bar (default: off)', 'playProgressColor', USER_CONFIG.playProgressColor); progressbarColorPicker.appendChild(playProgressColor); const progressbarColorPickerColor = createColorPicker('Progress Bar Color', 'progressbarColorPicker'); progressbarColorPicker.appendChild(progressbarColorPickerColor); form.appendChild(progressbarColorPicker); // pure b/w bg const pureBWBackground = createCheckboxField('Pure Black-and-White Background (default: off)', 'pureBWBackground', USER_CONFIG.pureBWBackground); form.appendChild(pureBWBackground); // no frosted glass const noFrostedGlass = createCheckboxField('No Frosted Glass Effect (default: off)', 'noFrostedGlass', USER_CONFIG.noFrostedGlass); form.appendChild(noFrostedGlass); // hide video scrubber const removeScrubber = createCheckboxField('Hide Video Scrubber (default: off)', 'removeScrubber', USER_CONFIG.removeScrubber); form.appendChild(removeScrubber); // hide video end cards const hideEndCards = createCheckboxField('Hide Video End Cards (default: off)', 'hideEndCards', USER_CONFIG.hideEndCards); form.appendChild(hideEndCards); // hide end screens const hideEndscreen = createCheckboxField('Hide End Screens (default: off)', 'hideEndscreen', USER_CONFIG.hideEndscreen); form.appendChild(hideEndscreen); // bottom gradient lower height and different bg image const gradientBottom = createCheckboxField('Less Intrusive Bottom Gradient (default: off)', 'gradientBottom', USER_CONFIG.gradientBottom); form.appendChild(gradientBottom); // show PIP btn const showPipBtn = createCheckboxField('Show "Picture-in-Picture" Button (default: on)', 'showPipBtn', USER_CONFIG.showPipBtn); form.appendChild(showPipBtn); // move save btn into menu const moveSaveBtn = createCheckboxField('Move "Save" Button into Menu (default: off)', 'moveSaveBtn', USER_CONFIG.moveSaveBtn); form.appendChild(moveSaveBtn); // hide the disabled autoplay button const hideAutoplayBtn = createCheckboxField('Hide "Autoplay" Button When Disabled (default: off)', 'hideAutoplayBtn', USER_CONFIG.hideAutoplayBtn); form.appendChild(hideAutoplayBtn); // hide play next button const hidePlayNextButton = createCheckboxField('Hide "Previous" and "Play Next" Buttons (default: off)', 'hidePlayNextButton', USER_CONFIG.hidePlayNextButton); form.appendChild(hidePlayNextButton); // hide airplay button const hideAirplayButton = createCheckboxField('Hide "Airplay" Button (default: off)', 'hideAirplayButton', USER_CONFIG.hideAirplayButton); form.appendChild(hideAirplayButton); // hide ask btn const hideAskButton = createCheckboxField('Hide "Ask" Button (default: off)', 'hideAskButton', USER_CONFIG.hideAskButton); form.appendChild(hideAskButton); // hide share btn global const hideShareBtnGlobal = createCheckboxField('Hide "Share" Button (default: off)', 'hideShareBtnGlobal', USER_CONFIG.hideShareBtnGlobal); form.appendChild(hideShareBtnGlobal); // hide share button const hideShareButton = createCheckboxField('Hide "Share" Button Only Under Videos (default: off)', 'hideShareButton', USER_CONFIG.hideShareButton); form.appendChild(hideShareButton); // hide add comment const hideAddComment = createCheckboxField('Hide "Add Comment" Textfield (default: off)', 'hideAddComment', USER_CONFIG.hideAddComment); form.appendChild(hideAddComment); // hide reply comment button const hideReplyButton = createCheckboxField('Hide Comment "Reply" Button (default: off)', 'hideReplyButton', USER_CONFIG.hideReplyButton); form.appendChild(hideReplyButton); // hide blue info panel under video const hideInfoPanel = createCheckboxField('Hide Blue Info Panels (default: off)', 'hideInfoPanel', USER_CONFIG.hideInfoPanel); form.appendChild(hideInfoPanel); // hide fundraiser const hideFundraiser = createCheckboxField('Hide Fundraiser Icons and Panels (default: off)', 'hideFundraiser', USER_CONFIG.hideFundraiser); form.appendChild(hideFundraiser); // hide hashtags under video const hideHashtags = createCheckboxField('Hide Hashtags Under Videos (default: off)', 'hideHashtags', USER_CONFIG.hideHashtags); form.appendChild(hideHashtags); // hide product span const hideProdTxt = createCheckboxField('Hide "X products" Text Under Videos (default: off)', 'hideProdTxt', USER_CONFIG.hideProdTxt); form.appendChild(hideProdTxt); // hide product suggestion under videos const hideProdSug = createCheckboxField('Hide Product Suggestion Under Videos (default: off)', 'hideProdSug', USER_CONFIG.hideProdSug); form.appendChild(hideProdSug); // hide ad slot const hideAdSlots = createCheckboxField('Hide Ad Slots on the Home Page (default: off)', 'hideAdSlots', USER_CONFIG.hideAdSlots); form.appendChild(hideAdSlots); // hide pay to watch const hidePayToWatch = createCheckboxField('Hide "Pay to Watch" Featured Videos (default: off)', 'hidePayToWatch', USER_CONFIG.hidePayToWatch); form.appendChild(hidePayToWatch); // hide free with ads const hideFreeWithAds = createCheckboxField('Hide "Free with ads" Videos (default: off)', 'hideFreeWithAds', USER_CONFIG.hideFreeWithAds); form.appendChild(hideFreeWithAds); // hide members only const hideMembersOnly = createCheckboxField('Hide Members Only Featured Videos (default: off)', 'hideMembersOnly', USER_CONFIG.hideMembersOnly); form.appendChild(hideMembersOnly); // hide playables const hidePlayables = createCheckboxField('Hide "YouTube Playables" (default: off)', 'hidePlayables', USER_CONFIG.hidePlayables); form.appendChild(hidePlayables); // hide news on home const hideNewsHome = createCheckboxField('Hide "Breaking News" on the Home Page (default: off)', 'hideNewsHome', USER_CONFIG.hideNewsHome); form.appendChild(hideNewsHome); // hide mix playlists on home const hidePlaylistsHome = createCheckboxField('Hide Mix-Playlists on the Home Page (default: off)', 'hidePlaylistsHome', USER_CONFIG.hidePlaylistsHome); form.appendChild(hidePlaylistsHome); // hide episode playlists on home const hideEpisodesHome = createCheckboxField('Hide Episode-Playlists on the Home Page (default: off)', 'hideEpisodesHome', USER_CONFIG.hideEpisodesHome); form.appendChild(hideEpisodesHome); // hide latest posts on home const hideLatestPostsHome = createCheckboxField('Hide "Latest YouTube posts" on the Home Page (default: off)', 'hideLatestPostsHome', USER_CONFIG.hideLatestPostsHome); form.appendChild(hideLatestPostsHome); // hide more topics section const hideExploreSection = createCheckboxField('Hide "Explore more topics" (default: off)', 'hideExploreSection', USER_CONFIG.hideExploreSection); form.appendChild(hideExploreSection); // hide right sidebar search const hideRightSidebarSearch = createCheckboxField('Hide Right Sidebar on Search Pages (default: off)', 'hideRightSidebarSearch', USER_CONFIG.hideRightSidebarSearch); form.appendChild(hideRightSidebarSearch); // hide latest post from . . . const hideLatestPosts = createCheckboxField('Hide "Latest posts from . . ." on Search Pages (default: off)', 'hideLatestPosts', USER_CONFIG.hideLatestPosts); form.appendChild(hideLatestPosts); // hide Most relevant on subscriptions page const hideMostRelevant = createCheckboxField('Hide "Most relevant" on Subscriptions Page (default: off)', 'hideMostRelevant', USER_CONFIG.hideMostRelevant); form.appendChild(hideMostRelevant); // hide queue button const hideQueueBtn = createCheckboxField('Hide "Add to queue" Button (default: off)', 'hideQueueBtn', USER_CONFIG.hideQueueBtn); form.appendChild(hideQueueBtn); // hide mini player const hideMiniPlayer = createCheckboxField('Hide Mini Player (default: off)', 'hideMiniPlayer', USER_CONFIG.hideMiniPlayer); form.appendChild(hideMiniPlayer); // hide watched videos globally const hideWatchedGlobal = document.createElement('div'); hideWatchedGlobal.textContent = 'Hide Watched Videos'; hideWatchedGlobal.classList.add('button-icons', 'features-text'); form.appendChild(hideWatchedGlobal); // css version const videosHideWatchedGlobal = createCheckboxField('Hide Watched Videos Regardless of Progress Everywhere (default: off)', 'videosHideWatchedGlobal', USER_CONFIG.videosHideWatchedGlobal); form.appendChild(videosHideWatchedGlobal); // hide watched only on home const videosHideWatched = createCheckboxField('Hide Watched Videos Regardless of Progress Only on the Home Page (default: off)', 'videosHideWatched', USER_CONFIG.videosHideWatched); form.appendChild(videosHideWatched); // info for hiding watched videos const descriptionHideWatchedVideos = document.createElement('small'); descriptionHideWatchedVideos.textContent = 'Please pick either a CSS version above or the JavaScript version below to hide watched videos.'; descriptionHideWatchedVideos.classList.add('CentAnni-info-text', 'hide-watched'); form.appendChild(descriptionHideWatchedVideos); // js version const videosHideWatchedGlobalJS = createNumberInputField("Percent Watched to Hide Videos (default: 0 | disabled)", 'videosHideWatchedGlobalJS', USER_CONFIG.videosHideWatchedGlobalJS, { max: 100, step: 1 }); form.appendChild(videosHideWatchedGlobalJS); // hide percentage watched on home page const videosHideWatchedHome = createCheckboxField('Hide X Percentage Watched Videos on the Home Page (default: on)', 'videosHideWatchedHome', USER_CONFIG.videosHideWatchedHome); form.appendChild(videosHideWatchedHome); // hide percentage watched on sub page const videosHideWatchedSubscriptions = createCheckboxField('Hide X Percentage Watched Videos on the Subscription Page (default: off)', 'videosHideWatchedSubscriptions', USER_CONFIG.videosHideWatchedSubscriptions); form.appendChild(videosHideWatchedSubscriptions); // hide percentage watched on channel page const videosHideWatchedChannels = createCheckboxField('Hide X Percentage Watched Videos on Channel Pages (default: off)', 'videosHideWatchedChannels', USER_CONFIG.videosHideWatchedChannels); form.appendChild(videosHideWatchedChannels); // hide percentage watched on pl const videosHideWatchedPlaylist = createCheckboxField('Hide X Percentage Watched Videos on Playlists (default: off)', 'videosHideWatchedPlaylist', USER_CONFIG.videosHideWatchedPlaylist); form.appendChild(videosHideWatchedPlaylist); // hide percentage watched on videos const videosHideWatchedVideo = createCheckboxField('Hide X Percentage Watched Videos on Video Pages (default: on)', 'videosHideWatchedVideo', USER_CONFIG.videosHideWatchedVideo); form.appendChild(videosHideWatchedVideo); // hide percentage watched on search const videosHideWatchedSearch = createCheckboxField('Hide X Percentage Watched Videos on Search Pages (default: off)', 'videosHideWatchedSearch', USER_CONFIG.videosHideWatchedSearch); form.appendChild(videosHideWatchedSearch); // YT Guide const leftnavbar = document.createElement('div'); leftnavbar.textContent = 'Hide UI Elements in the Guide'; leftnavbar.classList.add('button-icons', 'features-text'); form.appendChild(leftnavbar); // hide home button const lnbHideHomeBtn = createCheckboxField('Hide "Home" Button (default: off)', 'lnbHideHomeBtn', USER_CONFIG.lnbHideHomeBtn); form.appendChild(lnbHideHomeBtn); // hide subscriptions button const lnbHideSubscriptionsBtn = createCheckboxField('Hide "Subscriptions" Button (default: off)', 'lnbHideSubscriptionsBtn', USER_CONFIG.lnbHideSubscriptionsBtn); form.appendChild(lnbHideSubscriptionsBtn); // restore order: home > you > subs const lnbRestoreOrder = createCheckboxField('Restore Order: Home > You > Subscriptions (default: off)', 'lnbRestoreOrder', USER_CONFIG.lnbRestoreOrder); form.appendChild(lnbRestoreOrder); // Spacer-5 const spacer5Home = document.createElement('div'); spacer5Home.classList.add('spacer-5'); form.appendChild(spacer5Home); // hide you section const lnbHideYouSection = createCheckboxField('Hide "You" Section (default: off)', 'lnbHideYouSection', USER_CONFIG.lnbHideYouSection); form.appendChild(lnbHideYouSection); // hide you button const lnbHideYouBtn = createCheckboxField('Hide "You" Button (default: off)', 'lnbHideYouBtn', USER_CONFIG.lnbHideYouBtn); form.appendChild(lnbHideYouBtn); // hide your channel button const lnbHideUrChannelBtn = createCheckboxField('Hide "Your Channel" Button (default: off)', 'lnbHideUrChannelBtn', USER_CONFIG.lnbHideUrChannelBtn); form.appendChild(lnbHideUrChannelBtn); // hide history button const lnbHideHistoryBtn = createCheckboxField('Hide "History" Button (default: off)', 'lnbHideHistoryBtn', USER_CONFIG.lnbHideHistoryBtn); form.appendChild(lnbHideHistoryBtn); // hide playlists button const lnbHidePlaylistsBtn = createCheckboxField('Hide "Playlists" Button (default: off)', 'lnbHidePlaylistsBtn', USER_CONFIG.lnbHidePlaylistsBtn); form.appendChild(lnbHidePlaylistsBtn); // hide videos button const lnbHideVideosBtn = createCheckboxField('Hide "Your Videos" Button (default: off)', 'lnbHideVideosBtn', USER_CONFIG.lnbHideVideosBtn); form.appendChild(lnbHideVideosBtn); // hide courses button const lnbHideCoursesBtn = createCheckboxField('Hide "Your Courses" Button (default: off)', 'lnbHideCoursesBtn', USER_CONFIG.lnbHideCoursesBtn); form.appendChild(lnbHideCoursesBtn); // hide your podcasts button const lnbHideYPodcastsBtn = createCheckboxField('Hide "Your Podcasts" Button (default: off)', 'lnbHideYPodcastsBtn', USER_CONFIG.lnbHideYPodcastsBtn); form.appendChild(lnbHideYPodcastsBtn); // hide watch later button const lnbHideWlBtn = createCheckboxField('Hide "Watch Later" Button (default: off)', 'lnbHideWlBtn', USER_CONFIG.lnbHideWlBtn); form.appendChild(lnbHideWlBtn); // hide liked videos button const lnbHideLikedVideosBtn = createCheckboxField('Hide "Liked Videos" Button (default: off)', 'lnbHideLikedVideosBtn', USER_CONFIG.lnbHideLikedVideosBtn); form.appendChild(lnbHideLikedVideosBtn); // hide downloads button const lnbHideDLBtn = createCheckboxField('Hide "Downloads" Button (default: off)', 'lnbHideDLBtn', USER_CONFIG.lnbHideDLBtn); form.appendChild(lnbHideDLBtn); // Spacer-5 const spacer5Subscriptions = document.createElement('div'); spacer5Subscriptions.classList.add('spacer-5'); form.appendChild(spacer5Subscriptions); // hide subscriptions section const lnbHideSubscriptionsSection = createCheckboxField('Hide "Subscriptions" Section (default: off)', 'lnbHideSubscriptionsSection', USER_CONFIG.lnbHideSubscriptionsSection); form.appendChild(lnbHideSubscriptionsSection); // hide subscriptions title const lnbHideSubscriptionsTitle = createCheckboxField('Hide "Subscriptions" Title (default: off)', 'lnbHideSubscriptionsTitle', USER_CONFIG.lnbHideSubscriptionsTitle); form.appendChild(lnbHideSubscriptionsTitle); // hide more button const lnbHideMoreBtn = createCheckboxField('Hide "Show More" Button (default: off)', 'lnbHideMoreBtn', USER_CONFIG.lnbHideMoreBtn); form.appendChild(lnbHideMoreBtn); // Spacer-5 const spacer5Explore = document.createElement('div'); spacer5Explore.classList.add('spacer-5'); form.appendChild(spacer5Explore); // hide explore section const lnbHideExploreSection = createCheckboxField('Hide "Explore" Section (default: off)', 'lnbHideExploreSection', USER_CONFIG.lnbHideExploreSection); form.appendChild(lnbHideExploreSection); // hide explore title const lnbHideExploreTitle = createCheckboxField('Hide "Explore" Title (default: off)', 'lnbHideExploreTitle', USER_CONFIG.lnbHideExploreTitle); form.appendChild(lnbHideExploreTitle); // hide trending button const lnbHideTrendingBtn = createCheckboxField('Hide "Trending" Button (default: off)', 'lnbHideTrendingBtn', USER_CONFIG.lnbHideTrendingBtn); form.appendChild(lnbHideTrendingBtn); // hide music button const lnbHideMusicBtn = createCheckboxField('Hide "Music" Button (default: off)', 'lnbHideMusicBtn', USER_CONFIG.lnbHideMusicBtn); form.appendChild(lnbHideMusicBtn); // hide movies button const lnbHideMoviesBtn = createCheckboxField('Hide "Movies & TV" Button (default: off)', 'lnbHideMoviesBtn', USER_CONFIG.lnbHideMoviesBtn); form.appendChild(lnbHideMoviesBtn); // hide live button const lnbHideLiveBtn = createCheckboxField('Hide "Live" Button (default: off)', 'lnbHideLiveBtn', USER_CONFIG.lnbHideLiveBtn); form.appendChild(lnbHideLiveBtn); // hide gaming button const lnbHideGamingBtn = createCheckboxField('Hide "Gaming" Button (default: off)', 'lnbHideGamingBtn', USER_CONFIG.lnbHideGamingBtn); form.appendChild(lnbHideGamingBtn); // hide news button const lnbHideNewsBtn = createCheckboxField('Hide "News" Button (default: off)', 'lnbHideNewsBtn', USER_CONFIG.lnbHideNewsBtn); form.appendChild(lnbHideNewsBtn); // hide sports button const lnbHideSportsBtn = createCheckboxField('Hide "Sports" Button (default: off)', 'lnbHideSportsBtn', USER_CONFIG.lnbHideSportsBtn); form.appendChild(lnbHideSportsBtn); // hide learning button const lnbHideLearningBtn = createCheckboxField('Hide "Learning" Button (default: off)', 'lnbHideLearningBtn', USER_CONFIG.lnbHideLearningBtn); form.appendChild(lnbHideLearningBtn); // hide fashion & beauty button const lnbHideFashionBtn = createCheckboxField('Hide "Fashion & Beauty" Button (default: off)', 'lnbHideFashionBtn', USER_CONFIG.lnbHideFashionBtn); form.appendChild(lnbHideFashionBtn); // hide playables button const lnbHidePlayablesBtn = createCheckboxField('Hide "Playables" Button (default: off)', 'lnbHidePlayablesBtn', USER_CONFIG.lnbHidePlayablesBtn); form.appendChild(lnbHidePlayablesBtn); // hide podcasts button const lnbHidePodcastsBtn = createCheckboxField('Hide "Podcasts" Button (default: off)', 'lnbHidePodcastsBtn', USER_CONFIG.lnbHidePodcastsBtn); form.appendChild(lnbHidePodcastsBtn); // Spacer-5 const spacer5More = document.createElement('div'); spacer5More.classList.add('spacer-5'); form.appendChild(spacer5More); // hide more section const lnbHideMoreSection = createCheckboxField('Hide "More from YouTube" Section (default: off)', 'lnbHideMoreSection', USER_CONFIG.lnbHideMoreSection); form.appendChild(lnbHideMoreSection); // hide more title const lnbHideMoreTitle = createCheckboxField('Hide "More from YouTube" Title (default: off)', 'lnbHideMoreTitle', USER_CONFIG.lnbHideMoreTitle); form.appendChild(lnbHideMoreTitle); // hide youtube premium button const lnbHideYtPremiumBtn = createCheckboxField('Hide "YouTube Premium" Button (default: off)', 'lnbHideYtPremiumBtn', USER_CONFIG.lnbHideYtPremiumBtn); form.appendChild(lnbHideYtPremiumBtn); // hide youtube studio button const lnbHideYtStudioBtn = createCheckboxField('Hide "YouTube Studio" Button (default: off)', 'lnbHideYtStudioBtn', USER_CONFIG.lnbHideYtStudioBtn); form.appendChild(lnbHideYtStudioBtn); // hide youtube music button const lnbHideYtMusicBtn = createCheckboxField('Hide "YouTube Music" Button (default: off)', 'lnbHideYtMusicBtn', USER_CONFIG.lnbHideYtMusicBtn); form.appendChild(lnbHideYtMusicBtn); // hide youtube kids button const lnbHideYtKidsBtn = createCheckboxField('Hide "YouTube Kids" Button (default: off)', 'lnbHideYtKidsBtn', USER_CONFIG.lnbHideYtKidsBtn); form.appendChild(lnbHideYtKidsBtn); // Spacer-5 const spacer5Penultimate = document.createElement('div'); spacer5Penultimate.classList.add('spacer-5'); form.appendChild(spacer5Penultimate); // hide penultimate section const lnbHidePenultimateSection = createCheckboxField('Hide Penultimate Section (default: off)', 'lnbHidePenultimateSection', USER_CONFIG.lnbHidePenultimateSection); form.appendChild(lnbHidePenultimateSection); // hide settings button const lnbHideSettingsBtn = createCheckboxField('Hide "Settings" Button (default: off)', 'lnbHideSettingsBtn', USER_CONFIG.lnbHideSettingsBtn); form.appendChild(lnbHideSettingsBtn); // hide report history button const lnbHideReportHistoryBtn = createCheckboxField('Hide "Report History" Button (default: off)', 'lnbHideReportHistoryBtn', USER_CONFIG.lnbHideReportHistoryBtn); form.appendChild(lnbHideReportHistoryBtn); // hide help button const lnbHideHelpBtn = createCheckboxField('Hide "Help" Button (default: off)', 'lnbHideHelpBtn', USER_CONFIG.lnbHideHelpBtn); form.appendChild(lnbHideHelpBtn); // hide feedback button const lnbHideFeedbackBtn = createCheckboxField('Hide "Send Feedback" Button (default: off)', 'lnbHideFeedbackBtn', USER_CONFIG.lnbHideFeedbackBtn); form.appendChild(lnbHideFeedbackBtn); // Spacer-5 const spacer5Footer = document.createElement('div'); spacer5Footer.classList.add('spacer-5'); form.appendChild(spacer5Footer); // hide footer const lnbHideFooter = createCheckboxField('Hide Footer (default: off)', 'lnbHideFooter', USER_CONFIG.lnbHideFooter); form.appendChild(lnbHideFooter); return form; } // color code videos function createColorCodeVideosContent() { const form = document.createElement('form'); form.id = 'color-code-videos-form'; const subPanelHeader = document.createElement('div'); subPanelHeader.classList.add('sub-panel-header'); subPanelHeader.textContent = 'Configure Color Codes for Videos'; form.appendChild(subPanelHeader); // on home page const colorCodeVideosOnHome = document.createElement('div'); colorCodeVideosOnHome.textContent = 'Home Page'; colorCodeVideosOnHome.classList.add('button-icons', 'features-text'); form.appendChild(colorCodeVideosOnHome); const infoColorCodeVideosHome = document.createElement('small'); infoColorCodeVideosHome.textContent = "Disable the hover effect, set the opacity of videos older than a year, and pick border colors for videos based on their age for light and dark mode."; infoColorCodeVideosHome.classList.add('CentAnni-info-text'); form.appendChild(infoColorCodeVideosHome); // activate color code videos on home const colorCodeVideosEnabled = createCheckboxField('Color Code Videos Based on Age and Status (default: on)', 'colorCodeVideosEnabled', USER_CONFIG.colorCodeVideosEnabled); form.appendChild(colorCodeVideosEnabled); // disable hover effect const homeDisableHover = createCheckboxField('Disable Hover Effect (default: off)', 'homeDisableHover', USER_CONFIG.homeDisableHover); form.appendChild(homeDisableHover); // opacity picker for old videos const videosOldContainer = createSliderInputField('Change Opacity of Videos Uploaded More than 1 Year Ago:', 'videosOldOpacity', USER_CONFIG.videosOldOpacity, '0', '1', '0.1'); form.appendChild(videosOldContainer); // color pickers for different video ages const videosAgeContainer = document.createElement('div'); videosAgeContainer.classList.add('videos-colorpicker-container'); const colorPickerConfigs = [ { label: 'Videos Uploaded Within the Last 24 Hours:', id: 'videosAgeColorPickerNewly' }, { label: 'Videos Uploaded Within the Past Week:', id: 'videosAgeColorPickerRecent' }, { label: 'Videos Uploaded Within the Past Month:', id: 'videosAgeColorPickerLately' }, { label: 'Videos Uploaded Within 2 to 12 Months:', id: 'videosAgeColorPickerLatterly' }, { label: 'Videos Uploaded More than 1 Year Ago:', id: 'videosAgeColorPickerOld' }, { label: 'Videos Currently Live:', id: 'videosAgeColorPickerLive' }, { label: 'The Word "Streamed" from Videos That Were Live:', id: 'videosAgeColorPickerStreamed' }, { label: 'Scheduled Videos and Upcoming Live Streams:', id: 'videosAgeColorPickerUpcoming' } ]; colorPickerConfigs.forEach(config => { const row = createColorPicker(config.label, config.id); const darkModeSpan = document.createElement('span'); darkModeSpan.classList.add('label-style-settings'); darkModeSpan.textContent = 'Dark Mode'; row.insertBefore(darkModeSpan, row.firstChild); const lightPickerRow = createColorPicker('Light Mode', config.id + 'Light'); const lightPicker = lightPickerRow.querySelector('input'); const lightModeLabel = lightPickerRow.querySelector('span'); row.insertBefore(lightPicker, row.firstChild); row.insertBefore(lightModeLabel, row.firstChild); videosAgeContainer.appendChild(row); }); form.appendChild(videosAgeContainer); // on subscriptions page const colorCodeVideosOnSubscriptions = document.createElement('div'); colorCodeVideosOnSubscriptions.textContent = 'Subscriptions Page'; colorCodeVideosOnSubscriptions.classList.add('button-icons', 'features-text'); form.appendChild(colorCodeVideosOnSubscriptions); const infoColorCodeVideosSubscriptions = document.createElement('small'); infoColorCodeVideosSubscriptions.textContent = "On each visit, the newest uploaded video ID is saved, allowing subsequent visits to highlight and optionally auto-scroll to that video.\nYouTube loads about 90 videos on initial page load, so highlighting and scrolling apply within this limit."; infoColorCodeVideosSubscriptions.classList.add('CentAnni-info-text'); form.appendChild(infoColorCodeVideosSubscriptions); // color picker last seen video const lastSeenVideoColor = document.createElement('div'); lastSeenVideoColor.classList.add('videos-colorpicker-container'); const rowLSV = createColorPicker('Last Uploaded Video:', 'lastSeenVideoColor'); const darkModeSpanLSV = document.createElement('span'); darkModeSpanLSV.classList.add('label-style-settings'); darkModeSpanLSV.textContent = 'Dark Mode'; rowLSV.insertBefore(darkModeSpanLSV, rowLSV.firstChild); const lightPickerRowLSV = createColorPicker('Light Mode', 'lastSeenVideoColorLight'); const lightPickerLSV = lightPickerRowLSV.querySelector('input'); const lightModeLabelLSV = lightPickerRowLSV.querySelector('span'); rowLSV.insertBefore(lightPickerLSV, rowLSV.firstChild); rowLSV.insertBefore(lightModeLabelLSV, rowLSV.firstChild); lastSeenVideoColor.appendChild(rowLSV); form.appendChild(lastSeenVideoColor); // last seen video const lastSeenVideo = createCheckboxField('Color Code Last Uploaded Video (default: on)', 'lastSeenVideo', USER_CONFIG.lastSeenVideo); form.appendChild(lastSeenVideo); // scroll to last seen video const lastSeenVideoScroll = createCheckboxField('Auto-Scroll to Last Uploaded Video (default: off)', 'lastSeenVideoScroll', USER_CONFIG.lastSeenVideoScroll); form.appendChild(lastSeenVideoScroll); // on watch later page const colorCodeVideosOnWatchLater = document.createElement('div'); colorCodeVideosOnWatchLater.textContent = 'Watch Later Page'; colorCodeVideosOnWatchLater.classList.add('button-icons', 'features-text'); form.appendChild(colorCodeVideosOnWatchLater); const infoColorCodeVideosOnWatchLater = document.createElement('small'); infoColorCodeVideosOnWatchLater.textContent = 'Highlight unwatched videos from your favorite channels, categorized in gold, silver, and bronze. To enter multiple channels per category, enter the YouTube handles without the "@" symbol separated by commas, e.g. "youtubecreators1,youtubecreators2,youtubecreators3".'; infoColorCodeVideosOnWatchLater.classList.add('CentAnni-info-text'); form.appendChild(infoColorCodeVideosOnWatchLater); form.appendChild(createInputField(`Gold`, 'watchLaterGold', USER_CONFIG.watchLaterGold, 'label-gold')); form.appendChild(createInputField(`Silver`, 'watchLaterSilver', USER_CONFIG.watchLaterSilver, 'label-silver')); form.appendChild(createInputField(`Bronze`, 'watchLaterBronze', USER_CONFIG.watchLaterBronze, 'label-bronze')); return form; } } // helper function to create input fields function createInputField(labelText, settingKey, settingValue, labelClass) { const container = document.createElement('div'); container.classList.add('url-container'); const label = document.createElement('label'); label.textContent = labelText; label.className = labelClass; label.classList.add('label-style-settings'); label.htmlFor = settingKey; container.appendChild(label); const input = document.createElement('input'); const fieldInputClass = `input-field-${settingKey}`; input.type = 'text'; input.id = settingKey; input.name = settingKey; input.value = settingValue; input.classList.add('input-field-url'); input.classList.add(fieldInputClass); container.appendChild(input); return container; } // helper function to create select fields function createSelectField(labelText, labelClass, settingKey, settingValue, options) { const container = document.createElement('div'); container.classList.add('file-naming-container'); const labelId = `${settingKey}-label`; const selectId = `${settingKey}-button`; const listId = `${settingKey}-list`; const label = document.createElement('label'); label.id = labelId; label.textContent = labelText; label.className = labelClass; label.classList.add('label-style-settings'); container.appendChild(label); const select = document.createElement('div'); select.id = selectId; select.classList.add('select-file-naming'); select.textContent = options[settingValue]; select.setAttribute('tabindex', '0'); select.setAttribute('role', 'combobox'); select.setAttribute('aria-haspopup', 'listbox'); select.setAttribute('aria-expanded', 'false'); select.setAttribute('aria-controls', listId); select.setAttribute('aria-labelledby', `${labelId} ${selectId}`); container.appendChild(select); const hiddenSelect = document.createElement('select'); hiddenSelect.name = settingKey; hiddenSelect.classList.add('hidden-select'); for (const [value, text] of Object.entries(options)) { const option = document.createElement('option'); option.value = value; option.text = text; if (value === settingValue) option.selected = true; hiddenSelect.appendChild(option); } container.appendChild(hiddenSelect); const dropdownList = document.createElement('div'); dropdownList.id = listId; dropdownList.classList.add('dropdown-list'); dropdownList.setAttribute('role', 'listbox'); container.appendChild(dropdownList); for (const [value, text] of Object.entries(options)) { const item = document.createElement('div'); item.id = `${settingKey}-option-${value}`; item.classList.add('dropdown-item'); item.textContent = text; item.dataset.value = value; item.setAttribute('role', 'option'); item.setAttribute('aria-selected', value === settingValue ? 'true' : 'false'); if (value === settingValue) { item.classList.add('dropdown-item-selected'); select.setAttribute('aria-activedescendant', item.id); } item.onclick = () => { const previouslySelected = dropdownList.querySelector('.dropdown-item-selected'); if (previouslySelected) { previouslySelected.classList.remove('dropdown-item-selected'); previouslySelected.setAttribute('aria-selected', 'false'); } item.classList.add('dropdown-item-selected'); item.setAttribute('aria-selected', 'true'); select.textContent = text; select.setAttribute('aria-activedescendant', item.id); hiddenSelect.value = value; closeDropdown(); }; dropdownList.appendChild(item); } // open dropdown select.onclick = (event) => { event.stopPropagation(); document.querySelectorAll('.dropdown-list.show').forEach(list => { if (list !== dropdownList) { list.classList.remove('show'); list.closest('.file-naming-container')?.querySelector('.select-file-naming')?.setAttribute('aria-expanded', 'false'); } }); const isShown = dropdownList.classList.toggle('show'); select.setAttribute('aria-expanded', isShown ? 'true' : 'false'); if (isShown) document.addEventListener('click', closeDropdown, { once: true }); }; // close dropdown const closeDropdown = () => { dropdownList.classList.remove('show'); select.setAttribute('aria-expanded', 'false'); }; return container; } // helper function to create checkbox fields function createCheckboxField(labelText, settingKey, settingValue) { const container = document.createElement('div'); container.classList.add('checkbox-container'); const label = document.createElement('label'); label.classList.add('checkbox-label'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.name = settingKey; checkbox.checked = settingValue; checkbox.classList.add('checkbox-field'); label.appendChild(checkbox); const span = document.createElement('span'); span.textContent = labelText; label.appendChild(span); container.appendChild(label); return container; } // helper function to create a number input fields function createNumberInputField(labelText, settingKey, settingValue, options = {}) { const container = document.createElement('div'); container.classList.add('number-input-container'); const label = document.createElement('label'); label.classList.add('number-input-label'); const numberInput = document.createElement('input'); numberInput.type = options.type || 'number'; numberInput.name = settingKey; numberInput.value = settingValue; if (numberInput.type === 'number') { numberInput.min = options.min || 1; numberInput.max = options.max || 20; numberInput.step = options.step || .25; } numberInput.classList.add('number-input-field'); label.appendChild(numberInput); const span = document.createElement('span'); span.textContent = labelText; label.appendChild(span); container.appendChild(label); return container; } // helper function to create a slider fields function createSliderInputField(labelText, settingKey, settingValue, min, max, step) { const container = document.createElement('div'); container.classList.add('videos-old-container'); const label = document.createElement('span'); label.classList.add('label-style-settings'); label.textContent = labelText; container.appendChild(label); const sliderContainer = document.createElement('div'); sliderContainer.classList.add('slider-container'); const leftLabel = document.createElement('span'); leftLabel.textContent = min; sliderContainer.appendChild(leftLabel); const slider = document.createElement('input'); slider.type = 'range'; slider.min = min; slider.max = max; slider.step = step; slider.value = settingValue; slider.name = settingKey; sliderContainer.appendChild(slider); const rightLabel = document.createElement('span'); rightLabel.textContent = max; sliderContainer.appendChild(rightLabel); const currentValue = document.createElement('span'); currentValue.textContent = `(${parseFloat(slider.value).toFixed(1)})`; sliderContainer.appendChild(currentValue); container.appendChild(sliderContainer); slider.addEventListener('input', (e) => { const value = parseFloat(e.target.value).toFixed(1); currentValue.textContent = `(${value})`; }); return container; } // helper function to create a textarea fields function createTextareaField(labelText, settingKey, settingValue, labelClass) { const container = document.createElement('chatgpt-prompt'); const label = document.createElement('label'); label.textContent = labelText; label.className = labelClass; label.classList.add('label-style-settings'); container.appendChild(label); const textarea = document.createElement('textarea'); textarea.name = settingKey; textarea.value = settingValue; textarea.classList.add('chatgpt-prompt-textarea'); container.appendChild(textarea); return container; } // helper function to create color pickers function createColorPicker(labelText, configKey) { const row = document.createElement('div'); row.classList.add('videos-colorpicker-row'); const colorPicker = document.createElement('input'); colorPicker.type = 'color'; colorPicker.value = USER_CONFIG[configKey]; colorPicker.name = configKey; row.appendChild(colorPicker); const label = document.createElement('span'); label.classList.add('label-style-settings'); label.textContent = labelText; row.appendChild(label); return row; } // function to save settings async function saveSettings() { const form = document.getElementById('CentAnni-main-settings-form'); const subPanelLinks = document.getElementById('links-in-header-form'); const subPanelCustomCSS = document.getElementById('custom-css-form'); const subPanelColor = document.getElementById('color-code-videos-form'); // function to ensure secure URLs function normalizeUrl(url) { url = url.trim(); if (/^https?:\/\//i.test(url)) { url = url.replace(/^http:\/\//i, 'https://'); } else { url = 'https://' + url; } return url; } // validate ChatGPT and NotebookLM URLs function validateUrlAndLabel(formElement, configPrefix) { let url; let shouldNormalizeUrl = false; let targetUrlAndLabel = form.elements[`target${configPrefix}Url`].value.trim(); if (targetUrlAndLabel !== '') { let split = targetUrlAndLabel.split('|').map(s => s.trim()); if (split.length === 2) { if (/\p{L}+/u.test(split[0])) USER_CONFIG[`target${configPrefix}Label`] = split[0]; url = split[1]; shouldNormalizeUrl = true; } else if (targetUrlAndLabel.includes('.')) { if (!USER_CONFIG[`target${configPrefix}Label`] || !/\p{L}+/u.test(USER_CONFIG[`target${configPrefix}Label`])) delete USER_CONFIG[`target${configPrefix}Label`]; url = targetUrlAndLabel; shouldNormalizeUrl = true; } } if (shouldNormalizeUrl) USER_CONFIG[`target${configPrefix}Url`] = normalizeUrl(url); else { delete USER_CONFIG[`target${configPrefix}Label`]; delete USER_CONFIG[`target${configPrefix}Url`]; } } validateUrlAndLabel(form, 'ChatGPT'); validateUrlAndLabel(form, 'NotebookLM'); // save other settings USER_CONFIG.YouTubeTranscriptExporter = form.elements.YouTubeTranscriptExporter.checked; USER_CONFIG.lazyTranscriptLoading = form.elements.lazyTranscriptLoading.checked; USER_CONFIG.fileNamingFormat = form.elements.fileNamingFormat.value; USER_CONFIG.includeTimestamps = form.elements.includeTimestamps.checked; USER_CONFIG.useLegacyTranscriptPanel = form.elements.useLegacyTranscriptPanel.checked; USER_CONFIG.includeChapterHeaders = form.elements.includeChapterHeaders.checked; USER_CONFIG.openSameTab = form.elements.openSameTab.checked; USER_CONFIG.ChatGPTPrompt = form.elements.ChatGPTPrompt.value; // initialize buttonIcons if not already USER_CONFIG.buttonIcons = USER_CONFIG.buttonIcons || {}; // save button icons, removing empty values to use defaults const buttonIconCopy = form.elements.buttonIconCopy.value.trim(); const buttonIconChatGPT = form.elements.buttonIconChatGPT.value.trim(); const buttonIconSettings = form.elements.buttonIconSettings.value.trim(); const buttonIconlazyLoad = form.elements.buttonIconlazyLoad.value.trim(); const buttonIconDownload = form.elements.buttonIconDownload.value.trim(); const buttonIconNotebookLM = form.elements.buttonIconNotebookLM.value.trim(); USER_CONFIG.buttonIcons.copy = buttonIconCopy; USER_CONFIG.buttonIcons.ChatGPT = buttonIconChatGPT; USER_CONFIG.buttonIcons.download = buttonIconDownload; USER_CONFIG.buttonIcons.NotebookLM = buttonIconNotebookLM; USER_CONFIG.buttonIcons.lazyLoad = buttonIconlazyLoad; if (buttonIconSettings !== '') { USER_CONFIG.buttonIcons.settings = buttonIconSettings; } else { delete USER_CONFIG.buttonIcons.settings; } // save sub panels - header links if (subPanelLinks) { USER_CONFIG.buttonLeft1Text = subPanelLinks.elements.buttonLeft1Text.value; USER_CONFIG.buttonLeft1Url = subPanelLinks.elements.buttonLeft1Url.value; USER_CONFIG.buttonLeft2Text = subPanelLinks.elements.buttonLeft2Text.value; USER_CONFIG.buttonLeft2Url = subPanelLinks.elements.buttonLeft2Url.value; USER_CONFIG.buttonLeft3Text = subPanelLinks.elements.buttonLeft3Text.value; USER_CONFIG.buttonLeft3Url = subPanelLinks.elements.buttonLeft3Url.value; USER_CONFIG.buttonLeft4Text = subPanelLinks.elements.buttonLeft4Text.value; USER_CONFIG.buttonLeft4Url = subPanelLinks.elements.buttonLeft4Url.value; USER_CONFIG.buttonLeft5Text = subPanelLinks.elements.buttonLeft5Text.value; USER_CONFIG.buttonLeft5Url = subPanelLinks.elements.buttonLeft5Url.value; USER_CONFIG.buttonLeft6Text = subPanelLinks.elements.buttonLeft6Text.value; USER_CONFIG.buttonLeft6Url = subPanelLinks.elements.buttonLeft6Url.value; USER_CONFIG.buttonLeft7Text = subPanelLinks.elements.buttonLeft7Text.value; USER_CONFIG.buttonLeft7Url = subPanelLinks.elements.buttonLeft7Url.value; USER_CONFIG.buttonLeft8Text = subPanelLinks.elements.buttonLeft8Text.value; USER_CONFIG.buttonLeft8Url = subPanelLinks.elements.buttonLeft8Url.value; USER_CONFIG.buttonLeft9Text = subPanelLinks.elements.buttonLeft9Text.value; USER_CONFIG.buttonLeft9Url = subPanelLinks.elements.buttonLeft9Url.value; USER_CONFIG.buttonLeft10Text = subPanelLinks.elements.buttonLeft10Text.value; USER_CONFIG.buttonLeft10Url = subPanelLinks.elements.buttonLeft10Url.value; USER_CONFIG.mButtonText = subPanelLinks.elements.mButtonText.value; USER_CONFIG.mButtonDisplay = subPanelLinks.elements.mButtonDisplay.checked; } // save sub panels - custom css if (subPanelCustomCSS) { USER_CONFIG.settingsGuide = subPanelCustomCSS.elements.settingsGuide.checked; USER_CONFIG.preventBackgroundExecution = subPanelCustomCSS.elements.preventBackgroundExecution.checked; USER_CONFIG.defaultChannelPage = subPanelCustomCSS.elements.defaultChannelPage.value; USER_CONFIG.textTransform = subPanelCustomCSS.elements.textTransform.value; USER_CONFIG.defaultFontSize = parseFloat(subPanelCustomCSS.elements.defaultFontSize.value); USER_CONFIG.videosWatchedOpacity = parseFloat(subPanelCustomCSS.elements.videosWatchedOpacity.value); USER_CONFIG.videosHideWatchedGlobal = subPanelCustomCSS.elements.videosHideWatchedGlobal.checked; USER_CONFIG.videosHideWatched = subPanelCustomCSS.elements.videosHideWatched.checked; USER_CONFIG.videosPerRow = parseInt(subPanelCustomCSS.elements.videosPerRow.value); USER_CONFIG.sidebarWidth = parseFloat(subPanelCustomCSS.elements.sidebarWidth.value); USER_CONFIG.searchPosition = parseFloat(subPanelCustomCSS.elements.searchPosition.value); USER_CONFIG.spaceBelowPlayer = parseInt(subPanelCustomCSS.elements.spaceBelowPlayer.value) || 110; USER_CONFIG.videosHideWatchedGlobalJS = parseInt(subPanelCustomCSS.elements.videosHideWatchedGlobalJS.value); USER_CONFIG.videosHideWatchedHome = subPanelCustomCSS.elements.videosHideWatchedHome.checked; USER_CONFIG.videosHideWatchedSubscriptions = subPanelCustomCSS.elements.videosHideWatchedSubscriptions.checked; USER_CONFIG.videosHideWatchedChannels = subPanelCustomCSS.elements.videosHideWatchedChannels.checked; USER_CONFIG.videosHideWatchedPlaylist = subPanelCustomCSS.elements.videosHideWatchedPlaylist.checked; USER_CONFIG.videosHideWatchedVideo = subPanelCustomCSS.elements.videosHideWatchedVideo.checked; USER_CONFIG.videosHideWatchedSearch = subPanelCustomCSS.elements.videosHideWatchedSearch.checked; USER_CONFIG.autoOpenChapters = subPanelCustomCSS.elements.autoOpenChapters.checked; USER_CONFIG.autoOpenTranscript = subPanelCustomCSS.elements.autoOpenTranscript.checked; USER_CONFIG.autoOpenComments = subPanelCustomCSS.elements.autoOpenComments.checked; USER_CONFIG.highlightTranscript = subPanelCustomCSS.elements.highlightTranscript.checked; USER_CONFIG.subtitlesWhenMuted = subPanelCustomCSS.elements.subtitlesWhenMuted.checked; USER_CONFIG.subtitlesWhenRewind = subPanelCustomCSS.elements.subtitlesWhenRewind.checked; USER_CONFIG.displayRemainingTime = subPanelCustomCSS.elements.displayRemainingTime.checked; USER_CONFIG.showRemainingCompact = subPanelCustomCSS.elements.showRemainingCompact.checked; USER_CONFIG.fsRemainingTime = subPanelCustomCSS.elements.fsRemainingTime.checked; USER_CONFIG.progressBar = subPanelCustomCSS.elements.progressBar.checked; USER_CONFIG.hideShorts = subPanelCustomCSS.elements.hideShorts.checked; USER_CONFIG.redirectShorts = subPanelCustomCSS.elements.redirectShorts.checked; USER_CONFIG.hideAdSlots = subPanelCustomCSS.elements.hideAdSlots.checked; USER_CONFIG.hidePlayables = subPanelCustomCSS.elements.hidePlayables.checked; USER_CONFIG.hideProdTxt = subPanelCustomCSS.elements.hideProdTxt.checked; USER_CONFIG.hideProdSug = subPanelCustomCSS.elements.hideProdSug.checked; USER_CONFIG.hidePayToWatch = subPanelCustomCSS.elements.hidePayToWatch.checked; USER_CONFIG.hideFreeWithAds = subPanelCustomCSS.elements.hideFreeWithAds.checked; USER_CONFIG.hideMembersOnly = subPanelCustomCSS.elements.hideMembersOnly.checked; USER_CONFIG.hideExploreSection = subPanelCustomCSS.elements.hideExploreSection.checked; USER_CONFIG.hideLatestPosts = subPanelCustomCSS.elements.hideLatestPosts.checked; USER_CONFIG.videoTabView = subPanelCustomCSS.elements.videoTabView.checked; USER_CONFIG.toggleTheaterModeBtn = subPanelCustomCSS.elements.toggleTheaterModeBtn.checked; USER_CONFIG.tabViewChapters = subPanelCustomCSS.elements.tabViewChapters.checked; USER_CONFIG.noAnimation = subPanelCustomCSS.elements.noAnimation.checked; USER_CONFIG.hideCommentsSection = subPanelCustomCSS.elements.hideCommentsSection.checked; USER_CONFIG.hideVideosSection = subPanelCustomCSS.elements.hideVideosSection.checked; USER_CONFIG.playbackSpeed = subPanelCustomCSS.elements.playbackSpeed.checked; USER_CONFIG.playbackSpeedBtns = subPanelCustomCSS.elements.playbackSpeedBtns.checked; USER_CONFIG.playbackSpeedValue = parseFloat(subPanelCustomCSS.elements.playbackSpeedValue.value); USER_CONFIG.playbackSpeedKey1 = subPanelCustomCSS.elements.playbackSpeedKey1.value.trim(); USER_CONFIG.playbackSpeedKey1s = subPanelCustomCSS.elements.playbackSpeedKey1s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey1s.value); USER_CONFIG.playbackSpeedKey2 = subPanelCustomCSS.elements.playbackSpeedKey2.value.trim(); USER_CONFIG.playbackSpeedKey2s = subPanelCustomCSS.elements.playbackSpeedKey2s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey2s.value); USER_CONFIG.playbackSpeedKey3 = subPanelCustomCSS.elements.playbackSpeedKey3.value.trim(); USER_CONFIG.playbackSpeedKey3s = subPanelCustomCSS.elements.playbackSpeedKey3s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey3s.value); USER_CONFIG.playbackSpeedKey4 = subPanelCustomCSS.elements.playbackSpeedKey4.value.trim(); USER_CONFIG.playbackSpeedKey4s = subPanelCustomCSS.elements.playbackSpeedKey4s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey4s.value); USER_CONFIG.playbackSpeedKey5 = subPanelCustomCSS.elements.playbackSpeedKey5.value.trim(); USER_CONFIG.playbackSpeedKey5s = subPanelCustomCSS.elements.playbackSpeedKey5s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey5s.value); USER_CONFIG.playbackSpeedKey6 = subPanelCustomCSS.elements.playbackSpeedKey6.value.trim(); USER_CONFIG.playbackSpeedKey6s = subPanelCustomCSS.elements.playbackSpeedKey6s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey6s.value); USER_CONFIG.playbackSpeedKey7 = subPanelCustomCSS.elements.playbackSpeedKey7.value.trim(); USER_CONFIG.playbackSpeedKey7s = subPanelCustomCSS.elements.playbackSpeedKey7s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey7s.value); USER_CONFIG.playbackSpeedKey8 = subPanelCustomCSS.elements.playbackSpeedKey8.value.trim(); USER_CONFIG.playbackSpeedKey8s = subPanelCustomCSS.elements.playbackSpeedKey8s.value.trim() === '' ? '' : parseFloat(subPanelCustomCSS.elements.playbackSpeedKey8s.value); USER_CONFIG.playbackSpeedToggle = subPanelCustomCSS.elements.playbackSpeedToggle.value.trim() || 's'; USER_CONFIG.playbackSpeedDecrease = subPanelCustomCSS.elements.playbackSpeedDecrease.value.trim() || 'a'; USER_CONFIG.playbackSpeedIncrease = subPanelCustomCSS.elements.playbackSpeedIncrease.value.trim() || 'd'; USER_CONFIG.defaultQuality = subPanelCustomCSS.elements.defaultQuality.value; USER_CONFIG.defaultTranscriptLanguage = subPanelCustomCSS.elements.defaultTranscriptLanguage.value; USER_CONFIG.defaultAudioLanguage = subPanelCustomCSS.elements.defaultAudioLanguage.value; USER_CONFIG.secondaryLanguage = subPanelCustomCSS.elements.secondaryLanguage.value; USER_CONFIG.defaultSubtitleLanguage = subPanelCustomCSS.elements.defaultSubtitleLanguage.value; USER_CONFIG.hideVoiceSearch = subPanelCustomCSS.elements.hideVoiceSearch.checked; USER_CONFIG.selectionColor = subPanelCustomCSS.elements.selectionColor.checked; USER_CONFIG.hideCreateButton = subPanelCustomCSS.elements.hideCreateButton.checked; USER_CONFIG.hideNotificationBtn = subPanelCustomCSS.elements.hideNotificationBtn.checked; USER_CONFIG.hideNotificationBadge = subPanelCustomCSS.elements.hideNotificationBadge.checked; USER_CONFIG.hideOwnAvatar = subPanelCustomCSS.elements.hideOwnAvatar.checked; USER_CONFIG.hideRightSidebarSearch = subPanelCustomCSS.elements.hideRightSidebarSearch.checked; USER_CONFIG.hideMostRelevant = subPanelCustomCSS.elements.hideMostRelevant.checked; USER_CONFIG.hideBrandText = subPanelCustomCSS.elements.hideBrandText.checked; USER_CONFIG.visibleCountryCode = subPanelCustomCSS.elements.visibleCountryCode.checked; USER_CONFIG.visibleCountryCodeColor = subPanelCustomCSS.elements.visibleCountryCodeColor.value; USER_CONFIG.disablePlayOnHover = subPanelCustomCSS.elements.disablePlayOnHover.checked; USER_CONFIG.chronologicalNotifications = subPanelCustomCSS.elements.chronologicalNotifications.checked; USER_CONFIG.hideReadNotifications = subPanelCustomCSS.elements.hideReadNotifications.checked; USER_CONFIG.feedFilterChips = subPanelCustomCSS.elements.feedFilterChips.checked; USER_CONFIG.feedFilterChipsWatch = subPanelCustomCSS.elements.feedFilterChipsWatch.checked; USER_CONFIG.preventAutoplay = subPanelCustomCSS.elements.preventAutoplay.checked; USER_CONFIG.preventAutoplayPL = subPanelCustomCSS.elements.preventAutoplayPL.checked; USER_CONFIG.VerifiedArtist = subPanelCustomCSS.elements.VerifiedArtist.checked; USER_CONFIG.defaultQualityPremium = subPanelCustomCSS.elements.defaultQualityPremium.checked; USER_CONFIG.hideEndCards = subPanelCustomCSS.elements.hideEndCards.checked; USER_CONFIG.hideEndscreen = subPanelCustomCSS.elements.hideEndscreen.checked; USER_CONFIG.gradientBottom = subPanelCustomCSS.elements.gradientBottom.checked; USER_CONFIG.hideJoinButton = subPanelCustomCSS.elements.hideJoinButton.checked; USER_CONFIG.showPipBtn = subPanelCustomCSS.elements.showPipBtn.checked; USER_CONFIG.hideAutoplayBtn = subPanelCustomCSS.elements.hideAutoplayBtn.checked; USER_CONFIG.hidePlayNextButton = subPanelCustomCSS.elements.hidePlayNextButton.checked; USER_CONFIG.hideAirplayButton = subPanelCustomCSS.elements.hideAirplayButton.checked; USER_CONFIG.moveSaveBtn = subPanelCustomCSS.elements.moveSaveBtn.checked; USER_CONFIG.smallSubscribeButton = subPanelCustomCSS.elements.smallSubscribeButton.checked; USER_CONFIG.hideShareButton = subPanelCustomCSS.elements.hideShareButton.checked; USER_CONFIG.hideShareBtnGlobal = subPanelCustomCSS.elements.hideShareBtnGlobal.checked; USER_CONFIG.hideAskButton = subPanelCustomCSS.elements.hideAskButton.checked; USER_CONFIG.hideHashtags = subPanelCustomCSS.elements.hideHashtags.checked; USER_CONFIG.hideInfoPanel = subPanelCustomCSS.elements.hideInfoPanel.checked; USER_CONFIG.hideAddComment = subPanelCustomCSS.elements.hideAddComment.checked; USER_CONFIG.hideReplyButton = subPanelCustomCSS.elements.hideReplyButton.checked; USER_CONFIG.hidePlaylistsHome = subPanelCustomCSS.elements.hidePlaylistsHome.checked; USER_CONFIG.hideEpisodesHome = subPanelCustomCSS.elements.hideEpisodesHome.checked; USER_CONFIG.hideNewsHome = subPanelCustomCSS.elements.hideNewsHome.checked; USER_CONFIG.playProgressColor = subPanelCustomCSS.elements.playProgressColor.checked; USER_CONFIG.progressbarColorPicker = subPanelCustomCSS.elements.progressbarColorPicker.value; USER_CONFIG.lightModeSelectionColor = subPanelCustomCSS.elements.lightModeSelectionColor.value; USER_CONFIG.darkModeSelectionColor = subPanelCustomCSS.elements.darkModeSelectionColor.value; USER_CONFIG.pureBWBackground = subPanelCustomCSS.elements.pureBWBackground.checked; USER_CONFIG.noFrostedGlass = subPanelCustomCSS.elements.noFrostedGlass.checked; USER_CONFIG.removeScrubber = subPanelCustomCSS.elements.removeScrubber.checked; USER_CONFIG.autoTheaterMode = subPanelCustomCSS.elements.autoTheaterMode.checked; USER_CONFIG.autoTheaterModeNotVerticalVideo = subPanelCustomCSS.elements.autoTheaterModeNotVerticalVideo.checked; USER_CONFIG.autoTheaterModeNotLargeWindow = subPanelCustomCSS.elements.autoTheaterModeNotLargeWindow.checked; USER_CONFIG.autoTheaterModeNotPL = subPanelCustomCSS.elements.autoTheaterModeNotPL.checked; USER_CONFIG.autoExitFullscreen = subPanelCustomCSS.elements.autoExitFullscreen.checked; USER_CONFIG.enableCinemaMode = subPanelCustomCSS.elements.enableCinemaMode.checked; USER_CONFIG.maxVidSize = subPanelCustomCSS.elements.maxVidSize.checked; USER_CONFIG.expandVideoDescription = subPanelCustomCSS.elements.expandVideoDescription.checked; USER_CONFIG.channelRSSBtn = subPanelCustomCSS.elements.channelRSSBtn.checked; USER_CONFIG.channelPlaylistBtn = subPanelCustomCSS.elements.channelPlaylistBtn.checked; USER_CONFIG.playlistDirectionBtns = subPanelCustomCSS.elements.playlistDirectionBtns.checked; USER_CONFIG.playlistLinks = subPanelCustomCSS.elements.playlistLinks.checked; USER_CONFIG.playlistTrashCan = subPanelCustomCSS.elements.playlistTrashCan.checked; USER_CONFIG.playlistQueueBtn = subPanelCustomCSS.elements.playlistQueueBtn.checked; USER_CONFIG.plWLBtn = subPanelCustomCSS.elements.plWLBtn.checked; USER_CONFIG.commentsNewFirst = subPanelCustomCSS.elements.commentsNewFirst.checked; USER_CONFIG.hideFundraiser = subPanelCustomCSS.elements.hideFundraiser.checked; USER_CONFIG.hideLatestPostsHome = subPanelCustomCSS.elements.hideLatestPostsHome.checked; USER_CONFIG.hideMiniPlayer = subPanelCustomCSS.elements.hideMiniPlayer.checked; USER_CONFIG.hideQueueBtn = subPanelCustomCSS.elements.hideQueueBtn.checked; USER_CONFIG.closeChatWindow = subPanelCustomCSS.elements.closeChatWindow.checked; USER_CONFIG.lnbHideHomeBtn = subPanelCustomCSS.elements.lnbHideHomeBtn.checked; USER_CONFIG.lnbHideSubscriptionsBtn = subPanelCustomCSS.elements.lnbHideSubscriptionsBtn.checked; USER_CONFIG.lnbRestoreOrder = subPanelCustomCSS.elements.lnbRestoreOrder.checked; USER_CONFIG.lnbHideHistoryBtn = subPanelCustomCSS.elements.lnbHideHistoryBtn.checked; USER_CONFIG.lnbHidePlaylistsBtn = subPanelCustomCSS.elements.lnbHidePlaylistsBtn.checked; USER_CONFIG.lnbHideVideosBtn = subPanelCustomCSS.elements.lnbHideVideosBtn.checked; USER_CONFIG.lnbHideCoursesBtn = subPanelCustomCSS.elements.lnbHideCoursesBtn.checked; USER_CONFIG.lnbHideYPodcastsBtn = subPanelCustomCSS.elements.lnbHideYPodcastsBtn.checked; USER_CONFIG.lnbHideWlBtn = subPanelCustomCSS.elements.lnbHideWlBtn.checked; USER_CONFIG.lnbHideLikedVideosBtn = subPanelCustomCSS.elements.lnbHideLikedVideosBtn.checked; USER_CONFIG.lnbHideDLBtn = subPanelCustomCSS.elements.lnbHideDLBtn.checked; USER_CONFIG.lnbHideYouBtn = subPanelCustomCSS.elements.lnbHideYouBtn.checked; USER_CONFIG.lnbHideYouSection = subPanelCustomCSS.elements.lnbHideYouSection.checked; USER_CONFIG.lnbHideUrChannelBtn = subPanelCustomCSS.elements.lnbHideUrChannelBtn.checked; USER_CONFIG.lnbHideSubscriptionsSection = subPanelCustomCSS.elements.lnbHideSubscriptionsSection.checked; USER_CONFIG.lnbHideSubscriptionsTitle = subPanelCustomCSS.elements.lnbHideSubscriptionsTitle.checked; USER_CONFIG.lnbHideMoreBtn = subPanelCustomCSS.elements.lnbHideMoreBtn.checked; USER_CONFIG.lnbHideExploreSection = subPanelCustomCSS.elements.lnbHideExploreSection.checked; USER_CONFIG.lnbHideExploreTitle = subPanelCustomCSS.elements.lnbHideExploreTitle.checked; USER_CONFIG.lnbHideTrendingBtn = subPanelCustomCSS.elements.lnbHideTrendingBtn.checked; USER_CONFIG.lnbHideMusicBtn = subPanelCustomCSS.elements.lnbHideMusicBtn.checked; USER_CONFIG.lnbHideMoviesBtn = subPanelCustomCSS.elements.lnbHideMoviesBtn.checked; USER_CONFIG.lnbHideLiveBtn = subPanelCustomCSS.elements.lnbHideLiveBtn.checked; USER_CONFIG.lnbHideGamingBtn = subPanelCustomCSS.elements.lnbHideGamingBtn.checked; USER_CONFIG.lnbHideNewsBtn = subPanelCustomCSS.elements.lnbHideNewsBtn.checked; USER_CONFIG.lnbHideSportsBtn = subPanelCustomCSS.elements.lnbHideSportsBtn.checked; USER_CONFIG.lnbHideLearningBtn = subPanelCustomCSS.elements.lnbHideLearningBtn.checked; USER_CONFIG.lnbHideFashionBtn = subPanelCustomCSS.elements.lnbHideFashionBtn.checked; USER_CONFIG.lnbHidePlayablesBtn = subPanelCustomCSS.elements.lnbHidePlayablesBtn.checked; USER_CONFIG.lnbHidePodcastsBtn = subPanelCustomCSS.elements.lnbHidePodcastsBtn.checked; USER_CONFIG.lnbHideMoreSection = subPanelCustomCSS.elements.lnbHideMoreSection.checked; USER_CONFIG.lnbHideMoreTitle = subPanelCustomCSS.elements.lnbHideMoreTitle.checked; USER_CONFIG.lnbHideYtPremiumBtn = subPanelCustomCSS.elements.lnbHideYtPremiumBtn.checked; USER_CONFIG.lnbHideYtStudioBtn = subPanelCustomCSS.elements.lnbHideYtStudioBtn.checked; USER_CONFIG.lnbHideYtMusicBtn = subPanelCustomCSS.elements.lnbHideYtMusicBtn.checked; USER_CONFIG.lnbHideYtKidsBtn = subPanelCustomCSS.elements.lnbHideYtKidsBtn.checked; USER_CONFIG.lnbHidePenultimateSection = subPanelCustomCSS.elements.lnbHidePenultimateSection.checked; USER_CONFIG.lnbHideSettingsBtn = subPanelCustomCSS.elements.lnbHideSettingsBtn.checked; USER_CONFIG.lnbHideReportHistoryBtn = subPanelCustomCSS.elements.lnbHideReportHistoryBtn.checked; USER_CONFIG.lnbHideHelpBtn = subPanelCustomCSS.elements.lnbHideHelpBtn.checked; USER_CONFIG.lnbHideFeedbackBtn = subPanelCustomCSS.elements.lnbHideFeedbackBtn.checked; USER_CONFIG.lnbHideFooter = subPanelCustomCSS.elements.lnbHideFooter.checked; USER_CONFIG.displayFullTitle = subPanelCustomCSS.elements.displayFullTitle.checked; USER_CONFIG.squareSearchBar = subPanelCustomCSS.elements.squareSearchBar.checked; USER_CONFIG.squareDesign = subPanelCustomCSS.elements.squareDesign.checked; USER_CONFIG.squareAvatars = subPanelCustomCSS.elements.squareAvatars.checked; USER_CONFIG.compactLayout = subPanelCustomCSS.elements.compactLayout.checked; USER_CONFIG.compactLayoutVideo = subPanelCustomCSS.elements.compactLayoutVideo.checked; USER_CONFIG.maxPanelHeight = subPanelCustomCSS.elements.maxPanelHeight.checked; USER_CONFIG.noAmbientMode = subPanelCustomCSS.elements.noAmbientMode.checked; USER_CONFIG.noVideoZoom = subPanelCustomCSS.elements.noVideoZoom.checked; } // save sub panels - color code videos if (subPanelColor) { USER_CONFIG.colorCodeVideosEnabled = subPanelColor.elements.colorCodeVideosEnabled.checked; USER_CONFIG.homeDisableHover = subPanelColor.elements.homeDisableHover.checked; USER_CONFIG.videosOldOpacity = parseFloat(subPanelColor.elements.videosOldOpacity.value); USER_CONFIG.videosAgeColorPickerNewly = subPanelColor.elements.videosAgeColorPickerNewly.value; USER_CONFIG.videosAgeColorPickerNewlyLight = subPanelColor.elements.videosAgeColorPickerNewlyLight.value; USER_CONFIG.videosAgeColorPickerRecent = subPanelColor.elements.videosAgeColorPickerRecent.value; USER_CONFIG.videosAgeColorPickerRecentLight = subPanelColor.elements.videosAgeColorPickerRecentLight.value; USER_CONFIG.videosAgeColorPickerLately = subPanelColor.elements.videosAgeColorPickerLately.value; USER_CONFIG.videosAgeColorPickerLatelyLight = subPanelColor.elements.videosAgeColorPickerLatelyLight.value; USER_CONFIG.videosAgeColorPickerLatterly = subPanelColor.elements.videosAgeColorPickerLatterly.value; USER_CONFIG.videosAgeColorPickerLatterlyLight = subPanelColor.elements.videosAgeColorPickerLatterlyLight.value; USER_CONFIG.videosAgeColorPickerOld = subPanelColor.elements.videosAgeColorPickerOld.value; USER_CONFIG.videosAgeColorPickerOldLight = subPanelColor.elements.videosAgeColorPickerOldLight.value; USER_CONFIG.videosAgeColorPickerLive = subPanelColor.elements.videosAgeColorPickerLive.value; USER_CONFIG.videosAgeColorPickerLiveLight = subPanelColor.elements.videosAgeColorPickerLiveLight.value; USER_CONFIG.videosAgeColorPickerStreamed = subPanelColor.elements.videosAgeColorPickerStreamed.value; USER_CONFIG.videosAgeColorPickerStreamedLight = subPanelColor.elements.videosAgeColorPickerStreamedLight.value; USER_CONFIG.videosAgeColorPickerUpcoming = subPanelColor.elements.videosAgeColorPickerUpcoming.value; USER_CONFIG.videosAgeColorPickerUpcomingLight = subPanelColor.elements.videosAgeColorPickerUpcomingLight.value; USER_CONFIG.lastSeenVideo = subPanelColor.elements.lastSeenVideo.checked; USER_CONFIG.lastSeenVideoScroll = subPanelColor.elements.lastSeenVideoScroll.checked; USER_CONFIG.lastSeenVideoColor = subPanelColor.elements.lastSeenVideoColor.value; USER_CONFIG.lastSeenVideoColorLight = subPanelColor.elements.lastSeenVideoColorLight.value; USER_CONFIG.watchLaterGold = subPanelColor.elements.watchLaterGold.value; USER_CONFIG.watchLaterSilver = subPanelColor.elements.watchLaterSilver.value; USER_CONFIG.watchLaterBronze = subPanelColor.elements.watchLaterBronze.value; } // save updated config try { await GM.setValue('USER_CONFIG', USER_CONFIG); window.closeAlchemySettingsModal(true); showNotification('Settings have been updated!', true); setTimeout(() => location.reload(), 1000); } catch (error) { showNotification('Error saving settings!'); console.error("YouTubeAlchemy: Error saving user configuration:", error); } } // export and import settings async function exportSettings() { try { const scriptVersion = GM.info.script.version; const settingsString = JSON.stringify(USER_CONFIG, null, 2); const filename = `YouTube-Alchemy_v${scriptVersion}_Backup_${new Date().toISOString().replace(/[:.]/g, '-')}.json`; const blob = new Blob([settingsString], { type: 'application/json' }); const file = new File([blob], filename, { type: 'application/json' }); if ((navigator.maxTouchPoints > 0 || /iPhone|iPad|Android/i.test(navigator.userAgent)) && navigator.canShare && navigator.canShare({ files: [file] })) { await navigator.share({ files: [file] }); } else { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; docBody.appendChild(a); a.click(); docBody.removeChild(a); URL.revokeObjectURL(url); } showNotification('Settings have been exported.'); } catch (error) { showNotification("Error exporting settings!"); console.error("YouTubeAlchemy: Error exporting user settings:", error); } } let fileInputSettings; function importSettings() { const handleFile = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { const fileContent = event.target.result; try { const importedConfig = JSON.parse(fileContent); if (typeof importedConfig === 'object' && importedConfig !== null) { USER_CONFIG = { ...DEFAULT_CONFIG, ...importedConfig }; GM.setValue('USER_CONFIG', USER_CONFIG); window.closeAlchemySettingsModal(true); showNotification('Settings have been imported.', true); setTimeout(() => location.reload(), 1000); } else { showNotification('Invalid JSON format!'); } } catch (error) { showNotification('Invalid JSON format!'); } }; reader.readAsText(file); }; if (!fileInputSettings) { fileInputSettings = document.createElement('input'); fileInputSettings.type = 'file'; fileInputSettings.accept = 'application/json'; fileInputSettings.id = 'fileInputSettings'; fileInputSettings.style.position = 'fixed'; fileInputSettings.style.left = '-9999px'; fileInputSettings.style.top = '0'; fileInputSettings.style.width = '1px'; fileInputSettings.style.height = '1px'; fileInputSettings.style.opacity = '0'; fileInputSettings.addEventListener('change', handleFile); docBody.appendChild(fileInputSettings); } else fileInputSettings.value = ''; typeof fileInputSettings.showPicker === 'function' ? fileInputSettings.showPicker() : fileInputSettings.click(); } // set max video size in default view function maxVideoSize() { const HTML = document.documentElement; const watchFlexy = document.querySelector('ytd-watch-flexy'); const video = watchFlexy?.querySelector('video.html5-main-video'); if (!HTML || !watchFlexy || !video) return; const styleWF = getComputedStyle(watchFlexy); const styleHTML = getComputedStyle(HTML); const aspectRatioWidth = parseFloat(styleWF.getPropertyValue('--ytd-watch-flexy-width-ratio')) || 16; const aspectRatioHeight = parseFloat(styleWF.getPropertyValue('--ytd-watch-flexy-height-ratio')) || 9; const aspectRatio = (video.videoWidth && video.videoHeight) ? video.videoWidth / video.videoHeight : aspectRatioWidth / aspectRatioHeight; const sidebarWidth = parseFloat(styleWF.getPropertyValue('--ytd-watch-flexy-sidebar-width')) || 402; const mastheadHeight = parseFloat(styleHTML.getPropertyValue('--ytd-toolbar-height')) || 56; const marginTop = USER_CONFIG.compactLayoutVideo ? 0 : watchFlexy.hasAttribute('reduced-top-margin') ? 12 : 24; const horizontalPageMargin = parseFloat(watchFlexy.style.getPropertyValue('--ytd-watch-flexy-horizontal-page-margin')) || 24; const margin = USER_CONFIG.compactLayoutVideo ? 0 : horizontalPageMargin; const space = USER_CONFIG.spaceBelowPlayer; const marginBelow = USER_CONFIG.maxVidSize ? 0 : space; const calculate = () => { const maxWidth = window.innerWidth - sidebarWidth - margin * 3; const maxHeight = window.innerHeight - mastheadHeight - marginTop - marginBelow; let width = maxWidth; let height = width / aspectRatio; if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; } return { width, height }; }; const maxSize = function () { if (this.theater) return { width: NaN, height: NaN }; return calculate(); }; watchFlexy.calculateNormalPlayerSize_ = maxSize; watchFlexy.calculateCurrentPlayerSize_ = maxSize; window.dispatchEvent(new Event('resize')); } // get video thumbnail function getThumbnailUrl(callback) { const thumbnailOverlayURL = watchFlexyElement.querySelector(".ytp-cued-thumbnail-overlay-image")?.style.backgroundImage; if (thumbnailOverlayURL && initialRun) { callback(thumbnailOverlayURL); return; } // backup I const baseUrl = `https://i.ytimg.com/vi/${videoID}/`; const jsonCheck = document.querySelector('ytd-watch-flexy script[type="application/ld+json"]'); const jsonText = jsonCheck?.textContent; const videoIDcheck = jsonText?.includes(`/vi/${videoID}/`); const resolutionCheck = jsonText?.includes('maxresdefault.jpg'); if (videoIDcheck) { callback(`url("${baseUrl + (resolutionCheck ? 'maxresdefault.jpg' : 'hqdefault.jpg')}")`); return; } // backup II const img = new Image(); img.onload = () => callback(`url("${baseUrl + (img.width > 250 ? 'maxresdefault.jpg' : 'hqdefault.jpg')}")`); img.onerror = () => callback(`url("${baseUrl}hqdefault.jpg")`); img.src = baseUrl + 'maxresdefault.jpg'; } // builds key mappings for playback speed controls function speedKeyMap() { const lower = i => (i == null ? '' : String(i).toLowerCase()); toggleKey = lower(USER_CONFIG.playbackSpeedToggle); increaseKey = lower(USER_CONFIG.playbackSpeedIncrease); decreaseKey = lower(USER_CONFIG.playbackSpeedDecrease); const youtubeKeys = ['<', '>']; defaultKeys = new Set([toggleKey, increaseKey, decreaseKey, ...youtubeKeys]); specialKeys = Object.create(null); for (let i = 1; i <= 8; i++) { const key = lower(USER_CONFIG[`playbackSpeedKey${i}`]); const speed = USER_CONFIG[`playbackSpeedKey${i}s`]; if (key && speed != null) specialKeys[key] = speed; } speedKeysMapped = true; } // helper to save a playback specific speed for channels async function saveChannelPlaybackSpeeds(video) { if (!channelHandle || !video) return; const speed = video.playbackRate; const saved = Object.entries(USER_CONFIG.channelPlaybackSpeeds).map(([c, s]) => `${c}: ${s}x`).join(' | '); const input = window.prompt(`Save ${speed}x as the default playback speed for ${channelHandle}?\n\nType "reset" to remove the saved speed for this channel.\nType "DELETE ALL" to remove all saved values.\n\n— Saved Speeds —\n${saved || 'None'}`); if (input === null) return; if (input === 'reset') { delete USER_CONFIG.channelPlaybackSpeeds[channelHandle]; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Reset channel speed for ${channelHandle}.`); return; } if (input === 'DELETE ALL') { USER_CONFIG.channelPlaybackSpeeds = {}; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Deleted all saved channel speeds.`); return; } if (speed === USER_CONFIG.playbackSpeedValue) { showNotification(`${speed} is already the default playback speed.`); return; } USER_CONFIG.channelPlaybackSpeeds[channelHandle] = speed; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Saved ${speed}x for ${channelHandle}.`); } // helper to save a default page for channels async function saveChannelDefaultPage() { if (!channelHandleURL || !isChannelPage) return; const currentChannelPage = window.location.pathname.match(/^\/(?:@[^/]+|channel\/[^/]+)(?:\/([^/]+))?/)?.[1] || 'featured'; const saved = Object.entries(USER_CONFIG.channelDefaultPages).map(([c, p]) => `${c}: ${p}`).join(' | '); const input = window.prompt(`Save ${currentChannelPage} as the default page for ${channelHandleURL}?\n\nType "reset" to remove the saved page for this channel.\nType "DELETE ALL" to remove all saved values.\n\n— Saved Pages —\n${saved || 'None'}`); if (input === null) return; if (input === 'reset') { delete USER_CONFIG.channelDefaultPages[channelHandleURL]; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Reset channel page for ${channelHandleURL}.`); return; } if (input === 'DELETE ALL') { USER_CONFIG.channelDefaultPages = {}; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Deleted all saved channel pages.`); return; } if (currentChannelPage === USER_CONFIG.defaultChannelPage) { showNotification(`${currentChannelPage} is already the default channel page.`); return; } USER_CONFIG.channelDefaultPages[channelHandleURL] = currentChannelPage; await GM.setValue('USER_CONFIG', USER_CONFIG); showNotification(`Saved ${currentChannelPage} for ${channelHandleURL}.`); } // function to display a notification for settings change or reset function showNotification(message, reload = false) { const overlay = document.createElement('div'); if (reload) { overlay.classList.add('reload'); notificationTimer = 5000; } overlay.classList.add('CentAnni-overlay', 'active'); const modal = document.createElement('div'); modal.classList.add('CentAnni-notification'); modal.textContent = message; overlay.appendChild(modal); docBody.appendChild(overlay); setTimeout(() => { overlay.classList.remove('active'); setTimeout(() => overlay.remove(), 200); }, notificationTimer); } // function to add the transcript exporter buttons function buttonLocation(buttons, callback) { if (endElement) { buttons.forEach(({ id, text, clickHandler, tooltip, ariaLabel }) => { // button wrapper const buttonWrapper = document.createElement('div'); buttonWrapper.classList.add('CentAnni-button-wrapper', id); buttonWrapper.style.setProperty('--centanni-tooltip-anchor', `--${id}-tooltip`); // buttons const button = document.createElement('button'); button.id = id; button.textContent = text; button.classList.add('button-style'); button.setAttribute('aria-label', ariaLabel); if (id === 'transcript-settings-button') button.classList.add('button-style-settings'); button.onclick = clickHandler; // tooltip div const tooltipDiv = document.createElement('div'); tooltipDiv.textContent = tooltip; tooltipDiv.classList.add('button-tooltip'); // tooltip arrow const arrowDiv = document.createElement('div'); arrowDiv.classList.add('button-tooltip-arrow'); // append button elements buttonWrapper.appendChild(button); buttonWrapper.appendChild(tooltipDiv); buttonWrapper.appendChild(arrowDiv); id === 'transcript-settings-button' && USER_CONFIG.settingsGuide && headerElement ? (settingsBtnMoved = buttonWrapper, headerElement.append(buttonWrapper)) : endElement.prepend(buttonWrapper); }); } else { const observer = new MutationObserver((mutations, obs) => { const masthead = document.querySelector('#masthead #end'); if (masthead) { obs.disconnect(); updateCachedElements(); buttonsLeftHeader(); if (callback) callback(); } }); observer.observe(docBody, { childList: true, subtree: true }); } } async function runYTE() { try { await preLoadTranscript(); !USER_CONFIG.YouTubeTranscriptExporter ? createButtons('settings') : createButtons('all'); } catch (error) { setTimeout(() => { createButtons((isLive || !hasTranscriptPanel) ? ['settings'] : ['settings', 'error']); }, 2000); } } const transcriptError = () => { useSearchableTranscript = !useSearchableTranscript; runYTE(); }; function createButtons(buttonType = 'all') { const allButtons = { settings: USER_CONFIG.settingsGuide && settingsBtnMoved?.isConnected ? null : { id: 'transcript-settings-button', text: USER_CONFIG.buttonIcons.settings, clickHandler: showSettingsModal, tooltip: 'YouTube Alchemy settings', ariaLabel: 'YouTube Alchemy settings' }, download: { 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' }, copy: { id: 'transcript-copy-button', text: USER_CONFIG.buttonIcons.copy, clickHandler: handleCopyClick, tooltip: 'Copy transcript to clipboard', ariaLabel: 'Copy transcript to clipboard' }, chatgpt: { id: 'transcript-ChatGPT-button', text: USER_CONFIG.buttonIcons.ChatGPT, clickHandler: handleChatGPTClick, tooltip: `Copy transcript with a prompt and open ${ChatGPTLabel}`, ariaLabel: `Copy transcript to clipboard with a prompt and open ${ChatGPTLabel}` }, notebooklm: { id: 'transcript-NotebookLM-button', text: USER_CONFIG.buttonIcons.NotebookLM, clickHandler: handleNotebookLMClick, tooltip: `Copy transcript and open ${NotebookLMLabel}`, ariaLabel: `Copy transcript to clipboard and open ${NotebookLMLabel}` }, lazyload: { id: 'transcript-lazy-button', text: USER_CONFIG.buttonIcons.lazyLoad, clickHandler: runYTE, tooltip: 'Load the transcript', ariaLabel: 'Load the transcript' }, error: { id: 'transcript-error-button', text: '⚠️', clickHandler: transcriptError, tooltip: 'Retry loading transcript', ariaLabel: 'Retry loading transcript' } }; let buttonsToCreate = []; switch (buttonType) { case 'all': buttonsToCreate = Object.values(allButtons).filter(button => button && (button === allButtons.settings || (button.text && button.text.trim() !== '' && button !== allButtons.lazyload && button !== allButtons.error))); break; default: if (Array.isArray(buttonType)) buttonsToCreate = buttonType.map(type => allButtons[type]).filter(Boolean); else { buttonsToCreate = allButtons[buttonType]; buttonsToCreate = buttonsToCreate ? [buttonsToCreate] : []; } } endElement.querySelectorAll('.CentAnni-button-wrapper').forEach(el => el.remove()); if (buttonsToCreate.length) buttonLocation(buttonsToCreate, () => createButtons(buttonType)); } // functions to handle the button clicks function handleChatGPTClick() { handleTranscriptAction(function () { selectAndCopyTranscript('ChatGPT'); }); } function handleNotebookLMClick() { handleTranscriptAction(function () { selectAndCopyTranscript('NotebookLM'); }); } function handleCopyClick() { handleTranscriptAction(function () { selectAndCopyTranscript('Copy2Clipboard'); }); } function handleDownloadClick() { handleTranscriptAction(downloadTranscriptAsText); } // function to switch between transcript panels function getTranscriptMode() { if (useSearchableTranscript) { return { panel: () => watchFlexyElement.querySelector(transcriptSearchableSel), root: panel => panel?.querySelector(transcriptSearchableContainerSel), readySel: transcriptSearchableSegmentSel, preloadSel: transcriptSearchableSel, timeout: 10000, elements: root => root.children, chapterTitleElement: element => element.tagName === 'YTD-TRANSCRIPT-SECTION-HEADER-RENDERER' ? element.querySelector('h2 > span') : null, timeElement: element => element.querySelector('.segment-timestamp'), textElement: element => element.querySelector('.segment-text') }; } return { panel: () => watchFlexyElement.querySelector(`#panels ${transcriptListSel}`)?.closest('ytd-engagement-panel-section-list-renderer') || null, root: panel => panel?.querySelector(transcriptListSel), readySel: transcriptSegmentSel, preloadSel: '#panels ytd-engagement-panel-section-list-renderer[target-id="PAmodern_transcript_view"], #panels ytd-engagement-panel-section-list-renderer:not([target-id])', timeout: 5000, elements: root => root.querySelectorAll('macro-markers-panel-item-view-model'), chapterTitleElement: element => element.querySelector('timeline-chapter-view-model .ytwTimelineChapterViewModelTitle'), timeElement: element => element.querySelector('.ytwTranscriptSegmentViewModelTimestamp'), textElement: element => element.querySelector('.ytAttributedStringHost[role="text"]') }; } // function to check for a transcript function handleTranscriptAction(callback) { setTimeout(() => endElement.classList.remove("disabled"), endElement.classList.add("disabled") || 1000); const checkPanel = getTranscriptPanel(); if (!checkPanel) { console.error("YouTubeAlchemy: Transcript button or section not found. Subtitles/closed captions are unavailable or the language is unsupported. Reload this page to try again."); alert('Transcript unavailable or cannot be found.\nEnsure the video has a transcript.\nReload this page to try again.'); return; } if (!isTranscriptReady(checkPanel)) { console.error("YouTubeAlchemy: Transcript has not loaded."); alert('Transcript has not loaded successfully.\nReload this page to try again.'); return; } callback(); } // function to get video information async function getVideoInfo(playbackspeed = false) { let channelName; let isCollaborators = false; if (!playbackspeed) channelName = watchFlexyElement.querySelector('#channel-name a')?.textContent.trim(); if (!channelName) { const popupContainer = document.querySelector('ytd-app ytd-popup-container.ytd-app'); const attributedChannel = watchFlexyElement.querySelector('#attributed-channel-name a'); if (popupContainer && attributedChannel) { docElement.classList.add('CentAnni-hide-collabpanel'); attributedChannel.click(); await new Promise(resolve => { if (popupContainer.querySelector('.ytListItemViewModelHost')) return resolve(); const CollabPanelObserver = new MutationObserver(() => { if (!popupContainer.querySelector('.ytListItemViewModelHost')) return; clearTimeout(CollabPanelObserver.timer); CollabPanelObserver.disconnect(); resolve(); }); CollabPanelObserver.observe(popupContainer, { childList: true, subtree: true }); CollabPanelObserver.timer = setTimeout(() => { CollabPanelObserver.disconnect(); resolve(); }, 500); }); const videoCollab = [...document.querySelectorAll('.ytListItemViewModelHost')].map(element => playbackspeed ? element.querySelector('.ytListItemViewModelSubtitle')?.textContent.match(/@([^\s\u2066-\u2069]+)/)?.[1] : element.querySelector('a')?.textContent.trim()).filter(Boolean); if (playbackspeed) channelName = videoCollab; else { const collabFormatted = videoCollab.length > 2 ? videoCollab.slice(0, -1).join(', ') + ', and ' + videoCollab.at(-1) : videoCollab.join(' and '); channelName = collabFormatted || attributedChannel?.textContent.trim() || 'N/A'; } isCollaborators = true; requestAnimationFrame(() => requestAnimationFrame(() => { docBody.click(); requestAnimationFrame(() => requestAnimationFrame(() => docElement.classList.remove('CentAnni-hide-collabpanel'))); })); } else channelName = 'N/A'; } if (playbackspeed) return { channelName }; const ytTitle = watchFlexyElement.querySelector('h1.ytd-watch-metadata yt-formatted-string')?.textContent.trim() || 'N/A'; const uploadDate = watchFlexyElement.querySelector('#info-strings yt-formatted-string')?.textContent.trim() || 'N/A'; const videoURL = window.location.href; return { ytTitle, channelName, uploadDate, videoURL, isCollaborators }; } function getTranscriptPanel() { const mode = getTranscriptMode(); transcriptPanel = mode.panel(); hasTranscriptPanel = useSearchableTranscript ? !!transcriptPanel : !!(transcriptPanel || watchFlexyElement.querySelector(transcriptButtonSel)); return transcriptPanel; } function openTranscript(preload = false) { const panel = getTranscriptPanel(); if (!panel) return null; transcriptPanel = panel; if (preload) panel.classList.add("transcript-preload"); panel.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"); panel.visibility = "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"; return panel; } const clickTranscriptBtn = () => { watchFlexyElement.querySelector(transcriptButtonSel)?.click(); }; function closeTranscriptPanel(panel = transcriptPanel) { panel?.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"); if (panel) panel.visibility = "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"; } function isTranscriptReady(panel = transcriptPanel || getTranscriptPanel()) { const mode = getTranscriptMode(); const transcriptRoot = mode.root(panel); return !!transcriptRoot?.querySelector(mode.readySel); } // function to get the transcript text function getTranscriptText() { const mode = getTranscriptMode(); const panel = getTranscriptPanel(); const transcriptRoot = mode.root(panel); if (!transcriptRoot) { console.error("YouTubeAlchemy: Transcript panel not found."); return ''; } const transcriptLines = []; let currentLine = ''; let lastTime = null; for (const element of mode.elements(transcriptRoot)) { const chapterTitleElement = mode.chapterTitleElement(element); if (chapterTitleElement) { // chapter header segment if (USER_CONFIG.includeChapterHeaders) { if (currentLine) { transcriptLines.push(currentLine); currentLine = ''; } const chapterTitle = chapterTitleElement.textContent.trim(); transcriptLines.push(`\nChapter: ${chapterTitle}`); lastTime = null; } continue; } // transcript segment const timeElement = mode.timeElement(element); const textElement = mode.textElement(element); if (timeElement && textElement) { const time = timeElement.textContent.trim(); const text = textElement.textContent.replace(/\s+/g, ' ').trim(); if (time === lastTime) currentLine += ` ${text}`; else { if (currentLine) transcriptLines.push(currentLine); currentLine = USER_CONFIG.includeTimestamps ? `${time} ${text}` : text; lastTime = time; } } } if (currentLine) transcriptLines.push(currentLine); return transcriptLines.join('\n'); } // helper to determine word count const getSummaryWordTarget = (transcript) => { const l = transcript.length; if (l < 6000) return 100; if (l < 11000) return 150; if (l < 24000) return 200; if (l < 44000) return 300; if (l < 90000) return 400; return 500; }; // function to select and copy the transcript into the clipboard async function selectAndCopyTranscript(target) { const transcriptText = getTranscriptText(); const { ytTitle, channelName, uploadDate, videoURL, isCollaborators } = await getVideoInfo(); let finalText = ''; let targetUrl = ''; const author = isCollaborators ? 'Collaborators' : 'Channel'; if (target === 'ChatGPT') { const summaryWordCount = getSummaryWordTarget(transcriptText); const prompt = USER_CONFIG.ChatGPTPrompt.replace("summaryWordCount", summaryWordCount).replace(/paragraphCount/g, summaryWordCount >= 400 ? "Two-Paragraph" : "One-Paragraph"); finalText = `YouTube Transcript:\n${transcriptText.trimStart()}\n\n\nAdditional Information about the YouTube Video:\nTitle: ${ytTitle}\n${author}: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nTask Instructions:\n${prompt}`; targetUrl = USER_CONFIG.targetChatGPTUrl; } else { finalText = `Information about the YouTube Video:\nTitle: ${ytTitle}\n${author}: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nYouTube Transcript:\n${transcriptText.trimStart()}`; targetUrl = USER_CONFIG.targetNotebookLMUrl; } navigator.clipboard.writeText(finalText).then(() => { if (target === 'Copy2Clipboard') showNotification('Transcript copied to clipboard.'); else { const label = target === 'NotebookLM' ? NotebookLMLabel : ChatGPTLabel; showNotification(`Transcript copied. Opening ${label} . . .`); setTimeout(() => { USER_CONFIG.openSameTab ? window.open(targetUrl, '_self') : window.open(targetUrl, '_blank', 'noopener,noreferrer'); }, 150); } }); } // function to get the formatted transcript with video details for the text file async function getFormattedTranscript() { const transcriptText = getTranscriptText(); const { ytTitle, channelName, uploadDate, videoURL, isCollaborators } = await getVideoInfo(); const author = isCollaborators ? 'Collaborators' : 'Channel'; const finalText = `Information about the YouTube Video:\nTitle: ${ytTitle}\n${author}: ${channelName}\nUpload Date: ${uploadDate}\nURL: ${videoURL}\n\n\nYouTube Transcript:\n${transcriptText.trimStart()}`; return { finalText, ytTitle, channelName, uploadDate }; } // function to download the transcript as a text file async function downloadTranscriptAsText() { const { finalText, ytTitle, channelName, uploadDate } = await getFormattedTranscript(); const blob = new Blob([finalText], { type: 'text/plain' }); const sanitize = str => str.replace(/[<>:"/\\|?*]+/g, ''); const uploadDateFormatted = new Date(uploadDate).toLocaleDateString("en-CA"); // naming of text file based on user setting let fileName = ''; switch (USER_CONFIG.fileNamingFormat) { case 'title-channel': fileName = `${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`; break; case 'channel-title': fileName = `${sanitize(channelName)} - ${sanitize(ytTitle)}.txt`; break; case 'date-title-channel': fileName = `${sanitize(uploadDateFormatted)} - ${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`; break; case 'date-channel-title': fileName = `${sanitize(uploadDateFormatted)} - ${sanitize(channelName)} - ${sanitize(ytTitle)}.txt`; break; default: fileName = `${sanitize(ytTitle)} - ${sanitize(channelName)}.txt`; } const url = URL.createObjectURL(blob); // create a temporary anchor element to trigger the download const a = document.createElement('a'); a.href = url; a.download = fileName; a.style.display = 'none'; docBody.appendChild(a); a.click(); // clean up docBody.removeChild(a); URL.revokeObjectURL(url); showNotification('File has been downloaded.'); } // function to preload the transcript function preLoadTranscript(tryOtherPanel = false) { return new Promise((resolve, reject) => { const mode = getTranscriptMode(); endElement.querySelectorAll('.CentAnni-button-wrapper').forEach(el => el.remove()); if (isLive) { showNotificationError("Live stream, no transcript"); reject(); return; } const initialPanel = getTranscriptPanel(); const transcriptBtn = watchFlexyElement.querySelector(transcriptButtonSel); if (!initialPanel && !transcriptBtn) { showNotificationError("Transcript not available"); reject(); return; } const wrapper = document.createElement('div'); wrapper.id = 'CentAnni-notification-wrapper'; const notification = document.createElement('div'); notification.id = 'CentAnni-notification-error'; notification.classList.add("loading"); const textSpan = document.createElement("span"); textSpan.textContent = "Transcript is loading"; notification.appendChild(textSpan); wrapper.appendChild(notification); endElement.prepend(wrapper); let loaded = false; let transcriptObserver; let fallbackTimer; if (!tryOtherPanel) wasClosed = initialPanel?.getAttribute("visibility") !== "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"; if (!transcriptLoaded && !USER_CONFIG.YouTubeTranscriptExporter) wasClosed = false; const preloadPanels = new Set(); if (initialPanel) { openTranscript(true); preloadPanels.add(initialPanel); } else { watchFlexyElement.querySelectorAll(mode.preloadSel).forEach(panel => { panel.classList.add("transcript-preload"); preloadPanels.add(panel); }); transcriptBtn.click(); } const cleanup = (failed, retrying = false) => { if (fallbackTimer) clearTimeout(fallbackTimer); transcriptObserver?.disconnect(); wrapper?.remove(); if (wasClosed) closeTranscriptPanel(transcriptPanel); preloadPanels.forEach(panel => panel.classList.remove("transcript-preload")); if (!retrying) failed ? showNotificationError("Transcript failed to load") : transcriptLoaded = true; if (transcriptLoaded) { if (currentActiveTab !== 'tab-5') transcriptPanel.querySelector('#visibility-button button')?.click(); if (USER_CONFIG.defaultTranscriptLanguage !== 'auto' && useSearchableTranscript) setTimeout(setTranscriptLanguage, 250); } }; const handleTranscriptReady = () => { loaded = true; cleanup(false); }; const checkFirstItem = () => { const panel = getTranscriptPanel(); if (panel) { panel.classList.add("transcript-preload"); preloadPanels.add(panel); } if (!isTranscriptReady(panel)) return false; handleTranscriptReady(); return true; }; if (checkFirstItem()) { resolve(); return; } fallbackTimer = setTimeout(() => { if (!loaded) { console.error("YouTubeAlchemy: The transcript took too long to load. Reload this page to try again."); if (!tryOtherPanel) { cleanup(true, true); useSearchableTranscript = !useSearchableTranscript; preLoadTranscript(true).then(resolve).catch(reject); } else { cleanup(true); reject(); } } }, mode.timeout); transcriptObserver = new MutationObserver(() => { if (!initialPanel) { watchFlexyElement.querySelectorAll(mode.preloadSel).forEach(panel => { panel.classList.add("transcript-preload"); preloadPanels.add(panel); }); } if (!checkFirstItem()) return; transcriptObserver.disconnect(); resolve(); }); transcriptObserver.observe(panelsElement || transcriptPanel, { childList: true, subtree: true }); }); } // show notifications in the YouTube masthead for the transcript exporter function showNotificationError(message) { const wrapper = document.createElement('div'); wrapper.id = 'CentAnni-notification-wrapper'; const notification = document.createElement('div'); notification.id = 'CentAnni-notification-error'; notification.textContent = message; wrapper.appendChild(notification); endElement.prepend(wrapper); if (document.visibilityState === 'hidden') { document.addEventListener('visibilitychange', function handleVisibilityChange() { if (document.visibilityState === 'visible') { document.removeEventListener('visibilitychange', handleVisibilityChange); setTimeout(() => wrapper.remove(), 2000); } }); } else setTimeout(() => wrapper.remove(), 2000); } // helper function to switch theater mode function toggleTheaterMode(tabLayoutChange = false) { if (videoSizeBtn && tabLayoutChange) videoSizeBtn.click(); else { const event = new KeyboardEvent('keydown', { key: 'T', code: 'KeyT', keyCode: 84, bubbles: true, cancelable: true }); document.dispatchEvent(event); } } // helper to exit fullscreen const exitFullscreen = () => { if (!playerElement.classList.contains('countdown-running') && (!watchFlexyElement.hasAttribute('playlist') || !watchFlexyElement.querySelector('ytd-playlist-panel-video-renderer[selected].ytd-playlist-panel-renderer')?.nextElementSibling)) playerElement.toggleFullscreen(); }; // exit fullscreen when tabView is disabled function exitFullscreenNoTabView() { const fullscreenChange = () => { isFullscreen = playerElement.isFullscreen(); if (isFullscreen) document.addEventListener('yt-autonav-pause-player-ended', exitFullscreen); else document.removeEventListener('yt-autonav-pause-player-ended', exitFullscreen); }; cleanupExitFullscreenNoTabView = () => { document.removeEventListener('yt-autonav-pause-player-ended', exitFullscreen); document.removeEventListener('fullscreenchange', fullscreenChange); cleanupExitFullscreenNoTabView = null; }; if (isFullscreen) fullscreenChange(); document.addEventListener('fullscreenchange', fullscreenChange); } // set audio and subtitle language async function setLanguage() { let useSettingsMenu = false; const setLanguageViaMenu = async (categorySelector, languagePattern) => { if (!useSettingsMenu) { docElement.classList.add('CentAnni-style-hide-yt-settings'); useSettingsMenu = true; } await setTrackLanguage(categorySelector, languagePattern); }; if (USER_CONFIG.defaultAudioLanguage !== 'auto') { const audioTracks = new Map(playerElement.getAvailableAudioTracks().map(el => [el.j7.id.split(/[.-]/)[0], el])); const target = audioTracks.get(getLanguageCode(USER_CONFIG.defaultAudioLanguage)) || audioTracks.get(getLanguageCode(USER_CONFIG.secondaryLanguage)); if (!target || !playerElement.setAudioTrack(target)) await setLanguageViaMenu('.ytp-menuitem.ytp-audio-menu-item', audioInLanguage); } if (USER_CONFIG.defaultSubtitleLanguage !== 'auto') { if (USER_CONFIG.defaultSubtitleLanguage === 'off') { playerElement.setOption('captions', 'track', {}); } else { const subtitleTracks = new Map(playerElement.getOption('captions', 'tracklist', { includeAsr: true }).map(el => [el.languageCode.split('-')[0], el])); const target = subtitleTracks.get(getLanguageCode(USER_CONFIG.defaultSubtitleLanguage)) || subtitleTracks.get(getLanguageCode(USER_CONFIG.secondaryLanguage)); if (target) { playerElement.setOption('captions', 'track', target); } else { if (!uiLanguageLabels) createUiLanguageLabels(); const subtitlesLabel = uiLanguageLabels.subs.toLowerCase(); await setLanguageViaMenu(() => [...watchFlexyElement.querySelectorAll('.ytp-menuitem')].find(el => el.textContent.toLowerCase().includes(subtitlesLabel)), subtitleInLanguage); } } } if (useSettingsMenu) { docBody.click(); playerElement.focus(); docElement.classList.remove('CentAnni-style-hide-yt-settings'); } if (USER_CONFIG.preventAutoplay) playerElement.pauseVideo(); } async function setTrackLanguage(categorySelector, languagePattern) { const settingsPanel = '.ytp-settings-menu'; const settingsHeader = '.ytp-panel-header'; const settingsButton = '.ytp-settings-button'; function waitFor(selectorOrFn, ctx = document, timeout = 7000) { return new Promise((resolve, reject) => { const lookup = () => (typeof selectorOrFn === 'function') ? selectorOrFn() : ctx.querySelector(selectorOrFn); if (lookup()) { resolve(lookup()); return; } const timer = setTimeout(() => { observer.disconnect(); reject(); }, timeout); const observer = new MutationObserver(() => { const node = lookup(); if (node) { clearTimeout(timer); observer.disconnect(); resolve(node); } }); observer.observe(ctx, { childList: true, subtree: true }); }); } if (!watchFlexyElement.querySelector(settingsPanel) || getComputedStyle(watchFlexyElement.querySelector(settingsPanel)).display === 'none') { watchFlexyElement.querySelector(settingsButton).click(); await waitFor(settingsPanel); } const panel = watchFlexyElement.querySelector(settingsPanel); const headerBtn = panel.querySelector(settingsHeader); if (headerBtn) { headerBtn.click(); await waitFor(categorySelector, panel); } const categoryItem = typeof categorySelector === 'function' ? categorySelector() : panel.querySelector(categorySelector); if (!categoryItem) return; categoryItem.click(); await waitFor('.ytp-menuitem', panel); const items = [...panel.querySelectorAll('.ytp-menuitem')]; const target = items.find(el => el.textContent.trim().toLowerCase().startsWith(languagePattern)) || (secondaryInLanguage && languagePattern !== secondaryInLanguage && items.find(el => el.textContent.trim().toLowerCase().startsWith(secondaryInLanguage))); if (!target) return; target.click(); if (USER_CONFIG.defaultSubtitleLanguage === 'off') { setTimeout(() => { const ccButton = watchFlexyElement.querySelector('.ytp-chrome-bottom .ytp-subtitles-button'); if (ccButton?.getAttribute('aria-pressed') === 'true') ccButton.click(); }, 500); } await new Promise(resolve => setTimeout(resolve, 125)); } // set default transcript language function setTranscriptLanguage() { const menuTrigger = transcriptPanel.querySelector('tp-yt-paper-button#label'); const labelTextEl = document.getElementById('label-text'); if (!menuTrigger || !labelTextEl) return; const itemsArray = [...transcriptPanel.querySelectorAll('tp-yt-paper-item')]; const target = itemsArray.find(el => el.textContent.trim().toLowerCase().startsWith(transcriptInLanguage)) || (secondaryInLanguage && transcriptInLanguage !== secondaryInLanguage && itemsArray.find(el => el.textContent.trim().toLowerCase().startsWith(secondaryInLanguage))); target ? target.click() : console.error('YouTubeAlchemy: Transcript Language not found'); } // function tab view on video page function runInPage(fn) { const c = `;(${fn})();`; const s = document.createElement('script'); s.text = scriptPolicy ? scriptPolicy.createScript(c) : c; document.documentElement.appendChild(s); s.remove(); } function tabView() { // reset single to two colums for live chat windows, otherwise return if window is below 1,000 const checkYouTubeColumns = (setYouTubeColumns = false) => { if (watchFlexyElement.hasAttribute('is-single-column')) { if (window.innerWidth >= 1000) { watchFlexyElement.removeAttribute('is-single-column'); watchFlexyElement.setAttribute('is-two-columns_', ''); } else if (!setYouTubeColumns) { docElement.classList.remove('CentAnni-video-tabView'); return true; } } return false; }; if (checkYouTubeColumns()) return; let setMastheadWidth = false; const tabElementById = {}; let lastActiveTab = null; transcriptLoaded = false; currentActiveTab = null; let mastheadWidthTimer; let tabElements = []; let subheaderDiv; let isDefault; let dateSpan; // check chapter and transcript panels after navigating const panelCheck = () => { if (hasChapterPanel && !chapterPanel?.isConnected) { tabElementById['tab-4']?.classList.add('hidden'); if (currentActiveTab === 'tab-4') tabElementById['tab-1'].click(); hasChapterPanel = false; } if (hasTranscriptPanel && !transcriptPanel?.isConnected) { tabElementById['tab-5']?.classList.add('hidden'); if (currentActiveTab === 'tab-5') tabElementById['tab-1'].click(); hasTranscriptPanel = false; } }; const watchPanelObserver = new MutationObserver(panelCheck); if (!initialRun) { watchPanelObserver.observe(panelsElement, { childList: true }); watchPanelObserver.timer = setTimeout(() => watchPanelObserver.disconnect(), 5000); } // check and update sidebar and masthead width const updateMastheadWidth = () => { if (USER_CONFIG.sidebarWidth === 0) { const styleWFE = getComputedStyle(watchFlexyElement); const sidebarWidth = styleWFE.getPropertyValue('--ytd-watch-flexy-sidebar-width').trim(); docElement.style.setProperty('--sidebarWidth', sidebarWidth); } const columnsRight = watchFlexyElement.querySelector('#secondary').getBoundingClientRect().right; const hasRightSpace = docElement.clientWidth - columnsRight > .5; if (hasRightSpace) docElement.style.setProperty('--mastheadWidth', `${watchFlexyElement.querySelector('#primary').getBoundingClientRect().right}px`); else docElement.style.removeProperty('--mastheadWidth'); setMastheadWidth = true; }; const checkMastheadWidth = () => { if (isTheaterMode) setMastheadWidth = false; else { clearTimeout(mastheadWidthTimer); mastheadWidthTimer = setTimeout(() => { if (isTheaterMode) setMastheadWidth = false; else requestAnimationFrame(updateMastheadWidth); }, 250); } }; // prevent page scrolling due to opening the transcript panel const nativeScrollTop = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop"); Object.defineProperty(HTMLHtmlElement.prototype, "scrollTop", { configurable: true, enumerable: nativeScrollTop.enumerable, get() { return nativeScrollTop.get.call(this); }, set() {} }); // fixed side menu and split scroll fix const removeWatchFlexyAttrs = () => { watchFlexyElement.removeAttribute('split-scroll'); watchFlexyElement.removeAttribute('show-fixed-side-menu'); watchFlexyElement.removeAttribute('fixed-default-panels'); if (isDefault && currentActiveTab === 'tab-1') setTabPanelState('tab-1', true); }; const watchFlexyAttrsObserver = new MutationObserver(() => removeWatchFlexyAttrs()); const startWatchFlexyAttrsObs = () => watchFlexyAttrsObserver.observe(watchFlexyElement, { attributes: true, attributeFilter: ['split-scroll', 'show-fixed-side-menu'] }); // scroll to selected video in playlist panel const scrollSelectedVideoPL = () => { requestAnimationFrame(() => requestAnimationFrame(() => { const panel = playlistSelectedVideo.closest('.playlist-items.ytd-playlist-panel-renderer#items'); const panelTop = panel.getBoundingClientRect().top; const itemTop = playlistSelectedVideo.getBoundingClientRect().top; panel.scrollTop += itemTop - panelTop - panel.clientHeight / 2 + playlistSelectedVideo.clientHeight / 2; })); }; // determine the default tab -- priority: transcript > chapters > comments > playlists > info function determineActiveTab() { let activeTabId = 'tab-1'; if (USER_CONFIG.autoOpenTranscript && !isLive && hasTranscriptPanel) return 'tab-5'; if (USER_CONFIG.autoOpenChapters && !isLive && hasChapterPanel) return 'tab-4'; if (USER_CONFIG.autoOpenComments && !isLive) return 'tab-2'; if (hasPlaylistPanel || (checkPlaylistPanel && !document.getElementById('playlist')?.hasAttribute('hidden'))) return 'tab-6'; return activeTabId; } // update and set active tab function activateTab(tabId) { tabElementById[tabId]?.classList.add('active'); currentActiveTab = tabId; } // update tabView based on player layout function updateTabView() { isDefault = watchFlexyElement.hasAttribute('default-layout'); if (!isDefault) { isTheaterMode = true; if (tabElementById[currentActiveTab]?.classList.contains('active')) lastActiveTab = currentActiveTab; tabElements.forEach(obj => obj.element.classList.remove('active')); currentActiveTab = null; if (subheaderDiv) subheaderDiv.onclick = handleTabViewTabClick; } else { if (lastActiveTab) activateTab(lastActiveTab); else activateTab(determineActiveTab()); isDefault === isTheaterMode ? requestAnimationFrame(() => requestAnimationFrame(() => setTabPanelState(currentActiveTab, true))) : setTabPanelState(currentActiveTab, true); subheaderDiv.onclick = null; isTheaterMode = false; if (USER_CONFIG.maxPanelHeight && !setMastheadWidth) updateMastheadWidth(); } } // handle fullscreen change const fullscreenCheck = () => { isFullscreen = playerElement.isFullscreen(); if (isFullscreen) { if (isDefault) lastActiveTab = currentActiveTab; if (USER_CONFIG.autoExitFullscreen) document.addEventListener('yt-autonav-pause-player-ended', exitFullscreen); if (USER_CONFIG.tabViewChapters && hasChapterPanel && updateTitleContainer) requestAnimationFrame(() => requestAnimationFrame(() => updateTitleContainer())); } else { document.removeEventListener('yt-autonav-pause-player-ended', exitFullscreen); requestAnimationFrame(() => requestAnimationFrame(() => { checkYouTubeColumns(true); updateTabView(); if (USER_CONFIG.maxPanelHeight) checkMastheadWidth(); if (USER_CONFIG.tabViewChapters && hasChapterPanel && updateTitleContainer) updateTitleContainer(); })); } }; document.addEventListener('yt-set-theater-mode-enabled', updateTabView); document.addEventListener('fullscreenchange', fullscreenCheck); if (USER_CONFIG.maxPanelHeight) window.addEventListener('resize', checkMastheadWidth); // clean up cleanupTabView = () => { subheaderDiv.onclick = null; watchPanelObserver.disconnect(); clearTimeout(watchPanelObserver.timer); window.removeEventListener('resize', checkMastheadWidth); document.removeEventListener('fullscreenchange', fullscreenCheck); document.removeEventListener('yt-set-theater-mode-enabled', updateTabView); document.removeEventListener('yt-autonav-pause-player-ended', exitFullscreen); const activeTabId = currentActiveTab || (isTheaterMode ? lastActiveTab : null); contentSectionById[activeTabId]?.classList.remove('active'); docElement.classList.remove('tabView-tab-3'); watchFlexyAttrsObserver.disconnect(); setTabPanelState(activeTabId, false); clearTimeout(mastheadWidthTimer); tabElements.forEach(tab => { tab.element.onclick = null; tab.element.classList.remove('active'); }); tabElements = []; dateSpan?.remove(); Object.defineProperty(HTMLHtmlElement.prototype, "scrollTop", nativeScrollTop); cleanupTabView = null; }; // click listener for tabView buttons function handleTabViewTabClick(event) { const tab = event.target.closest('.CentAnni-tabView-tab'); if (!tab || !isTheaterMode) return; lastActiveTab = tab.dataset.tab; toggleTheaterMode(true); } // include date in info text under videos unless live if (!isLive) { const infoContainer = document.getElementById('info-container'); const infoTime = infoContainer?.querySelector('yt-formatted-string span:nth-child(3)'); if (infoTime) { const dateStringElement = watchFlexyElement.querySelector('#info-strings yt-formatted-string'); const dateString = dateStringElement?.textContent?.trim() ?? ""; if (dateString) { dateSpan = document.createElement('span'); dateSpan.classList.add('CentAnni-info-date', 'bold', 'style-scope', 'yt-formatted-string'); dateSpan.textContent = `(${dateString})`; infoTime.parentNode.insertBefore(dateSpan, infoTime.nextSibling); } } } // grab info and more video panels let videoInfo = null; const videoMore = watchFlexyElement.querySelector('#related.ytd-watch-flexy'); // create the main tabView container const newDiv = document.createElement('div'); newDiv.classList.add('CentAnni-tabView'); if (!USER_CONFIG.toggleTheaterModeBtn) newDiv.classList.add('no-theater-toggle'); // create the header const headerDiv = document.createElement('div'); headerDiv.classList.add('CentAnni-tabView-header'); // create the subheader subheaderDiv = document.createElement('div'); subheaderDiv.classList.add('CentAnni-tabView-subheader'); // define the tabs const tabs = [ 'Info', ...(!isLive ? ['Comments'] : []), ...((!isLive && hasChapterPanel) ? ['Chapters'] : []), ...((!isLive && hasTranscriptPanel) ? ['Transcript'] : []), ...(hasDonationPanel ? ['Donation'] : []), ...((hasPlaylistPanel || checkPlaylistPanel) ? ['Playlist'] : []), 'Videos', ]; // define the IDs const tabIDs = { 'Info': 'tab-1', 'Comments': 'tab-2', 'Playlist': 'tab-6', 'Donation': 'tab-9', 'Videos': 'tab-3', 'Chapters': 'tab-4', 'Transcript': 'tab-5' }; const activePanel = { 'tab-1': videoInfo, 'tab-6': playlistPanel, 'tab-4': chapterPanel, 'tab-5': transcriptPanel }; // create content sections tabs const contentSections = []; const contentSectionById = {}; tabs.forEach((tabText, index) => { const tabId = tabIDs[tabText]; const contentDiv = document.createElement('div'); contentDiv.classList.add('CentAnni-tabView-content'); contentDiv.id = tabId; if (index === 0) { contentDiv.classList.add('active'); currentActiveTab = tabId; } contentSections.push(contentDiv); contentSectionById[tabId] = contentDiv; }); // populate the comments sections const videoComments = watchFlexyElement.querySelector(cmtsSel); const commentsSection = contentSectionById['tab-2']; if (videoComments && commentsSection) commentsSection.appendChild(videoComments); if (USER_CONFIG.autoOpenComments && commentsSection) { contentSectionById[currentActiveTab]?.classList.remove('active'); commentsSection.classList.add('active'); currentActiveTab = 'tab-2'; } // update panel visibility based on active tab async function setTabPanelState(tabId, show) { if (tabId === 'tab-1') { if (!videoInfo?.isConnected) videoInfo = watchFlexyElement.querySelector(infoSel); videoInfo.setAttribute('visibility', show ? 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' : 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN'); return; } if (tabId === 'tab-4') { if (!chapterPanel?.isConnected) return; if (show) openChapters(); else runInPage(() => (document.querySelector('ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"], ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"]')).visibility = "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"); return; } if (tabId === 'tab-5') { if (!transcriptLoaded && isDefault) await runYTE(); if (!transcriptPanel?.isConnected) return; if (show) openTranscript(); else closeTranscriptPanel(); } if (tabId === 'tab-6') { if (!playlistPanel?.isConnected) return; if (show) { playlistPanel.removeAttribute('collapsed'); playlistPanel.classList.add('CentAnni-tabView-content-active'); playlistPanel.classList.remove('CentAnni-tabView-content-hidden'); if (playlistSelectedVideo) scrollSelectedVideoPL(); } else { playlistPanel.classList.add('CentAnni-tabView-content-hidden'); playlistPanel.classList.remove('CentAnni-tabView-content-active'); } return; } if (tabId === 'tab-3') { if (!videoMore?.isConnected) return; docElement.classList.toggle('tabView-tab-3', show); if (show) { videoMore.classList.add('CentAnni-tabView-content-attiva'); videoMore.classList.remove('CentAnni-tabView-content-nascosta'); } else { videoMore.classList.add('CentAnni-tabView-content-nascosta'); videoMore.classList.remove('CentAnni-tabView-content-attiva'); } return; } if (tabId === 'tab-9') { if (!donationPanel?.isConnected) return; if (show) { donationPanel.classList.add('CentAnni-tabView-content-active'); donationPanel.classList.remove('CentAnni-tabView-content-hidden'); } else { donationPanel.classList.add('CentAnni-tabView-content-hidden'); donationPanel.classList.remove('CentAnni-tabView-content-active'); } return; } } // create each tab link and add click behavior tabs.forEach((tabText) => { const tabId = tabIDs[tabText]; const tabLink = document.createElement('a'); tabLink.classList.add('CentAnni-tabView-tab'); tabLink.textContent = tabText; tabLink.href = `#${tabId}`; tabLink.dataset.tab = tabId; const tabClickHandler = (event) => { event.preventDefault(); // if clicked tab is active enter theater mode if (currentActiveTab === tabId) { if (!isTheaterMode && USER_CONFIG.toggleTheaterModeBtn) { event.stopPropagation(); toggleTheaterMode(true); } return; } const previousTabId = currentActiveTab || (isTheaterMode ? lastActiveTab : null); currentActiveTab = tabId; // deactivate active button, hide content and panel if (previousTabId && previousTabId !== tabId) { tabElementById[previousTabId]?.classList.remove('active'); contentSectionById[previousTabId]?.classList.remove('active'); setTabPanelState(previousTabId, false); } // activate button and show content and panel tabLink.classList.add('active'); contentSectionById[tabId]?.classList.add('active'); if (!isTheaterMode) setTabPanelState(tabId, true); }; tabElements.push({ element: tabLink }); tabElementById[tabId] = tabLink; tabLink.onclick = tabClickHandler; subheaderDiv.appendChild(tabLink); }); headerDiv.appendChild(subheaderDiv); newDiv.appendChild(headerDiv); contentSections.forEach(section => newDiv.appendChild(section)); // remove split-scroll removeWatchFlexyAttrs(); startWatchFlexyAttrsObs(); // add tab view header into the DOM const secondaryElement = watchFlexyElement.querySelector('#secondary'); if (!secondaryElement) return; const oldDiv = watchFlexyElement.querySelector('.CentAnni-tabView'); oldDiv ? oldDiv.replaceWith(newDiv) : secondaryElement.insertBefore(newDiv, secondaryElement.firstChild); // initiation updateTabView(); if (isFullscreen) fullscreenCheck(); if (USER_CONFIG.tabViewChapters && hasChapterPanel) requestIdleCallback(() => chapterTitles()); } // add chapter titles under videos async function chapterTitles() { const fullscreenContainer = watchFlexyElement.querySelector(fsCnSel); const titleElement = watchFlexyElement.querySelector('.ytp-chapter-container:not([style*="display: none"])'); const normalContainer = watchFlexyElement.querySelector('#description'); if (!fullscreenContainer || !titleElement || !normalContainer) return; const parent = titleElement.parentNode; parent.removeChild(titleElement); const containerChapterTitle = document.createElement('div'); containerChapterTitle.id = 'CentAnni-chapter-title'; containerChapterTitle.classList.add('bold', 'style-scope', 'yt-formatted-string'); const label = document.createElement('span'); label.textContent = 'Chapter:'; containerChapterTitle.appendChild(label); containerChapterTitle.appendChild(titleElement); normalContainer.appendChild(containerChapterTitle); let fullscreenTimeout = null; updateTitleContainer = () => { clearTimeout(fullscreenTimeout); const currentContainer = document.getElementById('CentAnni-chapter-title'); const targetContainer = isFullscreen ? fullscreenContainer : normalContainer; const element = currentContainer || containerChapterTitle; const moveElement = () => targetContainer.appendChild(element); if (element.parentNode !== targetContainer) { if (isFullscreen) fullscreenTimeout = setTimeout(moveElement, 3000); else moveElement(); } }; if (isFullscreen) updateTitleContainer(); moveChapterTitleBack = () => { if (parent && titleElement) parent.appendChild(titleElement); clearTimeout(fullscreenTimeout); containerChapterTitle.remove(); updateTitleContainer = null; moveChapterTitleBack = null; }; } // function to hide the 'X Products' span function hideProductsSpan() { const spans = watchFlexyElement.querySelectorAll('yt-formatted-string#info > span'); spans.forEach(span => { if (span.textContent.includes('product')) span.remove(); }); } // prevent scrolling to panel; restore default behavior after 3 seconds const scrollToTop = () => { const nativeScrollTop = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop"); Object.defineProperty(HTMLHtmlElement.prototype, "scrollTop", { configurable: true, enumerable: nativeScrollTop.enumerable, get() { return nativeScrollTop.get.call(this); }, set() {} }); setTimeout(() => { Object.defineProperty(HTMLHtmlElement.prototype, "scrollTop", nativeScrollTop); }, 3000); }; // enable subtitles when muted const ccOnMute = () => { const volumeIcon = watchFlexyElement.querySelector('.ytp-volume-icon'); const ccBtn = watchFlexyElement.querySelector('.ytp-subtitles-button'); if (!volumeIcon || !ccBtn) return; let muted = playerElement.isMuted(); if (muted && !ccBtn.querySelector('svg path[d^="M21 3H3C2"]')) playerElement.toggleSubtitles(); muteObserver = new MutationObserver(() => { if (!volumeIcon.querySelector('.ytp-svg-volume-animation-speaker')) { if (!ccBtn.querySelector('svg path[d^="M21 3H3C2"]')) playerElement.toggleSubtitles(); muted = true; } else if (muted) { if (ccBtn.querySelector('svg path[d^="M21 3H3C2"]')) playerElement.toggleSubtitles(); muted = false; } }); muteObserver.observe(volumeIcon, { childList: true, subtree: true }); cleanupCcOnMute = () => { muteObserver.disconnect(); muteObserver = null; cleanupCcOnMute = null; }; }; // on rewind turn on subtitles for 10 seconds const ccOnRewind = () => { let timer; let endTime = 0; let active = false; const ccBtn = watchFlexyElement.querySelector('.ytp-subtitles-button'); if (!ccBtn) return; const handler = event => { if (event.key !== 'j' || event.repeat) return; const target = event.target; if (target instanceof Element && (target.closest('input, textarea, select') || target.isContentEditable)) return; if (!active && ccBtn.querySelector('svg path[d^="M21 3H3C2"]')) return; if (!active) { playerElement.toggleSubtitles(); active = true; endTime = Date.now(); } endTime += 10000; clearTimeout(timer); timer = setTimeout(() => { if (ccBtn.querySelector('svg path[d^="M21 3H3C2"]')) playerElement.toggleSubtitles(); active = false; }, endTime - Date.now()); }; document.addEventListener('keydown', handler); cleanupCcOnRewind = () => { document.removeEventListener('keydown', handler); clearTimeout(timer); cleanupCcOnRewind = null; }; }; // find the YouTube video const findMainVideo = () => { if (!mainVideo?.isConnected || mainVideo.readyState === 0) { mainVideo = watchFlexyElement?.querySelector('video.html5-main-video'); if (isShortPage) mainVideo = document.querySelector('ytd-shorts video.html5-main-video'); } }; // main video observer function videoObserver() { if (mainVideoObserver) return; let video = mainVideo; const videoContainer = watchFlexyElement.querySelector('.html5-video-container'); if (!videoContainer || !video) return; mainVideoObserver = new MutationObserver(() => { const videoElement = watchFlexyElement.querySelector('video.html5-main-video'); if (videoElement && videoElement !== video) { findMainVideo(); if (playbackSpeedActive) requestAnimationFrame(onRateChange); if (progressBarActive) { cleanupProgressBar(); requestAnimationFrame(keepProgressBarVisible); } if (remainingTimeActive) { cleanupRemainingTime(); requestAnimationFrame(remainingTime); } video = mainVideo; } }); mainVideoObserver.observe(videoContainer, { childList: true, subtree: true, }); } // helper to get channel playback speed for collaborators const collabPlaybackSpeedCheck = async () => { const { channelName } = await getVideoInfo(true); const savedSpeeds = channelName.map(name => USER_CONFIG.channelPlaybackSpeeds[name]).filter(Boolean); const savedSpeed = savedSpeeds.length ? Math.min(...savedSpeeds) : null; if (savedSpeed) { defaultSpeed = savedSpeed; newUserRate = defaultSpeed; speedNotification = false; onRateChange(true); speedNotification = true; } }; // playback speed icon const playbackSpeedIconTemplate = (() => { const div = document.createElement('div'); div.className = 'CentAnni-playback-speed-icon'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('fill', 'none'); svg.setAttribute('height', '24'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('width', '24'); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 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'); path.setAttribute('fill', 'whitesmoke'); svg.appendChild(path); div.appendChild(svg); return div; })(); function createPlaybackSpeedIcon() { return playbackSpeedIconTemplate.cloneNode(true); } // playback speed functions function initialSpeed() { document.removeEventListener('yt-player-updated', initialSpeed); const videoContainer = docElement.querySelector('#page-manager > [role="main"] #player-container'); const video = videoContainer.querySelector('video.html5-main-video'); if (!video) return; if (USER_CONFIG.VerifiedArtist) { const isMusicVideoMeta = !!(docElement.querySelector('meta[itemprop="genre"][content="Music"]')); if (isMusicVideoMeta) { video.playbackRate = 1; return; } } if (!USER_CONFIG.redirectShorts && new URL(window.location.href).pathname.startsWith('/shorts/')) video.playbackRate = lastUserRate !== null ? lastUserRate : defaultSpeed; else if (videoContainer.querySelector('.ytp-time-display.ytp-live')) video.playbackRate = 1; else video.playbackRate = defaultSpeed; video.defaultPlaybackRate = video.playbackRate; } // speed controller async function createPlaybackSpeedController(options = {}) { const MIN_SPEED = 0.25; const MAX_SPEED = 17; const STEP_SIZE = 0.25; if (!mainVideo) findMainVideo(); let video = mainVideo; if (!video || playbackSpeedActive) return null; if (!speedKeysMapped) speedKeyMap(); playbackSpeedActive = true; let speedDisplay = null; function updateSpeedDisplay() { if (!speedDisplay?.isConnected) speedDisplay = document.getElementById("CentAnni-speed-display"); if (speedDisplay && video) speedDisplay.textContent = `${video.playbackRate}x`; } // set playback speed and update display function setSpeed(notif = true) { if (!video?.isConnected || video.readyState === 0) { findMainVideo(); if (!mainVideo) return; if (video !== mainVideo) { video?.removeEventListener('ratechange', onRateChange); video = mainVideo; video.addEventListener('ratechange', onRateChange); } } ignoreRateChange = true; const clamped = Math.max(MIN_SPEED, Math.min(MAX_SPEED, newUserRate)); video.playbackRate = clamped; video.preservesPitch = video.mozPreservesPitch = video.webkitPreservesPitch = true; if (notif) { updateSpeedDisplay(); lastUserRate = video.playbackRate; if (speedNotification) showSpeedNotification(video.playbackRate); if (USER_CONFIG.playbackSpeedBtns) updateActiveSpeedButton(video); } } // initial speed setting function initializeSpeed() { if (isShortPage) { newUserRate = lastUserRate !== null ? lastUserRate : defaultSpeed; } else if (isLive || (USER_CONFIG.VerifiedArtist && isMusicVideo)) newUserRate = 1; else newUserRate = defaultSpeed; video.defaultPlaybackRate = newUserRate; setSpeed(); speedNotification = true; if (USER_CONFIG.playbackSpeedBtns) playbackSpeedButtons(video, setSpeed); } // handle rate change events onRateChange = (collabSpeed = false) => { if (!video?.isConnected || video.readyState === 0) setSpeed(false); if (collabSpeed === true) setSpeed(); if (ignoreRateChange) { ignoreRateChange = false; return; } if (Math.abs(video.playbackRate - lastUserRate) > .2) video.playbackRate = lastUserRate; }; // keyboard control handler function playbackSpeedKeyListener(event) { const key = (event.key || '').toLowerCase(); const target = event.target; const isEditable = target instanceof Element && (target.closest('input, textarea, select') || target.isContentEditable); const shiftRequired = key === '<' || key === '>'; const isValidKey = defaultKeys.has(key); const matched = specialKeys[key]; if (!video || isEditable || (!shiftRequired && (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey)) || (!isValidKey && matched == null)) return; event.preventDefault(); event.stopPropagation(); switch (key) { case toggleKey: newUserRate = (video.playbackRate !== 1 ? 1 : defaultSpeed); setSpeed(); break; case decreaseKey: case '<': newUserRate = video.playbackRate - STEP_SIZE; setSpeed(); break; case increaseKey: case '>': newUserRate = video.playbackRate + STEP_SIZE; setSpeed(); break; default: if (matched != null) { newUserRate = matched; setSpeed(); } } } window.addEventListener('keydown', playbackSpeedKeyListener, true); // clean up on Navigation cleanupPlaybackSpeedController = () => { window.removeEventListener('keydown', playbackSpeedKeyListener, true); video?.removeEventListener('ratechange', onRateChange); liveObserver?.disconnect(); liveObserver = null; speedNotification = false; playbackSpeedActive = false; cleanupPlaybackSpeedController = null; }; // setup event listeners const setupEventListeners = () => { video.addEventListener('ratechange', onRateChange); // restore speed to 1x once caught up if (isLive && !isShortPage) { const liveBadge = watchFlexyElement.querySelector('.html5-video-player .ytp-live-badge'); liveObserver = new MutationObserver(() => { if (liveBadge.classList.contains('ytp-live-badge-is-livehead') && video.playbackRate !== 1) { newUserRate = 1; setSpeed(); } }); liveBadge && liveObserver.observe(liveBadge, { attributes: true, attributeFilter: ['class'] }); } }; // initialize function return controller API initializeSpeed(); requestIdleCallback(() => setupEventListeners()); return { video, setSpeed, STEP_SIZE, }; } // playback speed regular videos async function videoPlaybackSpeed() { let menuRenderer = watchFlexyElement.querySelector(menuSel); if (!menuRenderer) return; // create controller const controller = await createPlaybackSpeedController(); if (!controller) return; const { video, setSpeed, STEP_SIZE } = controller; let controlDiv; const speedID = "CentAnni-playback-speed-control"; const createPlaybackSpeedBtns = () => { const oldControlDiv = document.getElementById(speedID); const BTN_CLASS = (watchFlexyElement.querySelector('#notification-preference-button button') ?? watchFlexyElement.querySelector('#top-row.ytd-watch-metadata button[aria-label="Share"]'))?.className ?? ''; // create container for buttons, display speed, and icon controlDiv = document.createElement("div"); controlDiv.id = speedID; controlDiv.classList.add("CentAnni-playback-control", "top-level-buttons", "style-scope", "ytd-menu-renderer"); // create the SVG icon const iconDiv = createPlaybackSpeedIcon(); controlDiv.appendChild(iconDiv); // display the speed const speedDisplay = document.createElement("span"); speedDisplay.tabIndex = 0; speedDisplay.id = "CentAnni-speed-display"; speedDisplay.className = `${BTN_CLASS} CentAnni-playback-speed-display CentAnni-btn-feedback-shape`; speedDisplay.textContent = `${video.playbackRate}x`; speedDisplay.title = 'Set a custom playback speed for this channel'; speedDisplay.onclick = () => saveChannelPlaybackSpeeds(video); // create minus and plus buttons const createButton = (change) => { const button = document.createElement("button"); button.tabIndex = 0; button.textContent = change > 0 ? "+" : "-"; button.className = `${BTN_CLASS} CentAnni-playback-speed-button CentAnni-btn-feedback-shape`; button.onclick = () => { newUserRate = video.playbackRate + change; setSpeed(); }; return button; }; controlDiv.appendChild(createButton(-STEP_SIZE)); controlDiv.appendChild(speedDisplay); controlDiv.appendChild(createButton(STEP_SIZE)); oldControlDiv ? oldControlDiv.replaceWith(controlDiv) : menuRenderer.children[0].after(controlDiv); }; createPlaybackSpeedBtns(); requestIdleCallback(() => { if (!controlDiv?.isConnected) { const btnContainer = watchFlexyElement.querySelector('ytd-watch-metadata #top-level-buttons-computed'); cleanupSpeedObserver = () => { speedBtnObserver?.disconnect(); clearTimeout(speedBtnObserver.timer); speedBtnObserver = null; cleanupSpeedObserver = null; }; speedBtnObserver = new MutationObserver(() => { const btnCheck = document.getElementById(speedID); if (!btnCheck?.isConnected) createPlaybackSpeedBtns(); }); speedBtnObserver.observe(btnContainer, { childList: true }); speedBtnObserver.timer = setTimeout(cleanupSpeedObserver, 5000); } }); } // playback speed notification function showSpeedNotification(speed) { if (!isWatchPage && !isShortPage) return; if (!speedNotificationElement?.isConnected) { speedNotificationElement = document.createElement('div'); speedNotificationElement.id = 'CentAnni-playback-speed-popup'; docBody.appendChild(speedNotificationElement); } speedNotificationElement.textContent = `${speed}x`; speedNotificationElement.classList.add('active'); if (hideNotificationTimeout) clearTimeout(hideNotificationTimeout); hideNotificationTimeout = setTimeout(() => speedNotificationElement.classList.remove('active'), 700); } // playback speed buttons function playbackSpeedButtons(video, setSpeed) { const containerTheater = watchFlexyElement.querySelector('#columns #secondary'); const containerDefault = document.getElementById('bottom-row'); if (!containerDefault && !containerTheater && !video && !setSpeed) return; let isTheater; const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4]; // create button container const buttonContainer = document.createElement('div'); buttonContainer.id = 'CentAnni-speed-buttons'; // create buttons const BTN_CLASS = (watchFlexyElement.querySelector('#notification-preference-button button') ?? watchFlexyElement.querySelector('#top-row.ytd-watch-metadata button[aria-label="Share"]'))?.className ?? ''; speeds.forEach(speed => { const button = document.createElement('button'); button.textContent = `${speed}x`; button.className = `${BTN_CLASS} CentAnni-speed-preset-button`; button.dataset.speed = speed; button.onclick = () => { newUserRate = speed; setSpeed(); }; buttonContainer.appendChild(button); }); isTheater = watchFlexyElement.hasAttribute('theater'); document.getElementById('CentAnni-speed-buttons')?.remove(); isTheater ? containerTheater.appendChild(buttonContainer) : containerDefault.appendChild(buttonContainer); updateActiveSpeedButton(video); const updatePosition = () => { isTheater = watchFlexyElement.hasAttribute('theater'); const currentContainer = buttonContainer.parentElement; const targetContainer = isTheater ? containerTheater : containerDefault; if (currentContainer !== targetContainer) { isTheater ? targetContainer.appendChild(buttonContainer) : targetContainer.appendChild(buttonContainer); updateActiveSpeedButton(video); } }; cleanupPlaybackSpeedBtns = () => { document.removeEventListener('yt-set-theater-mode-enabled', updatePosition); cleanupPlaybackSpeedBtns = null; }; document.addEventListener('yt-set-theater-mode-enabled', updatePosition); } function updateActiveSpeedButton(video) { if (!video) return; const buttons = watchFlexyElement.querySelectorAll('.CentAnni-speed-preset-button'); const currentSpeed = video.playbackRate; buttons.forEach(button => { const buttonSpeed = parseFloat(button.dataset.speed); if (Math.abs(currentSpeed - buttonSpeed) < 0.01) { button.classList.add('active'); button.scrollIntoView({ behavior: 'smooth', inline: 'nearest', block: 'nearest' }); } else button.classList.remove('active'); }); } // function to display the remaining time based on playback speed minus SponsorBlock segments async function remainingTime() { let normalContainer = watchFlexyElement.querySelector(prBeSel); let fullscreenContainer = watchFlexyElement.querySelector(fsCnSel); if (!normalContainer || !fullscreenContainer || remainingTimeActive) return; if (!mainVideo) findMainVideo(); remainingTimeActive = true; let video = mainVideo; let animationFrameId; // function to format seconds into time string function formatTime(seconds) { if (!Number.isFinite(seconds) || seconds < 0) seconds = 0; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); return h > 0 ? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` : `${m}:${s.toString().padStart(2, '0')}`; } const element = document.createElement('div'); element.id = 'CentAnni-remaining-time-container'; const textNode = document.createTextNode(''); element.appendChild(textNode); normalContainer.prepend(element); let fullscreen; const updateRemainingTimeContainer = () => { const currentContainer = document.getElementById('CentAnni-remaining-time-container'); const targetContainer = playerElement.classList.contains('ytp-fullscreen') ? fullscreenContainer : normalContainer; fullscreen = targetContainer === fullscreenContainer; if (currentContainer && currentContainer.parentNode !== targetContainer) { if (targetContainer === fullscreenContainer) targetContainer.appendChild(currentContainer); else { targetContainer.prepend(currentContainer); if (getComputedStyle(normalContainer).position === 'static') normalContainer.style.position = 'relative'; } } }; // position container and await playback updateRemainingTimeContainer(); const handleFullscreenChange = () => requestAnimationFrame(() => requestAnimationFrame(() => updateRemainingTimeContainer())); document.addEventListener('fullscreenchange', handleFullscreenChange); if (!video.paused && !video.ended && video.readyState > 2) remainingTimeMinusSponsorBlockSegments(); else video.addEventListener('playing', remainingTimeMinusSponsorBlockSegments, { once: true }); // calculates and displays remaining time while accounting for SponsorBlock segments function remainingTimeMinusSponsorBlockSegments() { let cachedSegments = []; let baseEffective = NaN; let lastRawDuration = -1; // retrieves and validates video duration function ensureBaseEffectiveIsValid() { if (!isNaN(baseEffective) || !video.duration || isNaN(video.duration) || video.duration <= 0) return; const sponsorBlockTimeElement = document.getElementById('sponsorBlockDurationAfterSkips'); if (sponsorBlockTimeElement) { if (sponsorBlockTimeElement.textContent.trim()) { const rawText = sponsorBlockTimeElement.textContent.trim().replace(/[()]/g, ''); baseEffective = parseTime(rawText); } else { baseEffective = video.duration; sponsorBlockTimeObserver = new MutationObserver(() => { sponsorBlockTimeObserver?.disconnect(); cachedTotalDisplay = null; baseEffective = NaN; if (USER_CONFIG.progressBar) updateSegmentsOpacity?.(); }); sponsorBlockTimeObserver.observe(sponsorBlockTimeElement, { childList: true }); } } else baseEffective = video.duration; } // retrieves and merges SponsorBlock segments function getMergedSegments(rawDuration) { if (rawDuration === lastRawDuration && cachedSegments.length) return cachedSegments; const segments = []; cachedSegmentTimes.length = 0; const previewbar = document.getElementById('previewbar'); if (previewbar) { const liElements = previewbar.querySelectorAll('li.previewbar'); liElements.forEach(li => { const style = li.getAttribute('style'); const leftMatch = style.match(/left:\s*([\d.]+)%/); const rightMatch = style.match(/right:\s*([\d.]+)%/); if (leftMatch && rightMatch) { const leftFraction = parseFloat(leftMatch[1]) / 100; const rightFraction = parseFloat(rightMatch[1]) / 100; const startTime = Math.round(rawDuration * leftFraction * 1000) / 1000; const endTime = Math.round(rawDuration * (1 - rightFraction) * 1000) / 1000; if (endTime > startTime) { segments.push({ start: startTime, end: endTime }); cachedSegmentTimes.push({ end: endTime, el: li }); } } }); } segments.sort((a, b) => a.start - b.start); const merged = []; if (segments.length > 0) { let current = segments[0]; for (let i = 1; i < segments.length; i++) { const next = segments[i]; if (next.start < current.end + 0.1) current.end = Math.max(current.end, next.end); else { merged.push(current); current = next; } } merged.push(current); } lastRawDuration = rawDuration; cachedSegments = merged; return merged; } // build display format const displayFormat = (() => { if (USER_CONFIG.showRemainingCompact) return (elapsed, total, watched, remaining, playbackDisplay) => `${elapsed} / ${total} | -${remaining} ${playbackDisplay}`; else return (elapsed, total, watched, remaining, playbackDisplay) => `total: ${total} | elapsed: ${elapsed}${!USER_CONFIG.progressBar ? ` – watched: ${watched}` : ''} – remaining: ${remaining} ${playbackDisplay}`; })(); // update UI depending on playback speed and once per second let lastTick = -1; let lastrealTick = -1; let cachedTotalDisplay = null; let cachedWatchedPercent = '0%'; let cachedRemainingFormatted = ''; let cachedPlaybackDisplay = ''; let cachedElapsedFormatted = ''; video.ontimeupdate = () => { if (animationFrameId) return; const updateLoop = (timestamp, meta) => { ensureBaseEffectiveIsValid(); const rawDuration = video.duration; if (!isNaN(baseEffective) && rawDuration && !isNaN(rawDuration)) { const tick = Math.floor(meta.mediaTime); const realTick = Math.floor(timestamp / 1000); if (tick !== lastTick || realTick !== lastrealTick) { let shouldRender = false; if (cachedTotalDisplay === null) { const totalFormatted = formatTime(baseEffective); const rawTotalFormatted = formatTime(rawDuration); cachedTotalDisplay = Math.round(rawDuration) !== Math.round(baseEffective) ? `${rawTotalFormatted} (${totalFormatted})` : rawTotalFormatted; } if (realTick !== lastrealTick) { lastrealTick = realTick; const currentTime = meta.mediaTime; const playbackRate = video.playbackRate; const segments = getMergedSegments(rawDuration); let futureSkippableTime = 0; for (const seg of segments) { if (seg.end <= currentTime) continue; if (seg.start >= currentTime) futureSkippableTime += (seg.end - seg.start); else if (seg.end > currentTime) futureSkippableTime += (seg.end - currentTime); } const remaining = Math.max(0, (rawDuration - currentTime) - futureSkippableTime) / playbackRate; cachedRemainingFormatted = formatTime(remaining); cachedWatchedPercent = rawDuration ? Math.round((currentTime / rawDuration) * 100) + '%' : '0%'; cachedPlaybackDisplay = showPlaybackSpeed ? (fullscreen ? ` (${playbackRate}x)` : '') : ` (${playbackRate}x)`; if (video.playbackRate !== 1) shouldRender = true; } if (tick !== lastTick) { lastTick = tick; cachedElapsedFormatted = formatTime(meta.mediaTime); shouldRender = true; } if (shouldRender) textNode.data = displayFormat(cachedElapsedFormatted, cachedTotalDisplay, cachedWatchedPercent, cachedRemainingFormatted, cachedPlaybackDisplay); } } animationFrameId = (!video.paused && !video.ended) ? video.requestVideoFrameCallback(updateLoop) : null; }; animationFrameId = video.requestVideoFrameCallback(updateLoop); }; }; // handle cleanup cleanupRemainingTime = () => { video?.removeEventListener('playing', remainingTimeMinusSponsorBlockSegments); video?.cancelVideoFrameCallback(animationFrameId); animationFrameId = null; document.removeEventListener('fullscreenchange', handleFullscreenChange); sponsorBlockTimeObserver?.disconnect(); cachedSegmentTimes.length = 0; element.remove(); video.ontimeupdate = null; remainingTimeActive = false; cleanupRemainingTime = null; }; } // helper function to convert a time string into seconds function parseTime(timeString) { const parts = timeString.split(':').map(Number); if (parts.length === 2) return parts[0] * 60 + parts[1]; else if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; return 0; } // function to keep the progress bar visible with chapters container function keepProgressBarVisible() { const player = watchFlexyElement.querySelector(vidPSel); const chaptersContainer = player?.querySelector(chapSel); const progressBarContainer = player?.querySelector(prBaSel); if (!player || !chaptersContainer || !progressBarContainer || progressBarActive) return; if (!mainVideo) findMainVideo(); progressBarActive = true; let video = mainVideo; let animationFrameId; // update SponsorBlock segments opacity updateSegmentsOpacity = () => { if (!cachedSegmentTimes.length) return; if (!cachedSegmentTimes[0].el.isConnected) { const previewbar = document.getElementById('previewbar'); const lis = previewbar?.querySelectorAll('li.previewbar'); cachedSegmentTimes.forEach((seg, i) => { if (lis?.[i]) seg.el = lis[i]; }); } const currentTime = video.currentTime; for (const seg of cachedSegmentTimes) seg.el.classList.toggle('segment-watched', seg.end <= currentTime + .5); }; const bar = document.createElement('div'); bar.id = 'CentAnni-progress-bar-bar'; const progress = document.createElement('div'); progress.id = 'CentAnni-progress-bar-progress'; const buffer = document.createElement('div'); buffer.id = 'CentAnni-progress-bar-buffer'; const startDiv = document.createElement('div'); startDiv.id = 'CentAnni-progress-bar-start'; const endDiv = document.createElement('div'); endDiv.id = 'CentAnni-progress-bar-end'; player.appendChild(bar); bar.appendChild(buffer); bar.appendChild(progress); player.appendChild(startDiv); player.appendChild(endDiv); progress.style.transform = 'scaleX(0)'; startDiv.classList.add('active'); bar.classList.add('active'); endDiv.classList.add('active'); // convert time fraction to visual position let currentBarWidth = 0; let totalActiveWidth = 0; let chapterSegments = []; let chapterActiveStarts = []; let currentSegmentIndex = -1; let checkingChapters = false; const getVisualProgress = (fraction) => { // if no chapters, use linear time and check if we should have segments if (chapterSegments.length === 0 || totalActiveWidth === 0 || currentBarWidth === 0) { if (hasChapterPanel && currentBarWidth > 0 && !checkingChapters) { checkingChapters = setTimeout(() => { updateLayout(); checkingChapters = false; }, 1000); } return fraction; } const targetActivePx = fraction * totalActiveWidth; if (targetActivePx <= 0) { currentSegmentIndex = 0; return 0; } if (targetActivePx >= totalActiveWidth) { currentSegmentIndex = chapterSegments.length - 1; return 1; } // fast path for normal playback: current segment, then next segment if (currentSegmentIndex >= 0 && currentSegmentIndex < chapterSegments.length) { const currentSeg = chapterSegments[currentSegmentIndex]; const currentStart = chapterActiveStarts[currentSegmentIndex]; const currentEnd = currentStart + currentSeg.width; if (targetActivePx >= currentStart && targetActivePx < currentEnd) return (currentSeg.left + (targetActivePx - currentStart)) / currentBarWidth; const nextIndex = currentSegmentIndex + 1; if (nextIndex < chapterSegments.length) { const nextSeg = chapterSegments[nextIndex]; const nextStart = chapterActiveStarts[nextIndex]; const nextEnd = nextStart + nextSeg.width; if (targetActivePx >= nextStart && targetActivePx < nextEnd) { currentSegmentIndex = nextIndex; return (nextSeg.left + (targetActivePx - nextStart)) / currentBarWidth; } } } // find the chapter segment in O(log n) using precomputed active starts let left = 0; let right = chapterSegments.length - 1; while (left <= right) { const mid = (left + right) >> 1; const seg = chapterSegments[mid]; const segStart = chapterActiveStarts[mid]; const segEnd = segStart + seg.width; if (targetActivePx < segStart) right = mid - 1; else if (targetActivePx >= segEnd) left = mid + 1; else { currentSegmentIndex = mid; return (seg.left + (targetActivePx - segStart)) / currentBarWidth; } } return 1; }; const animateProgress = (_timestamp, meta) => { if (video.duration > 0) { const timeFraction = meta.mediaTime / video.duration; const visualFraction = getVisualProgress(timeFraction); progress.style.transform = `scaleX(${visualFraction})`; } animationFrameId = video.requestVideoFrameCallback(animateProgress); }; let isLoaded = false; const renderBuffer = (e) => { if (!video.duration) return; for (let i = video.buffered.length - 1; i >= 0; i--) { if (video.currentTime < video.buffered.start(i)) continue; const timeFraction = video.buffered.end(i) / video.duration; const visualFraction = getVisualProgress(timeFraction); buffer.style.transform = `scaleX(${visualFraction})`; const hasLoaded = visualFraction >= 0.9999; if (hasLoaded !== isLoaded) { endDiv.classList.toggle('loaded', hasLoaded); isLoaded = hasLoaded; } break; } if (e.type === 'seeked' && USER_CONFIG.displayRemainingTime) updateSegmentsOpacity?.(); }; const handleEnded = () => { if (animationFrameId != null) { video.cancelVideoFrameCallback(animationFrameId); animationFrameId = null; } progress.style.transform = 'scaleX(1)'; }; video.addEventListener('progress', renderBuffer); video.addEventListener('seeked', renderBuffer); video.addEventListener('ended', handleEnded); // chapters container let progressBarWidthMissing = true; let previousProgressBarWidth = 0; let previousChaptersLength = 0; let cachedMaskImage = null; let resizeTimer; const updateLayout = () => { const progressBarRect = progressBarContainer.getBoundingClientRect(); const progressBarWidth = progressBarRect.width; currentBarWidth = progressBarWidth; if (!progressBarWidth) { cachedMaskImage = null; return; } progressBarWidthMissing = false; // calculate position relative to the player container const playerRect = player.getBoundingClientRect(); bar.style.position = 'absolute'; bar.style.left = (progressBarRect.left - playerRect.left) + 'px'; bar.style.width = progressBarWidth + 'px'; docElement.style.setProperty('--progressBarMargin', bar.style.left); // reset math arrays totalActiveWidth = 0; chapterSegments = []; chapterActiveStarts = []; currentSegmentIndex = -1; if (chaptersContainer) { const chapters = chaptersContainer.querySelectorAll('.ytp-chapter-hover-container'); // find container with the real segments let sourceCC = chaptersContainer; let maxSegs = chapters.length; player.querySelectorAll('.ytp-chapters-container').forEach(cc => { const segs = cc.querySelectorAll('.ytp-chapter-hover-container').length; if (segs > maxSegs) { sourceCC = cc; maxSegs = segs; } }); const segs = sourceCC.querySelectorAll('.ytp-chapter-hover-container'); // calculate segments if (segs.length >= 2 && progressBarWidth > 0) { segs.forEach(el => { const segRect = el.getBoundingClientRect(); const relX = segRect.left - progressBarRect.left; const relW = segRect.width; if (relW <= 0) return; chapterActiveStarts.push(totalActiveWidth); chapterSegments.push({ left: relX, width: relW }); totalActiveWidth += relW; }); } // only regenerate SVG if width or chapters changed if (chapters.length !== previousChaptersLength || Math.abs(progressBarWidth - previousProgressBarWidth) > 1 || !cachedMaskImage) { previousChaptersLength = chapters.length; previousProgressBarWidth = progressBarWidth; const svgWidth = 100, svgHeight = 10; let rects = ""; if (chapterSegments.length >= 2 && progressBarWidth > 0) { chapterSegments.forEach((seg, i) => { const x = (seg.left / progressBarWidth) * svgWidth; const isLastChapter = i === chapterSegments.length - 1; const w = isLastChapter ? svgWidth - x : (seg.width / progressBarWidth) * svgWidth; if (w > 0) rects += ``; }); } // safety check - no segments > using full-width mask instead if (!rects) rects = ``; const svg = `${rects}`; const encoded = encodeURIComponent(svg).replace(/%20/g, ' '); cachedMaskImage = `url("data:image/svg+xml;utf8,${encoded}")`; } // apply mask based on current state if (cachedMaskImage) { bar.style.maskImage = cachedMaskImage; bar.style.webkitMaskImage = cachedMaskImage; bar.style.maskRepeat = 'no-repeat'; bar.style.webkitMaskRepeat = 'no-repeat'; bar.style.maskSize = '100% 100%'; bar.style.webkitMaskSize = '100% 100%'; } else { bar.style.maskImage = ''; bar.style.webkitMaskImage = ''; bar.style.maskRepeat = ''; bar.style.webkitMaskRepeat = ''; bar.style.maskSize = ''; bar.style.webkitMaskSize = ''; } } if (USER_CONFIG.displayRemainingTime) updateSegmentsOpacity?.(); }; // handle animation frame const handlePlay = () => { if (progressBarWidthMissing) updateLayout(); if (!animationFrameId) animationFrameId = video.requestVideoFrameCallback(animateProgress); }; const handlePause = () => { if (animationFrameId) { video.cancelVideoFrameCallback(animationFrameId); animationFrameId = null; } }; video.addEventListener('playing', handlePlay); video.addEventListener('pause', handlePause); if (!video.paused) handlePlay(); // handle cleanup cleanupProgressBar = () => { document.removeEventListener('yt-set-theater-mode-enabled', onTheaterResize); window.removeEventListener('resize', onResize); video?.removeEventListener('progress', renderBuffer); video?.removeEventListener('seeked', renderBuffer); video?.removeEventListener('ended', handleEnded); video?.removeEventListener('pause', handlePause); video?.removeEventListener('playing', handlePlay); video?.cancelVideoFrameCallback(animationFrameId); animationFrameId = null; updateSegmentsOpacity = null; clearTimeout(resizeTimer); checkingChapters = null; startDiv.remove(); endDiv.remove(); bar.remove(); progressBarActive = false; cleanupProgressBar = null; }; // debounce resize event const onTheaterResize = () => requestAnimationFrame(updateLayout); const onResize = () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => requestAnimationFrame(updateLayout), 200); }; // initialization requestIdleCallback(() => { updateLayout(); // handle layout changes window.addEventListener('resize', onResize); document.addEventListener('yt-set-theater-mode-enabled', onTheaterResize); }); } // close live chat initially function closeLiveChat() { const chatFrame = watchFlexyElement.querySelector('ytd-live-chat-frame'); if (!chatFrame) { docElement.classList.remove('CentAnni-close-live-chat'); return; } const retryInterval = 250; const maxRetries = 12; let iframeAttempts = 0; let buttonAttempts = 0; function tryCloseChat() { const iframe = document.getElementById('chatframe'); if (!iframe?.contentWindow?.document) { if (iframeAttempts < maxRetries) { iframeAttempts++; setTimeout(tryCloseChat, retryInterval); } else { docElement.classList.remove('CentAnni-close-live-chat'); initialCloseLiveChat = false; } return; } const button = iframe.contentWindow.document.querySelector('#close-button button'); if (!button) { if (buttonAttempts < maxRetries) { buttonAttempts++; setTimeout(tryCloseChat, retryInterval); } else { docElement.classList.remove('CentAnni-close-live-chat'); initialCloseLiveChat = false; } return; } button.click(); const chatElement = watchFlexyElement.querySelector('ytd-live-chat-frame#chat'); const observer = new MutationObserver((mutations) => { if (chatElement.hasAttribute('collapsed')) { removeChatCSS(); clearTimeout(fallbackTimer); observer.disconnect(); } }); observer.observe(chatElement, { attributes: true, attributeFilter: ['collapsed'] }); const fallbackTimer = setTimeout(() => { if (initialCloseLiveChat) { removeChatCSS(); observer.disconnect(); } }, 3000); function removeChatCSS() { initialCloseLiveChat = false; setTimeout(() => docElement.classList.remove('CentAnni-close-live-chat'), 250); } } tryCloseChat(); } // set video quality function setVideoQuality(desiredQuality, defaultQualityPremium) { let qualitySet = null; let qualityUHD = null; let found = false; // define quality levels const qualityLevels = [ 'highres', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny' ]; // get the YouTube player const player = document.getElementById("movie_player"); if (!player?.getAvailableQualityLevels) return; // get available qualities for the video and check for UHD const availableQualities = player.getAvailableQualityLevels(); if (availableQualities.includes('hd1440') || availableQualities.includes('hd2160') || availableQualities.includes('highres')) qualityUHD = true; // helper function to reduce repetition and store picked quality const setQuality = (quality) => { player.setPlaybackQualityRange(quality, quality); qualitySet = quality; }; // find closest available quality if exact match isn't available if (availableQualities.includes(desiredQuality)) { setQuality(desiredQuality); } else if (desiredQuality === "highest") { setQuality(availableQualities[0]); } else if (desiredQuality === "lowest") { const lowest = availableQualities[availableQualities.length - 1] === "auto" ? availableQualities[availableQualities.length - 2] : availableQualities[availableQualities.length - 1]; setQuality(lowest); } else { const desiredIndex = qualityLevels.indexOf(desiredQuality); if (desiredIndex !== -1) { for (let i = desiredIndex; i < qualityLevels.length; i++) { if (availableQualities.includes(qualityLevels[i])) { setQuality(qualityLevels[i]); found = true; break; } } if (!found) setQuality('auto'); } else setQuality('auto'); } // set 1080p premium quality if (defaultQualityPremium && qualitySet === 'hd1080' && !qualityUHD) setVideoQualityPremium(); async function setVideoQualityPremium() { document.documentElement.classList.add('CentAnni-style-hide-yt-settings'); await setQualityPremium(); document.body.click(); document.documentElement.classList.remove('CentAnni-style-hide-yt-settings'); } async function setQualityPremium() { const qualityTranslations = { 'cs': 'Kvalita', 'da': 'Kvalitet', 'de': 'Qualität', 'el': 'Ποιότητα', 'en': 'Quality', 'es': 'Calidad', 'fi': 'Laatu', 'fr': 'Qualité', 'he': 'איכות', 'hi': 'गुणवत्ता', 'hu': 'Minőség', 'id': 'Kualitas', 'it': 'Qualità', 'ja': '品質', 'ko': '품질', 'nl': 'Kwaliteit', 'no': 'Kvalitet', 'pl': 'Jakość', 'pt': 'Qualidade', 'ro': 'Calitate', 'sv': 'Kvalitet', 'uk': 'Якість', 'vi': 'Chất lượng', 'zh': '画质' }; const settingsPanel = 'ytd-watch-flexy .ytp-settings-menu'; const settingsButton = 'ytd-watch-flexy .ytp-settings-button'; const LanguageUI = document.documentElement.lang || navigator.language || 'en'; const lang = new Intl.Locale(LanguageUI).language; const qualityText = qualityTranslations[lang]; function waitFor(selectorOrFn, ctx = document, timeout = 7000) { return new Promise((resolve, reject) => { const lookup = () => (typeof selectorOrFn === 'function') ? selectorOrFn() : ctx.querySelector(selectorOrFn); if (lookup()) { resolve(lookup()); return; } const timer = setTimeout(() => { observer.disconnect(); reject(); }, timeout); const observer = new MutationObserver(() => { const node = lookup(); if (node) { clearTimeout(timer); observer.disconnect(); resolve(node); } }); observer.observe(ctx, { childList: true, subtree: true }); }); } if (!document.querySelector(settingsPanel) || getComputedStyle(document.querySelector(settingsPanel)).display === 'none') { document.querySelector(settingsButton).click(); await waitFor(settingsPanel); } const panel = document.querySelector(settingsPanel); const qualitySelector = () => [...panel.querySelectorAll('.ytp-menuitem')].find(item => item.textContent.includes(qualityText)); const qualityItem = qualitySelector(); if (!qualityItem) return; qualityItem.click(); await waitFor('.ytp-menuitem', panel); const premiumQualitySelector = () => [...panel.querySelectorAll('.ytp-menuitem')] .find(item => { const labelEl = item.querySelector('.ytp-menuitem-label'); return labelEl && labelEl.textContent.includes('1080p Premium'); }); const premiumOption = premiumQualitySelector(); if (!premiumOption) return; premiumOption.click(); } } // check if YT Guide is open/close const guideCheck = (e) => { const guideToggle = e.type === 'yt-guide-toggle'; guideClosed = document.getElementById('guide')?.hasAttribute('opened'); if (!guideToggle) guideClosed = !guideClosed; for (const [flag, className] of guideClassEntries) docElement.classList.toggle(className, !guideClosed); if (!guideOpen) { if (guideClosed) document.removeEventListener('yt-autonav-pause-guide-closed', guideCheck); else if (guideToggle) document.addEventListener('yt-autonav-pause-guide-closed', guideCheck, { once: true }); } }; // open/close YT Guide const openGuide = () => { if (!guideButton?.isConnected) guideButton = mastheadElement.querySelector('#guide-button'); guideButton.click(); }; // sidebar and header links function buttonsLeftHeader() { if (isHideSidebarChecked && guideOpen) openGuide(); if (!mastheadElement || !startElement || startElement.querySelector('.buttons-left')) return; // create sidebar button function createButton(text, onClick) { const btn = document.createElement('button'); btn.textContent = text; btn.classList.add('buttons-left'); btn.onclick = (e) => { e.preventDefault(); onClick(); }; return btn; } // create links function createLink(text, url) { const link = document.createElement('a'); link.textContent = text; link.classList.add('buttons-left'); link.href = url; return link; } // adding the buttons const buttonsConfig = [ { type: 'button', text: USER_CONFIG.mButtonText, onClick: openGuide }, { type: 'link', text: USER_CONFIG.buttonLeft1Text, url: USER_CONFIG.buttonLeft1Url }, { type: 'link', text: USER_CONFIG.buttonLeft2Text, url: USER_CONFIG.buttonLeft2Url }, { type: 'link', text: USER_CONFIG.buttonLeft3Text, url: USER_CONFIG.buttonLeft3Url }, { type: 'link', text: USER_CONFIG.buttonLeft4Text, url: USER_CONFIG.buttonLeft4Url }, { type: 'link', text: USER_CONFIG.buttonLeft5Text, url: USER_CONFIG.buttonLeft5Url }, { type: 'link', text: USER_CONFIG.buttonLeft6Text, url: USER_CONFIG.buttonLeft6Url }, { type: 'link', text: USER_CONFIG.buttonLeft7Text, url: USER_CONFIG.buttonLeft7Url }, { type: 'link', text: USER_CONFIG.buttonLeft8Text, url: USER_CONFIG.buttonLeft8Url }, { type: 'link', text: USER_CONFIG.buttonLeft9Text, url: USER_CONFIG.buttonLeft9Url }, { type: 'link', text: USER_CONFIG.buttonLeft10Text, url: USER_CONFIG.buttonLeft10Url } ]; buttonsConfig.forEach(config => { if (config.text && config.text.trim() !== '') { let element; if (config.type === 'button') { if (isHideSidebarChecked) { element = createButton(config.text, config.onClick); if (config.text === DEFAULT_CONFIG.mButtonText) { element.style.display = 'inline-block'; element.style.fontSize = '25px'; element.style.padding = '0 0 5px 0'; element.style.transform = 'scaleX(1.25)'; } } } else if (config.type === 'link') element = createLink(config.text, config.url); if (element) startElement.appendChild(element); } }); const saveStartHeaderWidth = async () => { const btnsLeftHeaderWidth = startElement.getBoundingClientRect().width; if (btnsLeftHeaderWidth !== smw) await GM.setValue('btnsLeftHeaderWidth', btnsLeftHeaderWidth); }; if (initialRun) requestIdleCallback(() => saveStartHeaderWidth()); } // color code videos on home function homeColorCodeVideos() { let homePageObserver = null; const homePage = document.querySelector('ytd-browse[page-subtype="home"][role="main"]:not([hidden])'); if (!homePage) return; // define age categories and regex if (!homeCategoryData) createHomeCategoryData(); const { categories, streamedRegex } = homeCategoryData; const categoryEntries = Object.entries(categories); const { upcoming, streamed } = categories; const targetContainer = homePage?.querySelector('ytd-rich-grid-renderer > #contents') || homePage; const streamedMetaSelectors = '.ytContentMetadataViewModelMetadataRow:not(:has(a, yt-badge-view-model)) span.ytContentMetadataViewModelMetadataText:last-of-type:not(:has(*)), .yt-content-metadata-view-model__metadata-row:not(:has(a, yt-badge-view-model)) .yt-content-metadata-view-model__metadata-text:last-of-type:not(:has(*))'; const liveSelectors = 'badge-shape.yt-badge-shape--thumbnail-live'; const pendingVideoRetries = new WeakMap(); // retry processVideo if metadata isn't rendered yet const scheduleVideoRetry = (videoContainer) => { const retryCount = pendingVideoRetries.get(videoContainer) || 0; if (retryCount >= 12) return; pendingVideoRetries.set(videoContainer, retryCount + 1); setTimeout(() => { if (!videoContainer.isConnected || videoContainer.hasAttribute('data-centanni-video-processed')) return; processVideo(videoContainer); }, 150); }; // process each individual video function processVideo(videoContainer) { try { if (videoContainer.hasAttribute('data-centanni-video-processed')) return; const metaBlock = videoContainer.querySelector(streamedMetaSelectors); if (!metaBlock) { scheduleVideoRetry(videoContainer); return; } pendingVideoRetries.delete(videoContainer); videoContainer.setAttribute('data-centanni-video-processed', 'true'); const textContent = metaBlock.textContent.trim().toLowerCase(); for (const [className, ages] of categoryEntries) { if (ages.some(age => textContent.includes(age))) { videoContainer.classList.add(`CentAnni-style-${className}-video`); break; } } const liveBadge = videoContainer.querySelector(liveSelectors); if (liveBadge && !videoContainer.classList.contains('CentAnni-style-live-video')) videoContainer.classList.add('CentAnni-style-live-video'); const spanElements = videoContainer.querySelectorAll(streamedMetaSelectors); spanElements.forEach(el => { const text = el.textContent; const textLower = text.toLowerCase(); if (upcoming.some(word => textLower.includes(word)) && !videoContainer.classList.contains('CentAnni-style-upcoming-video')) videoContainer.classList.add('CentAnni-style-upcoming-video'); if (streamed.some(word => textLower.includes(word))) { const nextEl = el.nextElementSibling; if (!nextEl || !nextEl.classList.contains('CentAnni-style-streamed-span')) { const cloneSpan = document.createElement('span'); cloneSpan.className = el.className + ' CentAnni-style-streamed-span'; const streamedWordSpan = document.createElement('span'); streamedWordSpan.className = 'CentAnni-style-streamed-text'; const matched = streamed.find(word => textLower.includes(word)) || streamed[0]; streamedWordSpan.textContent = matched.charAt(0).toUpperCase() + matched.slice(1) + ' '; const restText = document.createTextNode(text.replace(streamedRegex, '').trimStart()); cloneSpan.appendChild(streamedWordSpan); cloneSpan.appendChild(restText); el.replaceWith(cloneSpan); } } }); } catch { videoContainer.removeAttribute('data-centanni-video-processed'); pendingVideoRetries.delete(videoContainer); } } // gather all unprocessed videos for processVideo function processVideos() { const unprocessedVideos = [...targetContainer.querySelectorAll('ytd-rich-item-renderer:not([data-centanni-video-processed], [is-shelf-item])')]; unprocessedVideos.forEach(processVideo); } // manage the initial processing and observer function runProcessVideos(callback) { if (homePageObserver) homePageObserver.disconnect(); processVideos(); homePageObserver = new MutationObserver(mutations => { const videosToProcess = new Set(); for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== 1) continue; if (node.matches && node.matches('ytd-rich-item-renderer[rendered-from-rich-grid]')) videosToProcess.add(node); if (node.querySelectorAll) { const nestedVideos = node.querySelectorAll('ytd-rich-item-renderer[rendered-from-rich-grid]'); nestedVideos.forEach(video => videosToProcess.add(video)); } } } videosToProcess.forEach(processVideo); }); const config = { childList: true }; if (targetContainer === homePage) config.subtree = true; homePageObserver.observe(targetContainer, config); if (callback) callback(); } // handle navigation const serviceRequestSentHandler = () => { runProcessVideos(); }; const navigateFinishHandler = () => { setTimeout(cleanupAndReprocessVideos, 300); }; const pageTypeChangedHandler = function () { document.removeEventListener('yt-service-request-sent', serviceRequestSentHandler); document.removeEventListener('yt-service-request-completed', checkProcessedVideos); document.removeEventListener('yt-navigate-finish', navigateFinishHandler); document.removeEventListener('yt-page-type-changed', pageTypeChangedHandler); if (homePageObserver) { homePageObserver.disconnect(); homePageObserver = null; } }; runProcessVideos(function () { document.addEventListener('yt-service-request-sent', serviceRequestSentHandler); document.addEventListener('yt-service-request-completed', checkProcessedVideos); document.addEventListener('yt-navigate-finish', navigateFinishHandler); setTimeout(checkProcessedVideos, 1250); }); document.addEventListener('yt-page-type-changed', pageTypeChangedHandler); // ensure correct categories function checkProcessedVideos() { const processedVideos = homePage.querySelectorAll('ytd-rich-item-renderer:nth-of-type(-n+6)[rendered-from-rich-grid]'); if (processedVideos.length === 0) return; let allCorrect = true; for (const video of processedVideos) { const metaBlock = video.querySelector(streamedMetaSelectors); if (!metaBlock) continue; const textContent = metaBlock.textContent.trim().toLowerCase(); let expectedCategory = null; if (video.querySelector(liveSelectors)) expectedCategory = 'live'; else { for (const [className, ages] of categoryEntries) { if (ages.some(age => textContent.includes(age))) { expectedCategory = className; break; } } if (expectedCategory === null && [...video.querySelectorAll(streamedMetaSelectors)].some(el => /Scheduled for/i.test(el.textContent))) expectedCategory = 'upcoming'; } const expectedClassName = expectedCategory ? `CentAnni-style-${expectedCategory}-video` : null; let currentVideoIsCorrect = true; if (expectedClassName) { if (!video.classList.contains(expectedClassName)) currentVideoIsCorrect = false; } else { for (const cls of video.classList) { if (cls.startsWith('CentAnni-style-')) { currentVideoIsCorrect = false; break; } } } if (!currentVideoIsCorrect) { allCorrect = false; break; } } if (!allCorrect) cleanupAndReprocessVideos(); } // handle cleanup function cleanupAndReprocessVideos() { const videosToReprocess = homePage.querySelectorAll('ytd-rich-item-renderer[data-centanni-video-processed][rendered-from-rich-grid]'); videosToReprocess.forEach(video => { pendingVideoRetries.delete(video); video.classList.remove( 'CentAnni-style-live-video', 'CentAnni-style-upcoming-video', 'CentAnni-style-newly-video', 'CentAnni-style-recent-video', 'CentAnni-style-lately-video', 'CentAnni-style-latterly-video', 'CentAnni-style-old-video', 'CentAnni-style-streamed-video' ); video.querySelectorAll('.CentAnni-style-streamed-span').forEach(el => el.remove()); video.removeAttribute('data-centanni-video-processed'); }); processVideos(); handleFeedFilterAll(); createButtons('settings'); } // handle feed filter 'All' button let allButtonObserver = null; function handleFeedFilterAll() { if (allButtonObserver) return; const allButton = homePage.querySelector('yt-chip-cloud-chip-renderer:first-child'); if (!allButton) return; allButtonObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'class' && allButton.classList.contains('iron-selected')) { handleFeedFilterAllCleanup(); setTimeout(checkProcessedVideos, 800); break; } } }); allButtonObserver.observe(allButton, { attributes: true, attributeFilter: ['class', 'selected'] }); } function handleFeedFilterAllCleanup() { if (allButtonObserver) { allButtonObserver.disconnect(); allButtonObserver = null; } } } // mark last seen video on subscription page async function markLastSeenVideo() { const subscriptionPage = document.querySelector('ytd-browse[page-subtype="subscriptions"][role="main"]:not([hidden])'); const videoElement = subscriptionPage?.querySelector('ytd-rich-item-renderer[rendered-from-rich-grid] .lockup.ytd-rich-item-renderer'); // wait for content to load await new Promise(resolve => { const check = () => { const videoBadge = videoElement.querySelector('.ytThumbnailBadgeViewModelHost'); if (videoBadge?.childElementCount > 0) { videoElementObserver.disconnect(); resolve(videoBadge); } }; videoElementObserver = new MutationObserver(check); videoElementObserver.observe(videoElement, { childList: true, subtree: true }); check(); }); const videoContainers = subscriptionPage?.querySelectorAll('ytd-rich-item-renderer[rendered-from-rich-grid]'); const previousLastSeen = subscriptionPage?.querySelector('.CentAnni-style-last-seen'); if (!subscriptionPage || !videoContainers.length) return; // helper function to check if a video is live or upcoming const isSpecialVideo = (container) => { if (container.querySelector('.ytContentMetadataViewModelDelimiter,.yt-content-metadata-view-model__delimiter')) return false; if (container.querySelector('.ytBadgeShapeThumbnailLive, .ytLockupAttachmentsViewModelHost,.yt-badge-shape--thumbnail-live')) return true; return false; }; // helper function to extract video ID const extractVideoID = (container) => { const videoLockup = container.querySelector('[class*="content-id-"]'); const contentIDClass = Array.from(videoLockup?.classList || []).find(className => className.startsWith('content-id-')); if (contentIDClass) return contentIDClass.slice('content-id-'.length); const mainThumbnail = container.querySelector('a.ytLockupViewModelContentImage[href*="/watch?v="]'); if (!mainThumbnail || !mainThumbnail.href || !mainThumbnail.href.includes('/watch?v=')) return null; return new URL(mainThumbnail.href, location.origin).searchParams.get('v'); }; const key = "CentAnni_lastSeenVideoID"; const lastSeenID = await GM.getValue(key); let targetElement = null; let newLastSeenID = null; for (const container of videoContainers) { if (isSpecialVideo(container)) continue; const videoID = extractVideoID(container); if (!videoID) continue; if (!newLastSeenID) newLastSeenID = videoID; if (videoID === lastSeenID) { previousLastSeen?.classList.remove('CentAnni-style-last-seen'); container.classList.add("CentAnni-style-last-seen"); targetElement = container; } if (newLastSeenID && targetElement) break; } // update last seen video ID if (newLastSeenID && newLastSeenID !== lastSeenID) await GM.setValue(key, newLastSeenID); // scroll to the last seen video if (USER_CONFIG.lastSeenVideoScroll && targetElement) { requestAnimationFrame(() => { targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); } } // add trashcan icon to playlists to easily remove videos function playlistRemovalButtons() { const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"][role="main"]:not([hidden])'); const savePlaylistBtn = playlistPage?.querySelector(':is(toggle-button-view-model, ytd-toggle-button-renderer):not([hidden])'); const playlistContainer = playlistPage?.querySelector('ytd-playlist-video-list-renderer > #contents'); if (savePlaylistBtn || !playlistPage || !playlistContainer) return; const BTN_CLASS = document.querySelector('.shuffle-button a')?.className ?? ''; // watch for playlist changes const playlistObserver = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== 1) continue; if (node.matches && node.matches('ytd-playlist-video-renderer')) attachRemoveButtonToVideo(node); else node.querySelectorAll && node.querySelectorAll('ytd-playlist-video-renderer').forEach(attachRemoveButtonToVideo); } } }); playlistObserver.observe(playlistContainer, { childList: true, subtree: true }); // run and cleanup attachRemoveButtonsToAllVideos(); cleanupPlaylistRemovalButtons = () => { playlistObserver.disconnect(); cleanupRemoveButtons(); cleanupPlaylistRemovalButtons = null; }; function attachRemoveButtonToVideo(videoEl) { if (videoEl.hasAttribute('data-centanni-playlist-video-processed')) return; const metaContainer = videoEl.querySelector('#meta'); if (!metaContainer) return; const container = document.createElement('div'); container.className = 'CentAnni-container-playlist-btns'; if (USER_CONFIG.playlistTrashCan) { const removeBtn = document.createElement('button'); removeBtn.className = 'CentAnni-style-playlist-remove-btn'; removeBtn.title = 'Remove from Playlist'; removeBtn.textContent = '🗑️'; removeBtn.onclick = (event) => { event.stopPropagation(); event.preventDefault(); if (removeBtn.classList.contains('removing')) return; removeBtn.classList.add('removing'); simulateVideoRemoval(videoEl) .finally(() => { removeBtn.classList.remove('removing'); }); }; container.appendChild(removeBtn); } // adding 'Add to queue' button if (isWatchLater && runAddQueueBtn) { const addToQueue = document.createElement('button'); addToQueue.tabIndex = 0; addToQueue.className = `${BTN_CLASS} CentAnni-style-playlist-addToQueue-btn CentAnni-btn-feedback-shape`; addToQueue.textContent = 'Add to Queue'; addToQueue.onclick = (event) => { event.stopPropagation(); event.preventDefault(); simulateVideoRemoval(videoEl, true); }; container.appendChild(addToQueue); } metaContainer.appendChild(container); videoEl.setAttribute('data-centanni-playlist-video-processed', 'true'); } function attachRemoveButtonsToAllVideos() { const videoElements = playlistPage.querySelectorAll('ytd-playlist-video-renderer:not([data-centanni-playlist-video-processed])'); videoElements.forEach(attachRemoveButtonToVideo); } function cleanupRemoveButtons() { document.querySelectorAll('.CentAnni-container-playlist-btns').forEach(container => container.remove()); document.querySelectorAll('ytd-playlist-video-renderer[data-centanni-playlist-video-processed]').forEach(videoEl => videoEl.removeAttribute('data-centanni-playlist-video-processed')); } function simulateVideoRemoval(videoEl, addToQueue = false) { return new Promise((resolve) => { const menuBtn = videoEl.querySelector('#button'); if (!menuBtn) { resolve(); return; } const popupContainer = document.querySelector('ytd-popup-container'); if (popupContainer) popupContainer.classList.add('CentAnni-style-playlist-hide-menu'); menuBtn.click(); waitForElement('#items > ytd-menu-service-item-renderer', 300) .then(() => { return new Promise(r => setTimeout(r, 75)); }) .then(() => { const menuItems = [...document.querySelectorAll('#items > ytd-menu-service-item-renderer')]; let removeOption = null; for (const item of menuItems) { const formattedString = item.querySelector('yt-formatted-string'); if (addToQueue) { if ((formattedString && formattedString.textContent.includes('Add to queue')) || item.querySelector('svg path[d^="M2 2.864v6"]')) { item.click(); removeOption = false; break; } } else { if ((formattedString && formattedString.textContent.includes('Remove from')) || item.querySelector('svg path[d^="M19 3h-4V2a1"]')) { removeOption = item; break; } } } if (!removeOption) { docBody.click(); return Promise.resolve(); } removeOption.click(); return new Promise(r => setTimeout(r, 400)); }) .then(() => { if (popupContainer) popupContainer.classList.remove('CentAnni-style-playlist-hide-menu'); resolve(); }) .catch(() => { try { docBody.click(); } catch (e) {} if (popupContainer) popupContainer.classList.remove('CentAnni-style-playlist-hide-menu'); resolve(); }); }); } function waitForElement(selector, timeout = 700) { return new Promise((resolve) => { const element = playlistPage.querySelector(selector); if (element) return resolve(element); let timer; const observer = new MutationObserver(() => { const el = playlistPage.querySelector(selector); if (el) { clearTimeout(timer); observer.disconnect(); resolve(el); } }); timer = setTimeout(() => { observer.disconnect(); resolve(); }, timeout); observer.observe(playlistPage, { childList: true, subtree: true }); }); } } // add button to remove watched videos from watch later playlist function watchLaterRemoveBtn() { const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"][role="main"]:not([hidden])'); const wrapper = playlistPage?.querySelector('.metadata-buttons-wrapper.ytd-playlist-header-renderer'); const wrapperText = playlistPage?.querySelector('.metadata-text-wrapper.ytd-playlist-header-renderer'); const menuRendererText = wrapperText?.querySelector('ytd-playlist-byline-renderer'); const menuRenderer = wrapper?.querySelector('ytd-menu-renderer'); const menuButton = menuRenderer?.querySelector('button'); if (!playlistPage || !wrapper || !wrapperText || !menuRendererText || !menuRenderer || !menuButton || document.getElementById('CentAnni-remove-watched-btn')) return; const waitForElement = (selector, callback, timeout = 700) => { const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); callback(element); } }); observer.observe(document, { childList: true, subtree: true }); const cnl = () => { observer.disconnect(); docElement.classList.remove('CentAnni-playlist-remove-btn-hide-menus'); docBody.click(); }; setTimeout(cnl, timeout); }; const handleBtnClick = () => { docElement.classList.add('CentAnni-playlist-remove-btn-hide-menus'); menuButton.click(); const dPaths = ['M12 1C5', 'M12 3c']; const selector = dPaths.map(d => `ytd-popup-container > tp-yt-iron-dropdown a:has(svg path[d^="${d}"])`).join(','); waitForElement(selector, (removeLink) => { removeLink.click(); waitForElement('#confirm-button > yt-button-shape > button', (confirmButton) => { confirmButton.click(); showNotification('Videos removed from Watch Later playlist.'); if (USER_CONFIG.playlistLinks) setTimeout(() => location.reload(), 800); docElement.classList.remove('CentAnni-playlist-remove-btn-hide-menus'); }); }); }; const BTN_CLASS = document.querySelector('.shuffle-button a')?.className ?? ''; // btn remove watched videos const btnRemove = document.createElement('button'); btnRemove.id = 'CentAnni-remove-watched-btn'; btnRemove.className = `${BTN_CLASS} CentAnni-btn-feedback-shape`; btnRemove.textContent = 'Remove Watched Videos'; btnRemove.tabIndex = 0; btnRemove.onclick = handleBtnClick; wrapper.insertBefore(btnRemove, menuRenderer.nextSibling); // btn toggle watched videos const btnToggle = document.createElement('button'); btnToggle.id = 'CentAnni-toggle-watched-btn'; btnToggle.className = `${BTN_CLASS} CentAnni-btn-feedback-shape`; btnToggle.textContent = 'Toggle Watched'; btnToggle.tabIndex = 0; btnToggle.onclick = () => docElement.classList.toggle('CentAnni-hide-watched-wl'); wrapperText.insertBefore(btnToggle, menuRendererText.nextSibling); // clean up cleanupWatchLaterRemoveBtn = () => { docElement.classList.remove('CentAnni-playlist-remove-btn-hide-menus'); playlistPage.querySelectorAll('#CentAnni-remove-watched-btn, #CentAnni-toggle-watched-btn').forEach(el => el.remove()); cleanupWatchLaterRemoveBtn = null; }; } // move watch later header btn const moveWlChipBar = () => { const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"][role="main"]:not([hidden])'); wlHeader = playlistPage.querySelector('#header.ytd-item-section-renderer'); wlSidebar = playlistPage.querySelector('.thumbnail-and-metadata-wrapper.ytd-playlist-header-renderer'); wlSection = playlistPage.querySelector('ytd-item-section-renderer'); if (wlHeader && wlSidebar) { wlSidebar.appendChild(wlHeader); headerMoved = true; } }; const moveWlChipBarCleanUp = () => { wlSection.prepend(wlHeader); headerMoved = false; }; // open playlist videos without being in a playlist function handlePlaylistLinks() { let processedAllListVideos = false; const processTimers = []; function chromeClickHandler(event) { if (!event.ctrlKey && !event.metaKey && !event.shiftKey) { event.stopImmediatePropagation(); event.preventDefault(); const cleanUrl = event.currentTarget.getAttribute('CentAnni-chrome-pl-url'); window.open(cleanUrl, '_self'); } } const playlistPage = document.querySelector('ytd-browse[page-subtype="playlist"][role="main"]:not([hidden])'); if (!playlistPage) return; function processVideos() { const unprocessedVideos = [...playlistPage.querySelectorAll('#contents > ytd-playlist-video-renderer:not([data-centanni-list-video-processed])')]; if (unprocessedVideos.length === 0) { processedAllListVideos = true; return; } processedAllListVideos = false; unprocessedVideos.forEach(videoItem => { videoItem.setAttribute('data-centanni-list-video-processed', 'true'); const thumbnailLink = videoItem.querySelector('a#thumbnail'); const titleLink = videoItem.querySelector('a#video-title'); [thumbnailLink, titleLink].forEach(link => { if (link && link.href && (link.href.includes('list=WL') || link.href.includes('list=PL') || link.href.includes('list=LL'))) { try { const url = new URL(link.href); const videoID = url.searchParams.get('v'); if (videoID) { const cleanUrl = `https://www.youtube.com/watch?v=${videoID}`; link.href = cleanUrl; 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`); else { link.setAttribute('CentAnni-chrome-pl-url', cleanUrl); link.addEventListener('click', chromeClickHandler, true); } } } catch (e) { console.error('YouTube Alchemy: Error processing link:', e); } } }); }); } function runProcessVideos(times, initialDelay, interval, callback) { processedAllListVideos = false; let count = 0; function runProcess() { processVideos(); count++; if (count < times && !processedAllListVideos) processTimers.push(setTimeout(runProcess, interval)); else if (callback) callback(); } processTimers.push(setTimeout(runProcess, initialDelay)); } // handle navigation const serviceRequestSentHandler = () => { runProcessVideos(3, 1000, 1000, null); }; cleanupHandlePlaylistLinks = () => { processTimers.forEach(clearTimeout); document.removeEventListener('yt-service-request-sent', serviceRequestSentHandler); document.querySelectorAll('[data-centanni-list-video-processed]').forEach(el => { el.removeAttribute('data-centanni-list-video-processed'); }); if (USER_CONFIG.preventBackgroundExecution) { playlistPage.querySelectorAll('[CentAnni-chrome-pl-url]').forEach(link => { link.removeEventListener('click', chromeClickHandler, true); }); } cleanupHandlePlaylistLinks = null; }; runProcessVideos(6, 250, 500, function () { document.addEventListener('yt-service-request-sent', serviceRequestSentHandler); }); } // RSS feed button on channel page function addRSSFeedButton() { document.querySelector('.CentAnni-RSS-btn')?.remove(); const rssLinkElement = document.querySelector('link[rel="alternate"][type="application/rss+xml"]'); if (!rssLinkElement) return; const rssFeedUrl = rssLinkElement.getAttribute('href'); const actionsContainer = document.querySelector('.ytFlexibleActionsViewModelHost'); if (!actionsContainer) return; const BTN_CLASS = document.querySelector('.ytSubscribeButtonViewModelHost button')?.className ?? ''; const buttonContainer = document.createElement('div'); buttonContainer.className = 'ytFlexibleActionsViewModelAction'; const rssLink = document.createElement('a'); rssLink.id = 'CentAnni-channel-btn'; rssLink.className = `${BTN_CLASS} CentAnni-RSS-btn`; rssLink.title = 'Click to open RSS feed or right-click to Copy Link'; rssLink.rel = 'noopener noreferrer'; rssLink.href = rssFeedUrl; rssLink.target = '_blank'; const rssIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); rssIcon.setAttribute('viewBox', '0 0 24 24'); rssIcon.setAttribute('width', '24'); rssIcon.setAttribute('height', '24'); rssIcon.style.fill = '#FF9800'; const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', '6.18'); circle.setAttribute('cy', '17.82'); circle.setAttribute('r', '2.18'); rssIcon.appendChild(circle); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 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'); rssIcon.appendChild(path); rssLink.appendChild(rssIcon); rssLink.appendChild(document.createTextNode('RSS Feed')); buttonContainer.appendChild(rssLink); actionsContainer.appendChild(buttonContainer); } // add playlist buttons to channel page function addPlaylistButtons() { document.querySelectorAll('.CentAnni-pl-btn').forEach(el => el.remove()); const channelID = document.querySelector('[itemprop="identifier"]')?.content; if (!channelID) return; const allVideosURL = `https://www.youtube.com/playlist?list=UU${channelID.slice(2)}`; const fullVideoURL = `https://www.youtube.com/playlist?list=UULF${channelID.slice(2)}`; const shortsURL = `https://www.youtube.com/playlist?list=UUSH${channelID.slice(2)}`; const BTN_CLASS = document.querySelector('.ytSubscribeButtonViewModelHost button')?.className ?? ''; const actionsContainer = document.querySelector('.ytFlexibleActionsViewModelHost'); if (!actionsContainer) return; const createPlaylistButton = (url, text, buttonID) => { const buttonContainer = document.createElement('div'); buttonContainer.className = 'ytFlexibleActionsViewModelAction'; const buttonLink = document.createElement('a'); buttonLink.id = 'CentAnni-channel-btn'; buttonLink.className = `${BTN_CLASS} CentAnni-pl-btn`; buttonLink.setAttribute('data-centanni-playlist-channel-btn', buttonID); buttonLink.title = `Click to Open ${text} Playlist`; buttonLink.rel = 'noopener noreferrer'; buttonLink.href = url; buttonLink.target = '_self'; const playlistIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); playlistIcon.setAttribute('viewBox', '0 0 24 24'); playlistIcon.setAttribute('width', '24'); playlistIcon.setAttribute('height', '24'); playlistIcon.style.fill = 'currentColor'; const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('clip-rule', 'evenodd'); iconPath.setAttribute('fill-rule', 'evenodd'); 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'); playlistIcon.appendChild(iconPath); buttonLink.appendChild(playlistIcon); buttonLink.appendChild(document.createTextNode(text)); buttonContainer.appendChild(buttonLink); return buttonContainer; }; if (!USER_CONFIG.hideShorts) { const allVideosButton = createPlaylistButton(allVideosURL, 'All Uploads', 'all'); actionsContainer.appendChild(allVideosButton); } const fullVideosButton = createPlaylistButton(fullVideoURL, 'Videos', 'full'); actionsContainer.appendChild(fullVideosButton); if (!USER_CONFIG.hideShorts) { const shortsButton = createPlaylistButton(shortsURL, 'Shorts', 'shorts'); actionsContainer.appendChild(shortsButton); } } // function to change playlist direction async function playlistDirection() { if (!hasPlaylistPanel) return; let playlistItems; let currentVideoIndex; let videoAboveInfo; let videoBelowInfo; let nextButton; let observer; let nextBtnObserver; let timeoutNextBtnObs; let playNextBtnStatus = false; let validDataVideoAbove = false; let validDataVideoBelow = false; const key = "CentAnni_playlistDirection"; let savedDirection = await GM.getValue(key, 'down'); let reverseDirection = savedDirection === 'up'; function getPlaylistInfo() { videoAboveInfo = null; videoBelowInfo = null; validDataVideoAbove = false; validDataVideoBelow = false; function getVideoThumbnailURL(currentURL) { let videoID = null; if (currentURL) { const watchMatch = currentURL.match(/[?&]v=([^&#]*)/); if (watchMatch && watchMatch[1]) videoID = watchMatch[1]; else { const shortMatch = currentURL.match(/youtu\.be\/([^?&#]*)/); if (shortMatch && shortMatch[1]) videoID = shortMatch[1]; } } return videoID ? `https://i.ytimg.com/vi/${videoID}/mqdefault.jpg` : null; } function processVideoInfo(videoElement) { if (!videoElement) return { info: null, isValid: false }; const link = videoElement.querySelector('a#wc-endpoint'); const title = videoElement.querySelector('#video-title'); const href = link ? link.href : null; let thumbnailUrl = null; const thumbImg = videoElement.querySelector('ytd-thumbnail img'); if (thumbImg && thumbImg.src) thumbnailUrl = thumbImg.src; else thumbnailUrl = getVideoThumbnailURL(href); if (!(link && title)) return { info: null, isValid: false }; const info = { link: link, href: href, thumbnail: thumbnailUrl, title: title ? title.textContent.trim() : null }; const isValid = (link && href && thumbnailUrl && thumbnailUrl.trim() !== '' && title && title.textContent && title.textContent.trim() !== ''); return { info, isValid }; } if (!playlistSelectedVideo) return null; playlistItems = [...watchFlexyElement.querySelectorAll('ytd-playlist-panel-video-renderer')]; if (!playlistItems || playlistItems.length <= 1) return null; currentVideoIndex = playlistItems.indexOf(playlistSelectedVideo); if (currentVideoIndex === -1) return null; nextButton = watchFlexyElement.querySelector('.ytp-next-button'); if (currentVideoIndex > 0) { const videoAbove = playlistItems[currentVideoIndex - 1]; const result = processVideoInfo(videoAbove); videoAboveInfo = result.info; validDataVideoAbove = result.isValid; } if (currentVideoIndex < playlistItems.length - 1) { const videoBelow = playlistItems[currentVideoIndex + 1]; const result = processVideoInfo(videoBelow); videoBelowInfo = result.info; validDataVideoBelow = result.isValid; } const success = reverseDirection ? currentVideoIndex === 0 || validDataVideoAbove : validDataVideoBelow; return success; } function createButtons(reverseDirection) { const playlistActionMenu = watchFlexyElement.querySelector('#playlist-action-menu .top-level-buttons') || watchFlexyElement.querySelector('#playlist-action-menu'); if (!playlistActionMenu) return null; const BTN_CLASS = watchFlexyElement.querySelector('#secondary #playlist:not([playlist-type="TLPQ"]) #top-level-buttons-computed button')?.className ?? 'ytSpecButtonShapeNextHost ytSpecButtonShapeNextText ytSpecButtonShapeNextMono ytSpecButtonShapeNextSizeM ytSpecButtonShapeNextIconOnlyDefault ytSpecButtonShapeNextEnableBackdropFilterExperiment'; const ICON_CLASS = watchFlexyElement.querySelector('#secondary #playlist:not([playlist-type="TLPQ"]) #top-level-buttons-computed button > div')?.className ?? 'ytSpecButtonShapeNextIcon ytSpecButtonShapeNextElevatedContent'; const directionContainer = document.createElement('div'); directionContainer.id = 'CentAnni-playlist-direction-container'; const fragment = document.createDocumentFragment(); const directionText = document.createElement('span'); directionText.textContent = 'Playlist Direction:'; fragment.appendChild(directionText); function createDirectionButton(isUpButton) { const button = document.createElement('button'); button.className = `${BTN_CLASS} CentAnni-playlist-direction-btn`; if (reverseDirection === isUpButton) button.classList.add('active'); button.title = isUpButton ? 'Play Videos Ascending' : 'Play Videos Descending (YouTube Default)'; const iconDiv = document.createElement('div'); iconDiv.className = ICON_CLASS; iconDiv.setAttribute('aria-hidden', 'true'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); svg.setAttribute('height', '24'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('width', '24'); svg.setAttribute('focusable', 'false'); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', isUpButton ? 'M7.41 15.41 12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z' : 'M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'); svg.appendChild(path); iconDiv.appendChild(svg); button.appendChild(iconDiv); return button; } const upButton = createDirectionButton(true); const downButton = createDirectionButton(false); fragment.appendChild(upButton); fragment.appendChild(downButton); directionContainer.appendChild(fragment); const existingContainer = document.getElementById('CentAnni-playlist-direction-container'); existingContainer ? existingContainer.replaceWith(directionContainer) : playlistActionMenu.appendChild(directionContainer); return { upButton, downButton }; } function setNextButton(info) { if (!nextButton || !info) return; if (info.href && info.thumbnail && info.title) { nextButton.href = info.href; nextButton.dataset.preview = info.thumbnail; nextButton.dataset.tooltipText = info.title; } } function upNextBtn() { if (reverseDirection === playNextBtnStatus || !nextButton) return; if (reverseDirection) { if (videoAboveInfo) { setNextButton(videoAboveInfo); nextButton.removeEventListener('click', nextButtonClickHandler, true); nextButton.addEventListener('click', nextButtonClickHandler, true); document.addEventListener('keydown', handleKeyboardShortcut, true); playNextBtnStatus = true; } } else { document.removeEventListener('keydown', handleKeyboardShortcut, true); nextButton.removeEventListener('click', nextButtonClickHandler, true); if (videoBelowInfo) setNextButton(videoBelowInfo); playNextBtnStatus = false; } } function observeNextButtonReady(cb) { if (!nextButton || !cb) return; if (nextButton.href && nextButton.dataset.preview && nextButton.dataset.tooltipText) { cb(); return; } timeoutNextBtnObs = setTimeout(() => { nextBtnObserver?.disconnect(); cb(); }, 7000); nextBtnObserver = new MutationObserver(() => { if (nextButton.href && nextButton.dataset.preview && nextButton.dataset.tooltipText) { clearTimeout(timeoutNextBtnObs); nextBtnObserver.disconnect(); cb(); } }); nextBtnObserver.observe(nextButton, { attributes: true }); } function nextButtonClickHandler(e) { if (reverseDirection && videoAboveInfo) { e.preventDefault(); e.stopPropagation(); if (videoAboveInfo.link) videoAboveInfo.link.click(); return false; } } function handleKeyboardShortcut(e) { if (e.key === 'N' && e.shiftKey) { if (reverseDirection && videoAboveInfo) { e.preventDefault(); e.stopPropagation(); if (videoAboveInfo.link) videoAboveInfo.link.click(); return false; } } } async function upButtonHandler(e) { e.preventDefault(); e.stopPropagation(); await GM.setValue(key, 'up'); reverseDirection = true; upButton.classList.add('active'); downButton.classList.remove('active'); upNextBtn(); } // handle down button click async function downButtonHandler(e) { e.preventDefault(); e.stopPropagation(); await GM.setValue(key, 'down'); reverseDirection = false; upButton.classList.remove('active'); downButton.classList.add('active'); upNextBtn(); } // clean up cleanupPlaylistDirection = () => { document.removeEventListener('yt-autonav-pause-player-ended', handleVideoEnd); document.removeEventListener('keydown', handleKeyboardShortcut, true); nextButton?.removeEventListener('click', nextButtonClickHandler, true); upButton.onclick = null; downButton.onclick = null; clearTimeout(timeoutNextBtnObs); nextBtnObserver?.disconnect(); observer?.disconnect(); cleanupPlaylistDirection = null; }; const handleVideoEnd = () => { document.removeEventListener('yt-autonav-pause-player-ended', handleVideoEnd); if (reverseDirection && videoAboveInfo?.link) videoAboveInfo.link.click(); cleanupPlaylistDirection?.(); }; document.addEventListener('yt-autonav-pause-player-ended', handleVideoEnd); // initiation const { upButton, downButton } = createButtons(reverseDirection); if (upButton && downButton) { upButton.onclick = upButtonHandler; downButton.onclick = downButtonHandler; } observer = new MutationObserver(() => { const selectedItem = watchFlexyElement.querySelector('ytd-playlist-panel-video-renderer[selected]'); nextButton = watchFlexyElement.querySelector('.ytp-next-button'); if (selectedItem && nextButton) { playlistSelectedVideo = selectedItem; if (getPlaylistInfo()) { observer.disconnect(); observeNextButtonReady(upNextBtn); return; } } }); const config = { childList: true, subtree: true, }; observer.observe(watchFlexyElement, config); } // function to prevent autoplay function pauseYouTubeVideo(e) { e.currentTarget.removeEventListener(e.type, pauseYouTubeVideo); if (!/^https:\/\/(www\.|m\.)?youtube\.com\/watch\?v=/.test(window.location.href) || document.querySelector('.ytp-time-display.ytp-live') || (!USER_CONFIG.preventAutoplayPL && /&list=/.test(window.location.href))) return; const elements = { player: document.getElementById('movie_player'), video: document.querySelector('video'), thumbnailOverlayImage: document.querySelector('.ytp-cued-thumbnail-overlay-image'), thumbnailOverlay: document.querySelector('.ytp-cued-thumbnail-overlay') }; if (!elements.player || !elements.video) return; const urlParams = new URLSearchParams(window.location.search); const vinteo = urlParams.get('v'); if (document.querySelector('#player.theater') || document.querySelector('ytd-watch-flexy[theater]')) pauseVideo(); else { if (USER_CONFIG.autoTheaterMode) toggleTheaterMode(); requestAnimationFrame(() => requestAnimationFrame(pauseVideo)); } function pauseVideo() { const { player, video, thumbnailOverlayImage, thumbnailOverlay } = elements; if (player && video && !video.paused) { video.pause(); player.classList.remove('playing-mode'); player.classList.add('unstarted-mode', 'paused-mode'); const playingHandler = () => { if (thumbnailOverlayImage) { thumbnailOverlayImage.removeAttribute('style'); thumbnailOverlayImage.style.display = 'none'; thumbnailOverlay.removeAttribute('style'); thumbnailOverlay.style.display = 'none'; } video.removeEventListener('playing', playingHandler); }; video.addEventListener('playing', playingHandler); } showThumbnail(vinteo); } function showThumbnail(vinteo) { const { thumbnailOverlayImage, thumbnailOverlay } = elements; if (thumbnailOverlayImage && thumbnailOverlay && lastVideoID !== vinteo) { thumbnailOverlay.style.cssText = 'display: block'; void thumbnailOverlayImage.offsetHeight; thumbnailOverlayImage.style.cssText = ` display: block; z-index: 10; background-image: url("https://i.ytimg.com/vi/${vinteo}/maxresdefault.jpg"); `; } } } // YouTube Shorts loop control and auto-scroll const SVG_NS = "http://www.w3.org/2000/svg"; const createLoopIcon = () => { const svg = document.createElementNS(SVG_NS, "svg"); const path = document.createElementNS(SVG_NS, "path"); [["width", "24"], ["height", "24"], ["viewBox", "0 0 24 24"], ["fill", "currentColor"],].forEach(([k, v]) => svg.setAttribute(k, v)); path.setAttribute("d", "M7 7H17V10L21 6L17 2V5H5V11H7V7ZM17 17H7V14L3 18L7 22V19H19V13H17V17Z"); svg.appendChild(path); return svg; }; async function shortsPlaybackControl() { const shortVideo = document.querySelector('ytd-shorts #shorts-player > div.html5-video-container > video'); const menuBtn = document.querySelector('ytd-shorts ytd-reel-player-overlay-renderer .ytwReelActionBarViewModelHost'); if (!shortVideo || !menuBtn) return; const savedShortsSetting = 'CentAnni_shortsPlayMode'; const savedMode = await GM.getValue(savedShortsSetting, 'loop'); const BTN_CLASS = document.querySelector('.ytSpecButtonShapeWithLabelHost button')?.className ?? ''; let animationID = null; let endedListener = null; const cleanup = () => { if (animationID) { cancelAnimationFrame(animationID); animationID = null; } if (endedListener) { shortVideo.removeEventListener('ended', endedListener); endedListener = null; } }; cleanupShortsPlaybackControl = () => { cleanup(); document.getElementById('CentAnni-yt-shorts-controls')?.remove(); cleanupShortsPlaybackControl = null; }; const removeLoop = () => { shortVideo.removeAttribute('loop'); animationID = requestAnimationFrame(removeLoop); }; const nextShort = () => { document.querySelector('ytd-shorts #navigation-button-down button')?.click(); }; const applyMode = mode => { cleanup(); switch (mode) { case 'off': removeLoop(); break; case 'loop': shortVideo.setAttribute('loop', ''); break; case 'auto': removeLoop(); endedListener = nextShort; shortVideo.addEventListener('ended', endedListener); break; } }; const createButtons = () => { const container = document.createElement('div'); container.id = 'CentAnni-yt-shorts-controls'; const setActive = activeBtn => { container.querySelectorAll('.CentAnni-yt-shorts-control-btn').forEach(btn => { btn.classList.remove('active'); }); activeBtn.classList.add('active'); }; const createButton = (label, mode, title) => { const btn = document.createElement('button'); btn.className = `${BTN_CLASS} CentAnni-yt-shorts-control-btn`; btn.title = title; typeof label === 'string' ? (btn.textContent = label) : btn.appendChild(label); if (savedMode === mode) btn.classList.add('active'); btn.onclick = async () => { await GM.setValue(savedShortsSetting, mode); setActive(btn); applyMode(mode); }; return btn; }; container.append( createButton('off', 'off', 'Disable Looping'), createButton(createLoopIcon(), 'loop', 'Loop Short'), createButton('auto', 'auto', 'Auto Scroll') ); const existingContainer = document.getElementById('CentAnni-yt-shorts-controls'); existingContainer ? existingContainer.replaceWith(container) : menuBtn.prepend(container); }; createButtons(); applyMode(savedMode); } // function to sort comments newest first function sortCommentsNewFirst() { let commentObserver = null; let headerObserver = null; let dropdownObserver = null; let commentFallback = null; let dropdownFallback = null; if (!commentLanguage) createCommentLanguage(); const lang = commentLanguage; const newestLabel = lang.newest.toLowerCase(); // handler for YouTube service requests in tab view mode const serviceRequestHandler = function (event) { const commentsTab = watchFlexyElement.querySelector('.CentAnni-tabView-tab[data-tab="tab-2"]'); if (commentsTab && commentsTab.classList.contains('active')) { document.removeEventListener('yt-service-request-sent', serviceRequestHandler); observeCommentSection(); } }; // comment sorting detection function setupCommentSortingDetection() { if (USER_CONFIG.videoTabView) { document.removeEventListener('yt-service-request-sent', serviceRequestHandler); document.addEventListener('yt-service-request-sent', serviceRequestHandler); } else observeCommentSection(); } // observe comment section and detect when it's loaded function observeCommentSection() { const existing = watchFlexyElement.querySelector(cmtsSel); if (existing) { monitorHeader(existing); return; } commentObserver = new MutationObserver(() => { const commentsSection = watchFlexyElement.querySelector(cmtsSel); if (commentsSection) { clearTimeout(commentFallback); commentObserver.disconnect(); commentObserver = null; monitorHeader(commentsSection); } }); commentObserver.observe(watchFlexyElement, { childList: true, subtree: true }); commentFallback = setTimeout(() => { if (commentObserver) { commentObserver.disconnect(); commentObserver = null; } }, 7000); } function monitorHeader(commentsSection) { if (commentsSection.textContent.includes(lang.off)) return; headerObserver = new MutationObserver(() => { const commentsHeader = commentsSection.querySelector('ytd-comments-header-renderer'); const sortButton = commentsSection.querySelector('yt-sort-filter-sub-menu-renderer'); if (commentsHeader && sortButton) { headerObserver.disconnect(); headerObserver = null; changeSortingToNewestFirst(); } }); headerObserver.observe(commentsSection, { childList: true, subtree: true }); } // set newest first function changeSortingToNewestFirst() { const sortButton = watchFlexyElement.querySelector('yt-sort-filter-sub-menu-renderer yt-dropdown-menu tp-yt-paper-button'); if (!sortButton) return; sortButton.click(); dropdownObserver = new MutationObserver(() => { const listBox = watchFlexyElement.querySelector('tp-yt-paper-listbox'); if (!listBox) return; const options = listBox.querySelectorAll('a.yt-simple-endpoint'); let newestFirstOption = null; for (const option of options) { if (option.textContent.toLowerCase().includes(newestLabel)) { newestFirstOption = option; break; } } if (!newestFirstOption && options.length > 1) newestFirstOption = options[1]; if (newestFirstOption) { newestFirstOption.click(); dropdownObserver.disconnect(); dropdownObserver = null; clearTimeout(dropdownFallback); dropdownFallback = null; } }); dropdownObserver.observe(docElement, { childList: true, subtree: true }); dropdownFallback = setTimeout(() => { if (dropdownObserver) { dropdownObserver.disconnect(); dropdownObserver = null; } }, 7000); } cleanupSortCommentsNewFirst = () => { document.removeEventListener('yt-service-request-sent', serviceRequestHandler); if (commentObserver) { commentObserver.disconnect(); commentObserver = null; } if (headerObserver) { headerObserver.disconnect(); headerObserver = null; } if (dropdownObserver) { dropdownObserver.disconnect(); dropdownObserver = null; } if (commentFallback) { clearTimeout(commentFallback); commentFallback = null; } if (dropdownFallback) { clearTimeout(dropdownFallback); dropdownFallback = null; } cleanupSortCommentsNewFirst = null; }; setupCommentSortingDetection(); } // sort notifications chronologically function chronoNotifications() { const sectionSelector = '#sections yt-multi-page-menu-section-renderer'; const panel = document.querySelector('tp-yt-iron-dropdown:has([menu-style="multi-page-menu-style-type-notifications"])'); if (chronoNotificationRunning || !panel) return; chronoNotificationRunning = true; if (!notificationTimeData) createNotificationTimeData(); const { sequence, positions: unitPositions } = notificationTimeData; const ready = () => { const spinner = panel.querySelector('#spinner'), header = panel.querySelector('#header'), container = panel.querySelector('#container'), content = panel.querySelectorAll(`${sectionSelector} ytd-notification-renderer`); return content.length >= 4 && spinner && header && container && spinner.hasAttribute('hidden') && !header.hasAttribute('hidden') && !container.hasAttribute('hidden'); }; const whenReady = new Promise((resolve, reject) => { const timeout = setTimeout(() => { chronoNotificationRunning = false; observer.disconnect(); reject(); }, 7000); const check = () => { if (ready()) { observer.disconnect(); clearTimeout(timeout); resolve(); } }; const observer = new MutationObserver(check); observer.observe(panel, { subtree: true, attributes: true, attributeFilter: ['hidden'] }); check(); }); whenReady.then(() => { const sections = panel.querySelectorAll(sectionSelector); if (sections.length < 2) { chronoNotificationRunning = false; return; } const importantSection = sections[0]; const moreSection = sections[1]; const moreItemsContainer = moreSection.querySelector('#items'); const currentMoreItems = [...moreItemsContainer.children]; const importantItems = [...importantSection.querySelectorAll('ytd-notification-renderer')]; if (importantItems.length === 0) { chronoNotificationRunning = false; return; } function getSortKey(el) { const time = el.querySelector('.metadata yt-formatted-string:last-child'); if (!time) return { position: -1, numericValue: Infinity }; const rawTime = time.textContent.trim(); const digitMatch = rawTime.match(/(\d+)/); const numericToken = digitMatch ? digitMatch[1] : '0'; const numericValue = Number(numericToken); const unitString = rawTime.replace(numericToken, '').replace(/\s+/g, ' ').trim().toLowerCase(); const unitIndex = sequence.findIndex(u => unitString.includes(u)); const position = unitIndex === -1 ? -1 : unitPositions[sequence[unitIndex]]; return { position, numericValue }; } importantItems.forEach(item => { const A = getSortKey(item); const before = currentMoreItems.find(r => { const B = getSortKey(r); return (B.position > A.position) || (B.position === A.position && B.numericValue > A.numericValue); }); moreItemsContainer.insertBefore(item, before || null); const idx = currentMoreItems.indexOf(before); currentMoreItems.splice(idx === -1 ? currentMoreItems.length : idx, 0, item); }); chronoNotificationRunning = false; }); } // function to automatically open the chapter panel function openChapters() { runInPage(() => (document.querySelector('ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"], ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-auto-chapters"]')).visibility = "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"); } // check and close chat window and close function chatWindowCheck() { if (watchFlexyElement.hasAttribute('live-chat-present-and-expanded')) { const chatMessageElement = watchFlexyElement.querySelector('#chat-container ytd-live-chat-frame #message'); const chatMessageElementStatus = watchFlexyElement.querySelector('ytd-message-renderer.style-scope.ytd-live-chat-frame'); const isChatMessageElementVisible = chatMessageElementStatus && window.getComputedStyle(chatMessageElementStatus).display !== 'none'; if (chatMessageElement && isChatMessageElementVisible) { const messageText = chatMessageElement.textContent.trim(); if (messageText === 'Chat is disabled for this live stream.') docElement.classList.remove('CentAnni-close-live-chat'); } else closeLiveChat(); } else docElement.classList.remove('CentAnni-close-live-chat'); } // redirect channel home to default function channelRedirect() { const channelPage = USER_CONFIG.channelDefaultPages[channelHandleURL] || USER_CONFIG.defaultChannelPage; window.location.href = window.location.href.replace(/\/$/, '') + `/${channelPage}`; return; } // redirect shorts to video page function redirectShortsToVideoPage() { window.location.href = window.location.href.replace('/shorts/', '/watch?v='); return; } // expand video description function clickDescriptionBtn() { watchFlexyElement.querySelector('ytd-text-inline-expander tp-yt-paper-button#expand')?.click(); } // cinema mode while in theater and not hovering over the tab function cinemaMode() { cinemaModeActive = true; const btn = document.createElement('button'); btn.id = 'CentAnni-cinema-mode-btn'; btn.title = 'Toggle Cinema Mode'; btn.textContent = '🎬'; btn.onclick = () => docElement.classList.toggle('CentAnni-cinema-mode'); document.body.appendChild(btn); } // btn to save the current page as a channel's default function defaultChannelPageBtn() { if (document.getElementById('CentAnni-default-channel-page-btn')) return; const btn = document.createElement('button'); btn.id = 'CentAnni-default-channel-page-btn'; btn.title = 'Save Default Channel Page'; btn.textContent = '💾'; btn.onclick = saveChannelDefaultPage; (document.getElementById('page-header-banner') || document.getElementById('page-header')).appendChild(btn); } // function to hide watched videos based on percentage function markWatchedVideos() { markWatchedVideosObserver?.disconnect(); const location = (isHomePage && USER_CONFIG.videosHideWatchedHome && !USER_CONFIG.videosHideWatched && document.querySelector('ytd-browse[page-subtype="home"][role="main"]:not([hidden]) #contents')) || (isSubscriptionsPage && USER_CONFIG.videosHideWatchedSubscriptions && document.querySelector('ytd-browse[page-subtype="subscriptions"][role="main"]:not([hidden]) ytd-rich-grid-renderer > #contents')) || (isWatchPage && USER_CONFIG.videosHideWatchedVideo && document.querySelector('ytd-watch-flexy:not([hidden]) #secondary #related #items')) || (isChannelPage && USER_CONFIG.videosHideWatchedChannels && document.querySelector('ytd-browse[page-subtype="channels"][role="main"]:not([hidden]) #contents')) || (isPlaylistPage && USER_CONFIG.videosHideWatchedPlaylist && document.querySelector('ytd-browse[page-subtype="playlist"][role="main"]:not([hidden]) ytd-playlist-video-list-renderer > #contents')) || (isSearchPage && USER_CONFIG.videosHideWatchedSearch && document.querySelector('ytd-search:not([hidden]) ytd-section-list-renderer > #contents')); if (!location) return; const watchedClass = "CentAnni-marked-watched"; const checkedClass = "CentAnni-watched-checked"; const percentage = USER_CONFIG.videosHideWatchedGlobalJS; const progressSelector = ".ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment"; const tags = ['ytd-rich-item-renderer', 'yt-lockup-view-model', 'ytd-grid-video-renderer', 'ytd-playlist-video-renderer', 'ytd-video-renderer']; const videoSelector = tags.map(t => `${t}:has(${progressSelector}):not(.${watchedClass},.${checkedClass})`).join(","); const processVideos = () => { const videoContainers = location.querySelectorAll(videoSelector); videoContainers.forEach(container => { const progressBar = container.querySelector(progressSelector); if (progressBar) { const percent = parseFloat(progressBar.style.width) || 0; percent >= percentage ? container.classList.add(watchedClass) : container.classList.add(checkedClass); } }); }; processVideos(); markWatchedVideosObserver = new MutationObserver(() => { clearTimeout(markWatchedVideosTimeout); markWatchedVideosTimeout = setTimeout(() => { const newVideos = location.querySelectorAll(videoSelector); if (newVideos.length > 0) processVideos(); }, 50); }); markWatchedVideosObserver.observe(location, { childList: true }); } // helper to restore home chip const restoreHomeFeedFilterChip = (chipText) => { if (!chipText) return; runInPage(function (targetChipText) { const chip = [...document.querySelectorAll('ytd-browse[page-subtype="home"][role="main"]:not([hidden]) #header #chips yt-chip-cloud-chip-renderer')].find(chip => chip.querySelector('.ytChipShapeChip')?.textContent?.trim() === targetChipText); chip?.onTap?.(); }, chipText); }; // restore feed filter chip on the homepage and suggested videos async function restoreLastSelectedChip() { const homePage = document.querySelector('ytd-browse[page-subtype="home"][role="main"]:not([hidden])'); const feedFilterContainer = isHomePage ? homePage?.querySelector('#header #chips') : watchFlexyElement.querySelector('#related #chips'); if (!feedFilterContainer) return; let firstRun = true; let chipObserver; const ironChipStorage = isHomePage ? "CentAnni_lastSelectedIronChip" : "CentAnni_lastSelectedIronChipWatch"; const lastSelectedChip = await GM.getValue(ironChipStorage); const ironSelector = isHomePage ? 'yt-chip-cloud-chip-renderer.ytd-feed-filter-chip-bar-renderer' : 'yt-chip-cloud-chip-renderer.yt-chip-cloud-renderer'; const ironSelected = ironSelector + '.iron-selected'; const checkChips = async () => { const selectedChip = feedFilterContainer.querySelector(ironSelected + ':not(:first-child)'); if (selectedChip) { const chipText = selectedChip.querySelector('.ytChipShapeChip')?.textContent?.trim(); if (chipText) await GM.setValue(ironChipStorage, chipText); if (firstRun) setUpObserver(); } }; if (lastSelectedChip) { const ironChips = feedFilterContainer.querySelectorAll(ironSelector); for (const chip of ironChips) { const chipText = chip.querySelector('.ytChipShapeChip')?.textContent?.trim(); if (chipText === lastSelectedChip) { const button = chip.querySelector('button[role="tab"]'); isHomePage ? typeof chip.onTap === 'function' ? chip.onTap() : restoreHomeFeedFilterChip(chipText) : button?.click(); break; } } } cleanupRestoreLastSelectedChip = () => { chipObserver?.disconnect(); document.removeEventListener('yt-service-request-completed', checkChips); cleanupRestoreLastSelectedChip = null; }; document.addEventListener('yt-service-request-completed', checkChips); chipObserver?.disconnect(); const setUpObserver = () => { firstRun = false; const allChip = feedFilterContainer.querySelector(ironSelector + ':first-child'); chipObserver = new MutationObserver(async () => { if (allChip.hasAttribute('selected')) await GM.deleteValue(ironChipStorage); }); chipObserver.observe(allChip, { attributes: true, attributeFilter: ['selected'] }); }; } // reset function function handleYTNavigation() { if (isWatchPage) { const cleanUpVideo = [ [USER_CONFIG.videoTabView, cleanupTabView], [USER_CONFIG.playbackSpeed, cleanupSpeedObserver], [USER_CONFIG.subtitlesWhenMuted, cleanupCcOnMute], [USER_CONFIG.subtitlesWhenRewind, cleanupCcOnRewind], [USER_CONFIG.progressBar && !isLive, cleanupProgressBar], [USER_CONFIG.playbackSpeed, cleanupPlaybackSpeedController], [USER_CONFIG.feedFilterChipsWatch, cleanupRestoreLastSelectedChip], [USER_CONFIG.displayRemainingTime && !isLive, cleanupRemainingTime], [USER_CONFIG.commentsNewFirst && !isLive, cleanupSortCommentsNewFirst], [USER_CONFIG.playlistDirectionBtns && isPlaylistVideoPage, cleanupPlaylistDirection], [USER_CONFIG.playbackSpeed && USER_CONFIG.playbackSpeedBtns, cleanupPlaybackSpeedBtns], [USER_CONFIG.videoTabView && USER_CONFIG.tabViewChapters && hasChapterPanel, moveChapterTitleBack], [!USER_CONFIG.videoTabView && USER_CONFIG.autoExitFullscreen && !isPlaylistVideoPage, cleanupExitFullscreenNoTabView] ]; for (const [flag, callback] of cleanUpVideo) if (flag) callback?.(); mainVideoObserver?.disconnect(); mainVideoObserver = null; mainVideo = null; } else { const cleanUp = [ [isShortPage, cleanupShortsPlaybackControl], [isWatchLater && USER_CONFIG.plWLBtn, cleanupWatchLaterRemoveBtn], [isPlaylistPage && USER_CONFIG.playlistLinks, cleanupHandlePlaylistLinks], [isShortPage && USER_CONFIG.playbackSpeed, cleanupPlaybackSpeedController], [isHomePage && USER_CONFIG.feedFilterChips, cleanupRestoreLastSelectedChip], [isWatchLater && USER_CONFIG.compactLayout && headerMoved, moveWlChipBarCleanUp], [isPlaylistPage && (USER_CONFIG.playlistTrashCan || runAddQueueBtn), cleanupPlaylistRemovalButtons], [isSubscriptionsPage && USER_CONFIG.lastSeenVideo, () => { videoElementObserver?.disconnect(); videoElementObserver = null; }] ]; for (const [flag, callback] of cleanUp) if (flag) callback?.(); } if (USER_CONFIG.videosHideWatchedGlobalJS !== 0 && !USER_CONFIG.videosHideWatchedGlobal) { markWatchedVideosObserver?.disconnect(); markWatchedVideosObserver = null; clearTimeout(markWatchedVideosTimeout); markWatchedVideosTimeout = null; } if (USER_CONFIG.playbackSpeed) { document.removeEventListener('yt-player-updated', initialSpeed); document.addEventListener('yt-player-updated', initialSpeed); } if (USER_CONFIG.preventAutoplay) { document.removeEventListener('yt-player-updated', pauseYouTubeVideo); document.addEventListener('yt-player-updated', pauseYouTubeVideo); window.removeEventListener('load', pauseYouTubeVideo); window.addEventListener('load', pauseYouTubeVideo); } } // cache elements function updateCachedElements() { mastheadElement = document.getElementById('masthead'); startElement = mastheadElement?.querySelector('#start'); endElement = mastheadElement?.querySelector('#end'); guideOpen = mastheadElement?.hasAttribute('guide-persistent-and-visible'); headerElement = document.querySelector('#guide-content > #header'); if (!mastheadElement || !startElement || !endElement || !headerElement) console.error('YouTubeAlchemy: page element missing: mastheadElement', !!mastheadElement, ' startElement', !!startElement, ' endElement', !!endElement, ' headerElement', !!headerElement); } function updateVideoContext() { // cache video elements watchFlexyElement = document.querySelector('ytd-watch-flexy[role="main"]'); playerElement = document.getElementById('movie_player'); playlistPanel = document.getElementById('playlist'); panelsElement = document.getElementById('panels'); if (!watchFlexyElement || !playerElement || !playlistPanel || !panelsElement) { console.error('YouTubeAlchemy: video element missing: watchFlexyElement', !!watchFlexyElement, ' playerElement', !!playerElement, ' playlistPanel', !!playlistPanel, ' panelsElement', !!panelsElement); return; } videoSizeBtn = watchFlexyElement.querySelector('.ytp-size-button'); // live stream check isLiveVideo = !!watchFlexyElement.querySelector('.ytp-time-display.ytp-live'); isLive = isLiveVideo || isLiveStream; // music video check isMusicVideo = !!docElement.querySelector('meta[itemprop="genre"][content="Music"]'); // theater mode, vertical video, and large window check isTheaterMode = watchFlexyElement.hasAttribute('theater'); isLargeWindow = watchFlexyElement.hasAttribute('flexy-large-window_'); isVerticalVideo = watchFlexyElement.hasAttribute('is-vertical-video_'); if (USER_CONFIG.autoTheaterMode) enterTheaterMode = !isTheaterMode && !(USER_CONFIG.autoTheaterModeNotLargeWindow && isLargeWindow) && !(USER_CONFIG.autoTheaterModeNotPL && isPlaylistVideoPage) && !(USER_CONFIG.autoTheaterModeNotVerticalVideo && isVerticalVideo); // chapter panel check chapterPanel = watchFlexyElement.querySelector('ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-description-chapters], ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-macro-markers-auto-chapters]'); hasChapterPanel = !!chapterPanel; // playlist panel check if (isPlaylistVideoPage || checkPlaylistPanel) { hasPlaylistPanel = !!playlistPanel.querySelector('#playlist-items'); playlistSelectedVideo = playlistPanel.querySelector('ytd-playlist-panel-video-renderer[selected]'); } else hasPlaylistPanel = false; // transcript panel check useSearchableTranscript = USER_CONFIG.useLegacyTranscriptPanel; getTranscriptPanel(); // donation panel check if (!USER_CONFIG.hideFundraiser) { donationPanel = watchFlexyElement.querySelector('#donation-shelf'); hasDonationPanel = !!donationPanel?.querySelector('ytd-donation-shelf-renderer'); } // playback speed channel check if (USER_CONFIG.playbackSpeed) { channelHandle = watchFlexyElement.querySelector('#channel-name a')?.getAttribute('href')?.slice(2); defaultSpeed = USER_CONFIG.channelPlaybackSpeeds[channelHandle] || USER_CONFIG.playbackSpeedValue; if (!channelHandle) collabPlaybackSpeedCheck(); } } // Chrome CSP compliance for setVideoQuality function runSetVideoQuality() { const c = `(${setVideoQuality})(${JSON.stringify(USER_CONFIG.defaultQuality)},${JSON.stringify(USER_CONFIG.defaultQualityPremium)});`; const s = document.createElement('script'); s.text = scriptPolicy ? scriptPolicy.createScript(c) : c; document.documentElement.appendChild(s); s.remove(); } // ┌───────────────────────────────────────────────────────────────────┐ // │ language support │ // └───────────────────────────────────────────────────────────────────┘ // settings panel: audio, subtitles, and transcript languages function createLabeledLanguageOptions() { const flags = { auto: '🌐', chinese: '🇨🇳', czech: '🇨🇿', danish: '🇩🇰', dutch: '🇳🇱', english: '🇺🇸', finnish: '🇫🇮', french: '🇫🇷', german: '🇩🇪', greek: '🇬🇷', hebrew: '🇮🇱', hindi: '🇮🇳', hungarian: '🇭🇺', indonesian: '🇮🇩', italian: '🇮🇹', japanese: '🇯🇵', korean: '🇰🇷', norwegian: '🇳🇴', polish: '🇵🇱', portuguese: '🇵🇹', romanian: '🇷🇴', spanish: '🇪🇸', swedish: '🇸🇪', ukrainian: '🇺🇦', vietnamese: '🇻🇳' }; const languages = { auto: 'Auto (default)', english: 'English', chinese: 'Chinese - 中文 (简体)', french: 'French - Français', german: 'German - Deutsch', italian: 'Italian - Italiano', japanese: 'Japanese - 日本語', korean: 'Korean - 한국어', czech: 'Czech - Čeština', danish: 'Danish - Dansk', dutch: 'Dutch - Nederlands', finnish: 'Finnish - Suomi', greek: 'Greek - Ελληνικά', hebrew: 'Hebrew - עברית', hindi: 'Hindi - हिन्दी', hungarian: 'Hungarian - Magyar', indonesian: 'Indonesian - Bahasa Indonesia', norwegian: 'Norwegian - Norsk', polish: 'Polish - Polski', portuguese: 'Portuguese - Português', romanian: 'Romanian - Română', spanish: 'Spanish - Español', swedish: 'Swedish - Svenska', ukrainian: 'Ukrainian - Українська', vietnamese: 'Vietnamese - Tiếng Việt' }; const standard = {}; const withOff = {}; const withNone = {}; for (const [key, label] of Object.entries(languages)) { standard[key] = `${flags[key] || ''} ${label}`; withOff[key] = standard[key]; if (key === 'auto') { withOff.off = '🚫 Off'; withNone.none = '🚫 None (default)'; } else { withNone[key] = standard[key]; } } labeledLanguageOptions = { standard, withOff, withNone }; } // set audio, subtitles, and transcript languages function getLanguageCode(language) { switch (language) { case 'chinese': return 'zh'; case 'czech': return 'cs'; case 'danish': return 'da'; case 'dutch': return 'nl'; case 'english': return 'en'; case 'finnish': return 'fi'; case 'french': return 'fr'; case 'german': return 'de'; case 'greek': return 'el'; case 'hebrew': return 'he'; case 'hindi': return 'hi'; case 'hungarian': return 'hu'; case 'indonesian': return 'id'; case 'italian': return 'it'; case 'japanese': return 'ja'; case 'korean': return 'ko'; case 'norwegian': return 'no'; case 'polish': return 'pl'; case 'portuguese': return 'pt'; case 'romanian': return 'ro'; case 'spanish': return 'es'; case 'swedish': return 'sv'; case 'ukrainian': return 'uk'; case 'vietnamese': return 'vi'; } } function createUiLanguageLabels() { switch (uiLanguage) { case 'cs': uiLanguageLabels = { subs: 'Titulky', off: 'Vypnuto' }; break; case 'da': uiLanguageLabels = { subs: 'Undertekster', off: 'Fra' }; break; case 'de': uiLanguageLabels = { subs: 'Untertitel', off: 'Aus' }; break; case 'el': uiLanguageLabels = { subs: 'Υπότιτλοι', off: 'Απενεργοποίηση' }; break; case 'es': uiLanguageLabels = { subs: 'Subtítulos', off: 'Desactivados' }; break; case 'fi': uiLanguageLabels = { subs: 'Tekstitys', off: 'Pois' }; break; case 'fr': uiLanguageLabels = { subs: 'Sous-titres', off: 'Désactivés' }; break; case 'he': uiLanguageLabels = { subs: 'כתוביות', off: 'כבוי' }; break; case 'hi': uiLanguageLabels = { subs: 'उपशीर्षक', off: 'बंद' }; break; case 'hu': uiLanguageLabels = { subs: 'Feliratok', off: 'Ki' }; break; case 'id': uiLanguageLabels = { subs: 'Subtitle', off: 'Nonaktifkan' }; break; case 'it': uiLanguageLabels = { subs: 'Sottotitoli', off: 'Disattivati' }; break; case 'ja': uiLanguageLabels = { subs: '字幕', off: 'オフ' }; break; case 'ko': uiLanguageLabels = { subs: '자막', off: '끄기' }; break; case 'nl': uiLanguageLabels = { subs: 'Ondertiteling', off: 'Uit' }; break; case 'no': uiLanguageLabels = { subs: 'Undertekster', off: 'Av' }; break; case 'pl': uiLanguageLabels = { subs: 'Napisy', off: 'Wyłączone' }; break; case 'pt': uiLanguageLabels = { subs: 'Legendas', off: 'Desativar' }; break; case 'ro': uiLanguageLabels = { subs: 'Subtitrări', off: 'Dezactivat' }; break; case 'sv': uiLanguageLabels = { subs: 'Undertexter', off: 'Av' }; break; case 'uk': uiLanguageLabels = { subs: 'Субтитри', off: 'Вимкнено' }; break; case 'vi': uiLanguageLabels = { subs: 'Phụ đề', off: 'Tắt' }; break; case 'zh': uiLanguageLabels = { subs: '字幕', off: '关闭' }; break; default: uiLanguageLabels = { subs: 'Subtitles', off: 'Off' }; } } // sort notifications chronologically function createNotificationTimeData() { let sequence; if (uiLanguage === 'en') sequence = ['second ago', 'seconds ago', 'minute ago', 'minutes ago', 'hour ago', 'hours ago', 'day ago', 'days ago', 'week ago', 'weeks ago', 'month ago', 'months ago', 'year ago', 'years ago']; else { const rtf = new Intl.RelativeTimeFormat(uiLanguage, { numeric: 'always' }); const pr = new Intl.PluralRules(uiLanguage); const pluralNum = pr.select(2) === 'one' ? 3 : 2; const base = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year']; sequence = []; for (const unit of base) { sequence.push(rtf.format(-1, unit).replace(/^\d+\s*/, '').trim().toLowerCase()); sequence.push(rtf.format(-pluralNum, unit).replace(/^\d+\s*/, '').trim().toLowerCase()); } } const positions = Object.create(null); sequence.forEach((unit, index) => { positions[unit] = Math.floor(index / 2); }); notificationTimeData = { sequence, positions }; } // home color code videos function getHomeCategoryKeywords(locale) { switch (locale) { case 'cs': return { live: ['sledujících', 'živě'], upcoming: ['čeká', 'naplánováno na'], streamed: ['vysíláno'] }; case 'da': return { live: ['seere', 'direkte'], upcoming: ['venter', 'planlagt til'], streamed: ['streamet'] }; case 'de': return { live: ['zuschauer', 'live'], upcoming: ['wartend', 'geplant für'], streamed: ['gestreamt'] }; case 'el': return { live: ['θεατές', 'ζωντανά'], upcoming: ['αναμονή', 'προγραμματισμένο για'], streamed: ['μεταδόθηκε'] }; case 'es': return { live: ['espectadores', 'en vivo'], upcoming: ['esperando', 'programado para'], streamed: ['emitido', 'transmitido'] }; case 'fi': return { live: ['katsojaa', 'suorana'], upcoming: ['odottaa', 'suunniteltu'], streamed: ['striimattu'] }; case 'fr': return { live: ['spectateurs', 'en direct'], upcoming: ['en attente', 'programmé pour'], streamed: ['diffusé'] }; case 'he': return { live: ['צופים', 'בשידור חי'], upcoming: ['ממתין', 'מתוזמן ל'], streamed: ['שודר'] }; case 'hi': return { live: ['देख रहे', 'लाइव'], upcoming: ['प्रतीक्षा', 'निर्धारित'], streamed: ['स्ट्रीम किया'] }; case 'hu': return { live: ['néző', 'élő'], upcoming: ['várakozik', 'ütemezve'], streamed: ['streamelve'] }; case 'id': return { live: ['orang menonton', 'langsung'], upcoming: ['menunggu', 'dijadwalkan pada'], streamed: ['streaming'] }; case 'it': return { live: ['guardando', 'in diretta', 'spettatori'], upcoming: ['in attesa', 'programmato per'], streamed: ['trasmesso', 'in streaming'] }; case 'ja': return { live: ['人が視聴中', 'ライブ'], upcoming: ['待機中', '予定'], streamed: ['配信済み'] }; case 'ko': return { live: ['명 시청 중', '라이브'], upcoming: ['명 대기 중', '예정됨'], streamed: ['스트리밍됨'] }; case 'nl': return { live: ['kijkers', 'live'], upcoming: ['wachten', 'gepland voor'], streamed: ['gestreamd'] }; case 'no': return { live: ['seere', 'direkte'], upcoming: ['venter', 'planlagt til'], streamed: ['strømmet'] }; case 'pl': return { live: ['oglądających', 'na żywo'], upcoming: ['oczekujących', 'zaplanowano na'], streamed: ['transmitowano'] }; case 'pt': return { live: ['assistindo', 'ao vivo'], upcoming: ['aguardando', 'programado para'], streamed: ['transmitido'] }; case 'ro': return { live: ['spectatori', 'în direct'], upcoming: ['așteaptă', 'programat pentru'], streamed: ['transmis'] }; case 'sv': return { live: ['tittare', 'direkt'], upcoming: ['väntar', 'planerat till'], streamed: ['sändes'] }; case 'uk': return { live: ['глядачів', 'наживо'], upcoming: ['очікує', 'заплановано на'], streamed: ['трансляція'] }; case 'vi': return { live: ['đang xem', 'trực tiếp'], upcoming: ['đang chờ', 'đã lên lịch'], streamed: ['đã phát trực tiếp'] }; case 'zh': return { live: ['人正在观看', '直播'], upcoming: ['正在等待', '预定发布时间'], streamed: ['直播时间'] }; default: return { live: ['watching', 'live'], upcoming: ['waiting', 'scheduled for'], streamed: ['streamed'] }; } } function createHomeCategoryData() { let rawCategories; if (uiLanguage === 'en') { rawCategories = { live: ['watching'], upcoming: ['waiting', 'scheduled for'], newly: ['1 day ago', 'hours ago', 'hour ago', 'minutes ago', 'minute ago', 'seconds ago', 'second ago'], 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'], recent: ['1 week ago', '7 days ago', '6 days ago', '5 days ago', '4 days ago', '3 days ago', '2 days ago'], latterly: ['12 months ago', '11 months ago', '10 months ago', '9 months ago', '8 months ago', '7 months ago', '6 months ago', '5 months ago', '4 months ago', '3 months ago', '2 months ago'], old: ['years ago', '1 year ago'], streamed: ['streamed'] }; } else { const rtf = new Intl.RelativeTimeFormat(uiLanguage, { numeric: 'always' }); const format = (num, unit) => rtf.format(-num, unit).replace(/^-?\d+\s*/, '').toLowerCase(); const uniq = arr => [...new Set(arr)]; const keywords = getHomeCategoryKeywords(uiLanguage); rawCategories = { live: keywords.live, upcoming: keywords.upcoming, streamed: keywords.streamed, old: uniq([[2, 'year'], [1, 'year']].map(([n, u]) => format(n, u))), latterly: uniq([12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2].map(n => format(n, 'month'))), lately: uniq([[1, 'month'], [2, 'week'], [14, 'day'], [13, 'day'], [12, 'day'], [11, 'day'], [10, 'day'], [9, 'day'], [8, 'day']].map(([n, u]) => format(n, u))), recent: uniq([[1, 'week'], [7, 'day'], [6, 'day'], [5, 'day'], [4, 'day'], [3, 'day'], [2, 'day']].map(([n, u]) => format(n, u))), newly: uniq([[1, 'day'], [2, 'hour'], [1, 'hour'], [2, 'minute'], [1, 'minute'], [2, 'second'], [1, 'second']].map(([n, u]) => format(n, u))) }; } const categories = {}; for (const [key, values] of Object.entries(rawCategories)) categories[key] = values.map(value => value.toLowerCase()); homeCategoryData = { categories, streamedRegex: new RegExp(`(?:${categories.streamed.join('|')})`, 'i') }; } // sort comments newest first function createCommentLanguage() { switch (uiLanguage) { case 'cs': commentLanguage = { off: 'Komentáře jsou vypnuty', newest: 'nejnovější' }; break; case 'da': commentLanguage = { off: 'Kommentarer er slået fra', newest: 'nyeste' }; break; case 'de': commentLanguage = { off: 'Kommentare sind deaktiviert', newest: 'neueste' }; break; case 'el': commentLanguage = { off: 'Τα σχόλια είναι απενεργοποιημένα', newest: 'νεότερα' }; break; case 'es': commentLanguage = { off: 'Los comentarios están desactivados', newest: 'más recientes' }; break; case 'fi': commentLanguage = { off: 'Kommentit ovat pois päältä', newest: 'uusimmat' }; break; case 'fr': commentLanguage = { off: 'Les commentaires sont désactivés', newest: 'les plus récents' }; break; case 'he': commentLanguage = { off: 'התגובות מושבתות', newest: 'החדשות ביותר' }; break; case 'hi': commentLanguage = { off: 'टिप्पणियाँ बंद हैं', newest: 'नवीनतम' }; break; case 'hu': commentLanguage = { off: 'A hozzászólások ki vannak kapcsolva', newest: 'legújabb' }; break; case 'id': commentLanguage = { off: 'Komentar dimatikan', newest: 'terbaru' }; break; case 'it': commentLanguage = { off: 'I commenti sono disattivati', newest: 'più recenti' }; break; case 'ja': commentLanguage = { off: 'コメントはオフになっています', newest: '新しい順' }; break; case 'ko': commentLanguage = { off: '댓글이 사용 중지되었습니다', newest: '최신' }; break; case 'nl': commentLanguage = { off: 'Reacties zijn uitgeschakeld', newest: 'nieuwste' }; break; case 'no': commentLanguage = { off: 'Kommentarer er slått av', newest: 'nyeste' }; break; case 'pl': commentLanguage = { off: 'Komentarze są wyłączone', newest: 'najnowsze' }; break; case 'pt': commentLanguage = { off: 'Os comentários estão desativados', newest: 'mais recentes' }; break; case 'ro': commentLanguage = { off: 'Comentariile sunt dezactivate', newest: 'cele mai noi' }; break; case 'sv': commentLanguage = { off: 'Kommentarer är inaktiverade', newest: 'nyaste' }; break; case 'uk': commentLanguage = { off: 'Коментарі вимкнено', newest: 'найновіші' }; break; case 'vi': commentLanguage = { off: 'Tính năng bình luận đã bị tắt', newest: 'mới nhất' }; break; case 'zh': commentLanguage = { off: '评论已关闭', newest: '最新' }; break; default: commentLanguage = { off: 'Comments are turned off', newest: 'newest' }; } } // ┌───────────────────────────────────────────────────────────────────┐ // │ initialization │ // └───────────────────────────────────────────────────────────────────┘ let pageObserver; let liveObserver; let muteObserver; let speedBtnObserver; let mainVideoObserver; let videoElementObserver; let sponsorBlockTimeObserver; let initialRun = true; let locationUpdated = false; let currentURL = null; let xianURL = null; let videoID = null; let lastVideoID = null; let isHomePage = false; let isVideoPage = false; let isLive = false; let isLiveVideo = false; let isLiveStream = false; let isWatchPage = false; let isShortPage = false; let isSearchPage = false; let isSubscriptionsPage = false; let isPlaylistPage = false; let isPlaylistVideoPage = false; let isWatchLater = false; let playerElement = null; let panelsElement = null; let watchFlexyElement = null; let mainVideo = null; let guideButton = null; let guideOpen = null; let guideClosed = null; let mastheadElement = null; let startElement = null; let endElement = null; let headerElement = null; let settingsBtnMoved = null; let videoSizeBtn = null; let isFullscreen = false; let hasPlaylistPanel = false; let playlistPanel = null; let playlistSelectedVideo = null; let hasDonationPanel = false; let donationPanel = null; let isChannelPage = false; let isChannelHome = false; let channelHandleURL = null; let isMusicVideo = false; let initialCloseLiveChat = null; let enterTheaterMode = false; let isTheaterMode = null; let isVerticalVideo = null; let isLargeWindow = null; let hasChapterPanel = null; let chapterPanel = null; let hasTranscriptPanel = null; let transcriptPanel = null; let transcriptLoaded = false; let currentActiveTab = null; let useSearchableTranscript; let wasClosed; let markWatchedVideosTimeout; let markWatchedVideosObserver; let ignoreRateChange = false; let onRateChange = null; let lastUserRate = null; let cleanupCcOnRewind; let cleanupCcOnMute; let updateTitleContainer; let moveChapterTitleBack; let wlHeader, wlSidebar, wlSection; let headerMoved = false; let cleanupTabView; let cleanupProgressBar; let cleanupSpeedObserver; let cleanupRemainingTime; let cleanupPlaybackSpeedBtns; let cleanupPlaylistDirection; let cleanupWatchLaterRemoveBtn; let cleanupHandlePlaylistLinks; let cleanupSortCommentsNewFirst; let cleanupShortsPlaybackControl; let cleanupPlaylistRemovalButtons; let cleanupPlaybackSpeedController; let cleanupRestoreLastSelectedChip; let cleanupExitFullscreenNoTabView; let playbackSpeedActive = false; let remainingTimeActive = false; let progressBarActive = false; let cinemaModeActive = false; let speedNotification = false; let speedNotificationElement = null; let speedKeysMapped = false; let hideNotificationTimeout; let notificationTimer = 800; let toggleKey = '', increaseKey = '', decreaseKey = ''; let defaultKeys = new Set(); let specialKeys = Object.create(null); let chronoNotificationRunning = false; let defaultSpeed = USER_CONFIG.playbackSpeedValue; let newUserRate; let channelHandle; let commentLanguage; let homeCategoryData; let notificationTimeData; let labeledLanguageOptions; let updateSegmentsOpacity; let cachedSegmentTimes = []; const isHideSidebarChecked = USER_CONFIG.mButtonDisplay; const showPlaybackSpeed = USER_CONFIG.playbackSpeed; const ChatGPTLabel = USER_CONFIG.targetChatGPTLabel; const NotebookLMLabel = USER_CONFIG.targetNotebookLMLabel; const runAddQueueBtn = USER_CONFIG.playlistQueueBtn && !USER_CONFIG.hideMiniPlayer; const checkPlaylistPanel = !USER_CONFIG.hideQueueBtn || runAddQueueBtn; const transcriptSearchableSegmentSel = 'ytd-transcript-segment-renderer'; const transcriptButtonSel = '#panels ytd-video-description-transcript-section-renderer button'; const transcriptListSel = 'yt-section-list-renderer[data-target-id="PAmodern_transcript_view"]'; const transcriptSearchableContainerSel = 'ytd-transcript-segment-list-renderer #segments-container'; const transcriptSearchableSel = '#panels ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"]'; const transcriptSegmentSel = 'transcript-segment-view-model.ytwTranscriptSegmentViewModelHost, .ytwTranscriptSegmentViewModelHost'; const infoSel = 'ytd-engagement-panel-section-list-renderer[target-id=engagement-panel-structured-description]'; const prBaSel = '.ytp-chrome-bottom > .ytp-progress-bar-container'; const menuSel = '#primary #top-row #top-level-buttons-computed'; const chapSel = '.ytp-chrome-bottom .ytp-chapters-container'; const fsCnSel = '#movie_player > div.ytp-chrome-bottom'; const prBeSel = '#columns #primary #below'; const cmtsSel = 'ytd-comments#comments'; const vidPSel = '.html5-video-player'; const videoTargets = [infoSel, menuSel, cmtsSel, vidPSel, chapSel, prBaSel, fsCnSel, prBeSel]; const browseTargets = ['#contents img, #reel-video-renderer .action-container > #actions']; const videoPageContainer = '#page-manager > ytd-watch-flexy[role=main]:not([hidden])'; const browseContainer = '#page-manager > :is(ytd-browse, ytd-search, ytd-shorts):not([hidden])[role="main"]'; const scriptPolicy = window.trustedTypes && trustedTypes.createPolicy('CentAnniAlchemy', { createScript: s => s }); // create arrays with enabled features const runFeatures = features => { for (const [flags, callback] of features) if (!flags || flags()) callback(); }; const createEnabledFeatures = features => features.filter(([enabled]) => enabled).map(([, flags, callback]) => [flags, callback]); const videoFeatures = createEnabledFeatures([ [USER_CONFIG.defaultQuality !== 'auto', null, runSetVideoQuality], [USER_CONFIG.autoTheaterMode, () => enterTheaterMode, toggleTheaterMode], [USER_CONFIG.playbackSpeed, null, videoPlaybackSpeed], [USER_CONFIG.progressBar, () => !isLive, keepProgressBarVisible], [USER_CONFIG.displayRemainingTime, () => !isLive, remainingTime], [USER_CONFIG.commentsNewFirst, () => !isLive, sortCommentsNewFirst], [USER_CONFIG.expandVideoDescription && !USER_CONFIG.videoTabView, null, clickDescriptionBtn], [USER_CONFIG.autoOpenChapters && !USER_CONFIG.videoTabView, () => hasChapterPanel, openChapters], [USER_CONFIG.autoOpenTranscript && !USER_CONFIG.videoTabView, () => hasTranscriptPanel, clickTranscriptBtn], [USER_CONFIG.defaultAudioLanguage !== 'auto' || USER_CONFIG.defaultSubtitleLanguage !== 'auto', null, setLanguage], [!USER_CONFIG.videoTabView && (USER_CONFIG.autoOpenChapters || USER_CONFIG.autoOpenTranscript), () => (USER_CONFIG.autoOpenChapters && hasChapterPanel) || (USER_CONFIG.autoOpenTranscript && hasTranscriptPanel), scrollToTop], [USER_CONFIG.hideEndscreen, null, () => setTimeout(() => { getThumbnailUrl((url) => { docElement.style.setProperty('--video-url', url); }); }, 1000)], [USER_CONFIG.playlistDirectionBtns, () => isPlaylistVideoPage, () => requestIdleCallback(playlistDirection)], [USER_CONFIG.feedFilterChipsWatch, null, () => requestIdleCallback(restoreLastSelectedChip)], [USER_CONFIG.maxVidSize || USER_CONFIG.compactLayoutVideo, null, maxVideoSize], [USER_CONFIG.videoTabView, null, () => initialRun ? requestIdleCallback(tabView) : tabView()], [USER_CONFIG.closeChatWindow, null, () => initialRun ? requestIdleCallback(chatWindowCheck) : setTimeout(chatWindowCheck, 1000)], [USER_CONFIG.enableCinemaMode, () => !cinemaModeActive, cinemaMode], [USER_CONFIG.hideProdTxt, null, hideProductsSpan], [USER_CONFIG.subtitlesWhenMuted, null, () => requestIdleCallback(ccOnMute)], [USER_CONFIG.subtitlesWhenRewind, null, () => requestIdleCallback(ccOnRewind)], [!USER_CONFIG.videoTabView && USER_CONFIG.autoExitFullscreen, () => !isPlaylistVideoPage, exitFullscreenNoTabView], [USER_CONFIG.playbackSpeed || USER_CONFIG.progressBar || USER_CONFIG.displayRemainingTime, null, () => requestIdleCallback(videoObserver)] ]); const browseFeatures = createEnabledFeatures([ [!USER_CONFIG.redirectShorts, () => isShortPage, shortsPlaybackControl], [USER_CONFIG.plWLBtn, () => isWatchLater, watchLaterRemoveBtn], [USER_CONFIG.compactLayout, () => isWatchLater, moveWlChipBar], [USER_CONFIG.playlistLinks, () => isPlaylistPage, handlePlaylistLinks], [USER_CONFIG.lastSeenVideo, () => isSubscriptionsPage, markLastSeenVideo], [USER_CONFIG.colorCodeVideosEnabled, () => isHomePage, homeColorCodeVideos], [!USER_CONFIG.redirectShorts && USER_CONFIG.playbackSpeed, () => isShortPage, createPlaybackSpeedController], [USER_CONFIG.defaultChannelPage !== 'home', () => isChannelPage, defaultChannelPageBtn], [USER_CONFIG.channelRSSBtn, () => isChannelPage, () => requestIdleCallback(addRSSFeedButton)], [USER_CONFIG.feedFilterChips, () => isHomePage, () => requestIdleCallback(restoreLastSelectedChip)], [USER_CONFIG.channelPlaylistBtn, () => isChannelPage, () => requestIdleCallback(addPlaylistButtons)], [USER_CONFIG.playlistTrashCan || runAddQueueBtn, () => isPlaylistPage, () => requestIdleCallback(playlistRemovalButtons)], [USER_CONFIG.hideMiniPlayer, null, () => document.querySelector('.ytp-miniplayer-close-button')?.click()] ]); // initiate the script async function initializeAlchemy() { updateCachedElements(); buttonsLeftHeader(); if (isWatchPage) { updateVideoContext(); runFeatures(videoFeatures); // transcript exporter if (!USER_CONFIG.YouTubeTranscriptExporter) createButtons('settings'); else if (USER_CONFIG.lazyTranscriptLoading && hasTranscriptPanel && !isLive) createButtons(['settings', 'lazyload']); else requestIdleCallback(() => setTimeout(runYTE, 250)); } else { createButtons('settings'); runFeatures(browseFeatures); } initialRun = false; locationUpdated = false; } // YouTube navigation handler async function handleYouTubeNavigation() { // console.log("YouTubeAlchemy: Event Listner Arrived"); const newURL = window.location.href; if (newURL !== currentURL) { currentURL = newURL; // console.log("YouTubeAlchemy: Only One Survived"); if (pageObserver) pageObserver.disconnect(); if (!cssSettingsApplied) loadCSSsettings(); if (!locationUpdated) updateLocation(); if (!docBody) docBody = document.body; chronoNotificationRunning = false; initialCloseLiveChat = true; if (USER_CONFIG.preventBackgroundExecution) { await awaitVisibility(); } if (USER_CONFIG.videosHideWatchedGlobalJS !== 0 && !USER_CONFIG.videosHideWatchedGlobal) markWatchedVideos(); // wait for targets const tar = isWatchPage ? videoTargets : browseTargets; const sel = isWatchPage ? videoPageContainer : browseContainer; let ctn = document.querySelector(sel); if (!ctn) { const pm = document.getElementById('page-manager'); ctn = await new Promise(resolve => { const pageManagerObserver = new MutationObserver(() => { const visible = document.querySelector(sel); if (!visible) return; pageManagerObserver.disconnect(); requestAnimationFrame(() => requestAnimationFrame(() => resolve(visible))); }); for (const visible of pm.children) pageManagerObserver.observe(visible, { attributes: true, attributeFilter: ['hidden'] }); }); } const init = () => { if (pageObserver) { pageObserver.disconnect(); pageObserver = null; } initialRun ? setTimeout(initializeAlchemy, 50) : setTimeout(() => requestIdleCallback(initializeAlchemy, { timeout: 1900 }), 100); }; const t = setTimeout(init, 5000); let remaining = new Set(); for (const s of tar) if (!ctn.querySelector(s)) remaining.add(s); if (!remaining.size) { clearTimeout(t); init(); return; } pageObserver = new MutationObserver(rs => { for (const r of rs) for (const n of r.addedNodes) { if (n.nodeType !== 1) continue; for (const s of remaining) if (n.matches(s) || n.querySelector(s)) remaining.delete(s); if (!remaining.size) { clearTimeout(t); init(); return; } } }); pageObserver.observe(ctn, { childList: true, subtree: true }); } } // helper to determine the location and trigger CSS const updateLocation = () => { const xinURL = window.location.href; if (xinURL !== xianURL) { xianURL = xinURL; const urlObj = new URL(xinURL); const pn = urlObj.pathname; const sp = urlObj.searchParams; isHomePage = pn === '/'; isVideoPage = pn === '/watch'; isSearchPage = pn === '/results'; isPlaylistPage = pn === '/playlist'; isPlaylistVideoPage = sp.has('list'); isLiveStream = pn.startsWith('/live/'); isWatchLater = sp.get('list') === 'WL'; isShortPage = pn.startsWith('/shorts/'); isWatchPage = isVideoPage || isLiveStream; isSubscriptionsPage = pn === '/feed/subscriptions'; isChannelPage = /^(\/@[^/]+|\/channel\/[a-zA-Z0-9_\-=.]+)/.test(pn); isChannelHome = /^(\/@[^/]+|\/channel\/[a-zA-Z0-9_\-=.]+)$/.test(pn); if (isChannelPage) channelHandleURL = pn.match(/^\/@([^/]+)/)?.[1]; if (isChannelHome && USER_CONFIG.defaultChannelPage !== 'home') channelRedirect(); if (isShortPage && USER_CONFIG.redirectShorts) redirectShortsToVideoPage(); if (isWatchPage || isShortPage) { lastVideoID = videoID; videoID = sp.get('v'); if (isLiveStream || isShortPage) videoID = pn.split('/').pop(); if (USER_CONFIG.closeChatWindow) docElement.classList.add('CentAnni-close-live-chat'); } // toggle CSS based on current page docElement.classList.toggle('is-watch-page', isWatchPage); docElement.classList.toggle('yt-watch-later', isWatchLater); for (const [flag, entry] of pageClassEntries) docElement.classList.toggle(entry.class, entry.pages()); locationUpdated = true; } }; // pause script until tab becomes visible function awaitVisibility() { if (document.visibilityState === 'visible') return Promise.resolve(); return new Promise(resolve => { const onVisibility = () => { if (document.visibilityState === 'visible') { document.removeEventListener('visibilitychange', onVisibility); resolve(); } }; document.addEventListener('visibilitychange', onVisibility); }); } // event listeners updateLocation(); document.addEventListener('yt-guide-toggle', guideCheck); // YT Guide CSS document.addEventListener('yt-update-title', updateLocation); // page CSS document.addEventListener('yt-page-type-changed', updateLocation); // backup document.addEventListener('yt-navigate-start', handleYTNavigation); // reset document.addEventListener('yt-navigate-finish', handleYouTubeNavigation); // default document.addEventListener('yt-page-data-updated', handleYouTubeNavigation); // backup document.addEventListener('yt-page-data-fetched', handleYouTubeNavigation); // backup if (USER_CONFIG.playbackSpeed) document.addEventListener('yt-player-updated', initialSpeed); // set playback speed if (USER_CONFIG.preventAutoplay) { document.addEventListener('yt-player-updated', pauseYouTubeVideo); window.addEventListener('load', pauseYouTubeVideo); } // prevent autoplay if (USER_CONFIG.chronologicalNotifications) { document.addEventListener('yt-update-unseen-notification-count', () => setTimeout(() => requestIdleCallback(chronoNotifications, { timeout: 250 }), 100)); } // sort notifications chronologically })();