YouTube - Force rounded corners + tweaks included

This script forces the rounded version of the layout (which includes some fewer tweaks applied which also improves bugs).

  1. // ==UserScript==
  2. // @name YouTube - Force rounded corners + tweaks included
  3. // @version 2025.02.10
  4. // @description This script forces the rounded version of the layout (which includes some fewer tweaks applied which also improves bugs).
  5. // @author Joey_JTS (original author: Magma_Craft)
  6. // @license MIT
  7. // @match *://www.youtube.com/*
  8. // @namespace https://greasyfork.org/en/users/933798
  9. // @icon https://www.youtube.com/favicon.ico
  10. // @run-at document-start
  11. // @grant none
  12. // ==/UserScript==
  13. // Attributes to remove from <html>
  14. const ATTRS = [
  15. "darker-dark-theme",
  16. "darker-dark-theme-deprecate"
  17. ];
  18. // Regular config keys.
  19. const CONFIGS = {
  20. BUTTON_REWORK: true
  21. }
  22. // Experiment flags.
  23. const EXPFLAGS = {
  24. /* Force rounded corners */
  25. web_button_rework: true,
  26. web_button_rework_with_live: true,
  27. web_darker_dark_theme: true,
  28. web_filled_subscribed_button: true,
  29. web_guide_ui_refresh: true,
  30. web_modern_ads: true,
  31. web_modern_buttons: true,
  32. web_modern_chips: true,
  33. web_modern_dialogs: true,
  34. web_modern_playlists: true,
  35. web_modern_subscribe: true,
  36. web_rounded_containers: true,
  37. web_rounded_thumbnails: true,
  38. web_searchbar_style: "rounded_corner_borders_light_btn",
  39. web_segmented_like_dislike_button: true,
  40. web_sheets_ui_refresh: true,
  41. web_snackbar_ui_refresh: true,
  42. /* Force rounded watch layout and few tweaks to be included (such as disabling the useless 'watch grid' UI */
  43. kevlar_watch_metadata_refresh: true,
  44. kevlar_watch_metadata_refresh_attached_subscribe: true,
  45. kevlar_watch_metadata_refresh_clickable_description: true,
  46. kevlar_watch_metadata_refresh_compact_view_count: true,
  47. kevlar_watch_metadata_refresh_description_info_dedicated_line: true,
  48. kevlar_watch_metadata_refresh_description_inline_expander: true,
  49. kevlar_watch_metadata_refresh_description_primary_color: true,
  50. kevlar_watch_metadata_refresh_for_live_killswitch: true,
  51. kevlar_watch_metadata_refresh_full_width_description: true,
  52. kevlar_watch_metadata_refresh_narrower_item_wrap: true,
  53. kevlar_watch_metadata_refresh_relative_date: true,
  54. kevlar_watch_metadata_refresh_top_aligned_actions: true,
  55. kevlar_watch_modern_metapanel: true,
  56. kevlar_watch_modern_panels: true,
  57. kevlar_watch_panel_height_matches_player: true,
  58. kevlar_watch_grid: false,
  59. kevlar_watch_grid_hide_chips: false,
  60. small_avatars_for_comments: false,
  61. small_avatars_for_comments_ep: false,
  62. web_watch_compact_comments: false,
  63. web_watch_compact_comments_ep: false,
  64. web_watch_theater_chat: false,
  65. web_watch_theater_fixed_chat: false,
  66. live_chat_over_engagement_panels: false,
  67. live_chat_scaled_height: false,
  68. live_chat_smaller_min_height: false,
  69. wn_grid_max_item_width: 0,
  70. wn_grid_min_item_width: 0,
  71. kevlar_set_internal_player_size: false,
  72. kevlar_watch_flexy_metadata_height: "136",
  73. kevlar_watch_max_player_width: "1280",
  74. web_watch_rounded_player_large: false,
  75. kevlar_watch_cinematics: false,
  76. desktop_delay_player_resizing: false,
  77. /* Additional tweaks (which includes reverting new UI changes and disabling animations except for both web_modern_tabs and web_enable_youtab configs ) */
  78. kevlar_refresh_on_theme_change: false,
  79. smartimation_background: false,
  80. web_animated_actions: false,
  81. web_animated_like: false,
  82. web_animated_like_lazy_load: false,
  83. enable_channel_page_header_profile_section: false,
  84. kevlar_modern_sd_v2: false,
  85. web_modern_collections_v2: false,
  86. web_modern_tabs: false,
  87. web_modern_typography: true,
  88. web_enable_youtab: true
  89. }
  90. // Player flags
  91. // !!! USE STRINGS FOR VALUES !!!
  92. // For example: "true" instead of true
  93. const PLYRFLAGS = {
  94. web_rounded_containers: "true",
  95. web_rounded_thumbnails: "true"
  96. }
  97. class YTP {
  98. static observer = new MutationObserver(this.onNewScript);
  99. static _config = {};
  100. static isObject(item) {
  101. return (item && typeof item === "object" && !Array.isArray(item));
  102. }
  103. static mergeDeep(target, ...sources) {
  104. if (!sources.length) return target;
  105. const source = sources.shift();
  106. if (this.isObject(target) && this.isObject(source)) {
  107. for (const key in source) {
  108. if (this.isObject(source[key])) {
  109. if (!target[key]) Object.assign(target, { [key]: {} });
  110. this.mergeDeep(target[key], source[key]);
  111. } else {
  112. Object.assign(target, { [key]: source[key] });
  113. }
  114. }
  115. }
  116. return this.mergeDeep(target, ...sources);
  117. }
  118. static onNewScript(mutations) {
  119. for (var mut of mutations) {
  120. for (var node of mut.addedNodes) {
  121. YTP.bruteforce();
  122. }
  123. }
  124. }
  125. static start() {
  126. this.observer.observe(document, {childList: true, subtree: true});
  127. }
  128. static stop() {
  129. this.observer.disconnect();
  130. }
  131. static bruteforce() {
  132. if (!window.yt) return;
  133. if (!window.yt.config_) return;
  134. this.mergeDeep(window.yt.config_, this._config);
  135. }
  136. static setCfg(name, value) {
  137. this._config[name] = value;
  138. }
  139. static setCfgMulti(configs) {
  140. this.mergeDeep(this._config, configs);
  141. }
  142. static setExp(name, value) {
  143. if (!("EXPERIMENT_FLAGS" in this._config)) this._config.EXPERIMENT_FLAGS = {};
  144. this._config.EXPERIMENT_FLAGS[name] = value;
  145. }
  146. static setExpMulti(exps) {
  147. if (!("EXPERIMENT_FLAGS" in this._config)) this._config.EXPERIMENT_FLAGS = {};
  148. this.mergeDeep(this._config.EXPERIMENT_FLAGS, exps);
  149. }
  150. static decodePlyrFlags(flags) {
  151. var obj = {},
  152. dflags = flags.split("&");
  153. for (var i = 0; i < dflags.length; i++) {
  154. var dflag = dflags[i].split("=");
  155. obj[dflag[0]] = dflag[1];
  156. }
  157. return obj;
  158. }
  159. static encodePlyrFlags(flags) {
  160. var keys = Object.keys(flags),
  161. response = "";
  162. for (var i = 0; i < keys.length; i++) {
  163. if (i > 0) {
  164. response += "&";
  165. }
  166. response += keys[i] + "=" + flags[keys[i]];
  167. }
  168. return response;
  169. }
  170. static setPlyrFlags(flags) {
  171. if (!window.yt) return;
  172. if (!window.yt.config_) return;
  173. if (!window.yt.config_.WEB_PLAYER_CONTEXT_CONFIGS) return;
  174. var conCfgs = window.yt.config_.WEB_PLAYER_CONTEXT_CONFIGS;
  175. if (!("WEB_PLAYER_CONTEXT_CONFIGS" in this._config)) this._config.WEB_PLAYER_CONTEXT_CONFIGS = {};
  176. for (var cfg in conCfgs) {
  177. var dflags = this.decodePlyrFlags(conCfgs[cfg].serializedExperimentFlags);
  178. this.mergeDeep(dflags, flags);
  179. this._config.WEB_PLAYER_CONTEXT_CONFIGS[cfg] = {
  180. serializedExperimentFlags: this.encodePlyrFlags(dflags)
  181. }
  182. }
  183. }
  184. }
  185. window.addEventListener("yt-page-data-updated", function tmp() {
  186. YTP.stop();
  187. for (i = 0; i < ATTRS.length; i++) {
  188. document.getElementsByTagName("html")[0].removeAttribute(ATTRS[i]);
  189. }
  190. window.removeEventListener("yt-page-date-updated", tmp);
  191. });
  192. YTP.start();
  193. YTP.setCfgMulti(CONFIGS);
  194. YTP.setExpMulti(EXPFLAGS);
  195. YTP.setPlyrFlags(PLYRFLAGS);
  196. function $(q) {
  197. return document.querySelector(q);
  198. }
  199. (function() {
  200. let css = `
  201. /* Add rounded corners under the player */
  202. div#ytp-id-17.ytp-popup.ytp-settings-menu,
  203. div#ytp-id-18.ytp-popup.ytp-settings-menu {
  204. border-radius: 12px !important
  205. }
  206. div.branding-context-container-inner.ytp-rounded-branding-context {
  207. border-radius: 8px !important
  208. }
  209. .iv-card {
  210. border-radius: 8px !important
  211. }
  212. .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-overlay-image img, .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-text-overlay, .ytp-ad-overlay-container.ytp-overlay-ad .ytp-ad-enhanced-overlay {
  213. border-radius: 8px !important
  214. }
  215. .ytp-tooltip.ytp-text-detail.ytp-preview .ytp-tooltip-bg {
  216. border-top-left-radius: 12px !important;
  217. border-bottom-left-radius: 12px !important
  218. }
  219. .ytp-tooltip.ytp-text-detail.ytp-preview {
  220. border-radius: 12px !important
  221. }
  222. .ytp-ce-video.ytp-ce-medium, .ytp-ce-playlist.ytp-ce-medium, .ytp-ce-medium .ytp-ce-expanding-overlay-background {
  223. border-radius: 8px !important
  224. }
  225. .ytp-autonav-endscreen-upnext-thumbnail {
  226. border-radius: 8px !important
  227. }
  228. .ytp-autonav-endscreen-upnext-button {
  229. border-radius: 18px !important
  230. }
  231. .ytp-videowall-still-image {
  232. border-radius: 8px !important
  233. }
  234. .ytp-sb-subscribe, .ytp-sb-unsubscribe {
  235. border-radius: 18px !important
  236. }
  237. /* Watch page tweaks (including the 'Revert video list' CSS) */
  238. ytd-watch-flexy[rounded-player-large]:not([fullscreen]):not([theater]) #ytd-player.ytd-watch-flexy {
  239. border-radius: 0px !important
  240. }
  241. #actions.ytd-watch-metadata {
  242. min-width: auto !important
  243. }
  244. ytd-watch-flexy[default-layout][reduced-top-margin] #primary.ytd-watch-flexy, ytd-watch-flexy[default-layout][reduced-top-margin] #secondary.ytd-watch-flexy {
  245. padding-top: var(--ytd-margin-6x) !important
  246. }
  247. ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata, ytd-watch-metadata[title-headline-m] h1.ytd-watch-metadata {
  248. font-family: "YouTube Sans","Roboto",sans-serif !important;
  249. font-weight: 600 !important;
  250. font-size: 2rem !important;
  251. line-height: 2.8rem !important
  252. }
  253.  
  254. ytd-comments-header-renderer[compact-header] #title.ytd-comments-header-renderer {
  255. margin-bottom: 24px !important
  256. }
  257.  
  258. ytd-comments-header-renderer[modern-typography][compact-header] .count-text.ytd-comments-header-renderer {
  259. font-size: 2rem !important;
  260. line-height: 2.8rem !important;
  261. font-weight: 700 !important;
  262. max-height: 2.8rem !important;
  263. display: flex !important;
  264. flex-direction: row-reverse !important
  265. }
  266.  
  267. [compact-header] .count-text.ytd-comments-header-renderer {
  268. display: flex !important;
  269. flex-direction: row-reverse !important
  270. }
  271.  
  272. [compact-header] .count-text.ytd-comments-header-renderer span {
  273. margin-right: 6px !important
  274. }
  275. ytd-watch-flexy #comment-teaser.ytd-watch-metadata {
  276. display: none
  277. }
  278.  
  279. ytd-watch-flexy ytd-rich-item-renderer[rendered-from-rich-grid] {
  280. --ytd-rich-item-row-usable-width: 100% !important
  281. }
  282.  
  283. ytd-watch-flexy ytd-rich-item-renderer[rendered-from-rich-grid][is-in-first-column] {
  284. margin-left: 0
  285. }
  286.  
  287. ytd-watch-flexy ytd-rich-item-renderer ytd-menu-renderer .ytd-menu-renderer[style-target=button] {
  288. width: 24px !important;
  289. height: 24px !important
  290. }
  291.  
  292. ytd-watch-flexy #dismissible.ytd-rich-grid-media {
  293. flex-direction: row
  294. }
  295. ytd-watch-flexy #attached-survey.ytd-rich-grid-media,
  296. ytd-watch-flexy #avatar-link.ytd-rich-grid-media,
  297. ytd-watch-flexy #avatar-container.ytd-rich-grid-media {
  298. display: none
  299. }
  300. ytd-watch-flexy ytd-thumbnail.ytd-rich-grid-media,
  301. ytd-watch-flexy ytd-playlist-thumbnail.ytd-rich-grid-media {
  302. margin-right: 8px;
  303. height: 94px;
  304. width: 168px
  305. }
  306. ytd-watch-flexy ytd-thumbnail[size=large] a.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large]:before,
  307. ytd-watch-flexy ytd-thumbnail[size=large][large-margin] a.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin]:before {
  308. border-radius: 8px
  309. }
  310. ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large][large-margin] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail,
  311. ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-button-renderer.ytd-thumbnail, ytd-watch-flexy ytd-thumbnail[size=large] ytd-thumbnail-overlay-toggle-button-renderer.ytd-thumbnail {
  312. margin: 4px
  313. }
  314. ytd-watch-flexy ytd-rich-item-renderer,
  315. ytd-watch-flexy ytd-rich-grid-row #contents.ytd-rich-grid-row {
  316. margin: 0
  317. }
  318. ytd-watch-flexy ytd-rich-item-renderer[reduced-bottom-margin] {
  319. margin-top: 8px;
  320. margin-bottom: 0
  321. }
  322. ytd-watch-flexy ytd-rich-grid-renderer[reduced-top-margin] #contents.ytd-rich-grid-renderer {
  323. padding-top: 0px
  324. }
  325. ytd-watch-flexy ytd-rich-grid-media {
  326. margin-bottom: 8px
  327. }
  328. ytd-watch-flexy #details.ytd-rich-grid-media {
  329. width: 100%;
  330. min-width: 0
  331. }
  332. ytd-watch-flexy ytd-video-meta-block[rich-meta] #metadata-line.ytd-video-meta-block,
  333. ytd-watch-flexy #channel-name.ytd-video-meta-block {
  334. font-family: "Roboto", "Arial", sans-serif;
  335. font-size: 1.2rem;
  336. line-height: 1.8rem;
  337. font-weight: 400
  338. }
  339. ytd-watch-flexy #video-title.ytd-rich-grid-media {
  340. margin: 0 0 4px 0;
  341. display: block;
  342. font-family: "Roboto", "Arial", sans-serif;
  343. font-size: 1.4rem;
  344. line-height: 2rem;
  345. font-weight: 500;
  346. overflow: hidden;
  347. display: block;
  348. max-height: 4rem;
  349. -webkit-line-clamp: 2;
  350. display: box;
  351. display: -webkit-box;
  352. -webkit-box-orient: vertical;
  353. text-overflow: ellipsis;
  354. white-space: normal
  355. }
  356. ytd-watch-flexy h3.ytd-rich-grid-media {
  357. margin: 0
  358. }
  359. ytd-watch-flexy .title-badge.ytd-rich-grid-media, ytd-watch-flexy .video-badge.ytd-rich-grid-media {
  360. margin-top: 0
  361. }
  362. ytd-watch-flexy ytd-rich-section-renderer.style-scope.ytd-rich-grid-renderer {
  363. display: none
  364. }
  365. ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar] ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer, ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar-on-watch] ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer, ytd-watch-flexy ytd-rich-grid-renderer[hide-chips-bar-on-home] #header.ytd-rich-grid-renderer ytd-feed-filter-chip-bar-renderer.ytd-rich-grid-renderer {
  366. display: flex;
  367. height: 51px;
  368. margin-bottom: 8px
  369. }
  370. ytd-watch-flexy #chips-wrapper.ytd-feed-filter-chip-bar-renderer {
  371. position: relative;
  372. top: 0
  373. }
  374. ytd-watch-flexy ytd-feed-filter-chip-bar-renderer[fluid-width] #chips-content.ytd-feed-filter-chip-bar-renderer {
  375. padding: 0
  376. }
  377. ytd-watch-flexy yt-chip-cloud-chip-renderer.ytd-feed-filter-chip-bar-renderer, ytd-watch-flexy yt-chip-cloud-chip-renderer.ytd-feed-filter-chip-bar-renderer:first-of-type {
  378. margin: 8px;
  379. margin-left: 0
  380. }
  381. ytd-watch-flexy ytd-button-renderer.ytd-feed-filter-chip-bar-renderer {
  382. margin: 0;
  383. padding: 0 8px
  384. }
  385. /* More tweaks to be applied */
  386. #buttons.ytd-c4-tabbed-header-renderer {
  387. flex-direction: row-reverse !important
  388. }
  389.  
  390. ytd-channel-tagline-renderer {
  391. display: block !important;
  392. padding: 0 !important
  393. }
  394.  
  395. #content.ytd-channel-tagline-renderer::before {
  396. content: "More about this channel";
  397. font-weight: 500 !important
  398. }
  399.  
  400. #content.ytd-channel-tagline-renderer {
  401. max-width: 162px !important
  402. }
  403.  
  404. ytd-browse[page-subtype="channels"] .page-header-view-model-wiz__page-header-description {
  405. margin-top: 0px !important;
  406. max-width: 236px !important
  407. }
  408.  
  409. ytd-browse[page-subtype="channels"] yt-description-preview-view-model .truncated-text-wiz__truncated-text-content:before {
  410. content: "More about this channel >   ";
  411. font-weight: 500 !important
  412. }
  413.  
  414. ytd-browse[page-subtype="channels"] button.truncated-text-wiz__absolute-button {
  415. display: none !important
  416. }
  417.  
  418. #avatar.ytd-c4-tabbed-header-renderer, .yt-spec-avatar-shape__button--button-giant {
  419. width: 80px !important;
  420. height: 80px !important;
  421. margin: 0 24px 0 0 !important;
  422. flex: none !important;
  423. overflow: hidden !important
  424. }
  425.  
  426. .yt-spec-avatar-shape__button--button-giant, .yt-spec-avatar-shape--avatar-size-giant, .yt-spec-avatar-shape__button--button-extra-extra-large, .yt-spec-avatar-shape--avatar-size-extra-extra-large {
  427. width: 80px !important;
  428. height: 80px !important;
  429. margin-right: 0px !important;
  430. }
  431.  
  432. #avatar-editor.ytd-c4-tabbed-header-renderer {
  433. --ytd-channel-avatar-editor-size: 80px !important
  434. }
  435.  
  436. #channel-name.ytd-c4-tabbed-header-renderer {
  437. margin-bottom: 0 !important
  438. }
  439.  
  440. #channel-header-container.ytd-c4-tabbed-header-renderer {
  441. padding-top: 0 !important;
  442. align-items: center !important
  443. }
  444.  
  445. #inner-header-container.ytd-c4-tabbed-header-renderer {
  446. margin-top: 0 !important;
  447. align-items: center !important
  448. }
  449.  
  450. .yt-content-metadata-view-model-wiz--inline .yt-content-metadata-view-model-wiz__metadata-row {
  451. margin-top: 0 !important
  452. }
  453.  
  454. yt-formatted-string#channel-pronouns.style-scope.ytd-c4-tabbed-header-renderer, #videos-count {
  455. display: none !important
  456. }
  457.  
  458. .meta-item.ytd-c4-tabbed-header-renderer {
  459. display: block !important
  460. }
  461.  
  462. div#channel-header-links.style-scope.ytd-c4-tabbed-header-renderer,
  463. .page-header-view-model-wiz__page-header-attribution {
  464. display: none !important
  465. }
  466.  
  467. ytd-c4-tabbed-header-renderer[use-page-header-style] #channel-name.ytd-c4-tabbed-header-renderer,
  468. [page-subtype="channels"] .page-header-view-model-wiz__page-header-title--page-header-title-large {
  469. font-size: 2.4em !important;
  470. font-weight: 400 !important;
  471. line-height: var(--yt-channel-title-line-height, 3rem) !important;
  472. margin: 0 !important
  473. }
  474.  
  475. span.delimiter.style-scope.ytd-c4-tabbed-header-renderer, .yt-content-metadata-view-model-wiz__delimiter {
  476. display: none !important
  477. }
  478.  
  479. div#meta.style-scope.ytd-c4-tabbed-header-renderer {
  480. width: auto !important
  481. }
  482.  
  483. ytd-c4-tabbed-header-renderer[use-page-header-style] #inner-header-container.ytd-c4-tabbed-header-renderer {
  484. flex-direction: row !important
  485. }
  486.  
  487. div.page-header-banner.style-scope.ytd-c4-tabbed-header-renderer {
  488. margin-left: 0px !important;
  489. margin-right: 8px !important;
  490. border-radius: 0px !important
  491. }
  492.  
  493. [has-inset-banner] #page-header-banner.ytd-tabbed-page-header {
  494. padding-left: 0 !important;
  495. padding-right: 0 !important
  496. }
  497.  
  498. ytd-c4-tabbed-header-renderer[use-page-header-style] .page-header-banner.ytd-c4-tabbed-header-renderer,
  499. .yt-image-banner-view-model-wiz--inset {
  500. border-radius: 0px !important
  501. }
  502.  
  503. .yt-content-metadata-view-model-wiz__metadata-text {
  504. margin-right: 8px !important
  505. }
  506.  
  507. .yt-content-metadata-view-model-wiz__metadata-text, .truncated-text-wiz, .truncated-text-wiz__absolute-button {
  508. font-size: 1.4rem !important
  509. }
  510.  
  511. .yt-tab-shape-wiz {
  512. padding: 0 32px !important;
  513. margin-right: 0 !important
  514. }
  515.  
  516. .yt-tab-shape-wiz__tab {
  517. font-size: 14px !important;
  518. font-weight: 500 !important;
  519. letter-spacing: var(--ytd-tab-system-letter-spacing) !important;
  520. text-transform: uppercase !important
  521. }
  522.  
  523. .yt-tab-group-shape-wiz__slider {
  524. display: none !important
  525. }
  526.  
  527. ytd-browse[page-subtype="channels"] ytd-tabbed-page-header .yt-content-metadata-view-model-wiz__metadata-row--metadata-row-inline {
  528. display: flex
  529. }
  530.  
  531. ytd-browse[page-subtype="channels"] ytd-tabbed-page-header .yt-content-metadata-view-model-wiz__metadata-text:last-of-type {
  532. display: none
  533. }
  534.  
  535. ytd-browse[page-subtype="channels"] ytd-tabbed-page-header .yt-content-metadata-view-model-wiz__metadata-text:first-of-type {
  536. display: flex
  537. }
  538.  
  539. ytd-browse[page-subtype="channels"] .yt-flexible-actions-view-model-wiz--inline {
  540. flex-direction: row-reverse
  541. }
  542.  
  543. ytd-browse[page-subtype="channels"] .page-header-view-model-wiz__page-header-flexible-actions {
  544. margin-top: -56px
  545. }
  546.  
  547. ytd-browse[page-subtype="channels"] .yt-flexible-actions-view-model-wiz__action-row {
  548. margin-top: 60px
  549. }
  550.  
  551. ytd-browse[page-subtype="channels"] .yt-flexible-actions-view-model-wiz__action {
  552. padding-right: 8px
  553. }
  554.  
  555. ytd-browse[page-subtype="channels"] span.yt-core-attributed-string--link-inherit-color {
  556. font-weight: 400 !important
  557. }
  558.  
  559. ytd-browse[page-subtype="channels"] .page-header-view-model-wiz__page-header-headline-info {
  560. margin-bottom: 8px
  561. }
  562.  
  563. #title.ytd-playlist-sidebar-primary-info-renderer,
  564. ytd-inline-form-renderer[component-style=INLINE_FORM_STYLE_TITLE] #text-displayed.ytd-inline-form-renderer {
  565. font-family: YouTube Sans !important;
  566. font-weight: 700 !important
  567. }
  568. ytd-comments-header-renderer[use-space-between] #title.ytd-comments-header-renderer {
  569. justify-content: start !important
  570. }
  571. #panel-button.ytd-comments-header-renderer {
  572. margin-left: 32px;
  573. margin-right: 8px
  574. }
  575. #panel-button .yt-spec-button-shape-next__icon {
  576. margin-right: 0
  577. }
  578. #panel-button .yt-spec-button-shape-next--size-m {
  579. padding-left: 12px;
  580. padding-right: 6px
  581. }
  582. #panel-button .yt-spec-button-shape-next__button-text-content {
  583. display: none !important
  584. }
  585. #panel-button .yt-spec-button-shape-next__icon path {
  586. d: path("M10 3H17V7H10V3ZM20 0H0V14H20V0ZM1 1H19V13H1V1Z");
  587. transform: scale(1.20)
  588. }
  589. div#end.style-scope.ytd-masthead .yt-spec-button-shape-next--size-m[aria-label="Create"] {
  590. height: 40px !important;
  591. border-radius: 50px !important;
  592. color: var(--yt-spec-icon-active-other) !important;
  593. background-color: transparent !important
  594. }
  595. div#end.style-scope.ytd-masthead .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-leading[aria-label="Create"] .yt-spec-button-shape-next__button-text-content {
  596. display: none !important
  597. }
  598. div#end.style-scope.ytd-masthead .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-leading[aria-label="Create"] .yt-spec-button-shape-next__icon {
  599. margin-left: -8px !important;
  600. margin-right: -8px !important
  601. }
  602. div#end.style-scope.ytd-masthead .yt-spec-button-shape-next--size-m.yt-spec-button-shape-next--icon-leading[aria-label="Create"] path {
  603. d: path("M14 13h-3v3H9v-3H6v-2h3V8h2v3h3v2zm3-7H3v12h14v-6.39l4 1.83V8.56l-4 1.83V6m1-1v3.83L22 7v8l-4-1.83V19H2V5h16z")
  604. }
  605.  
  606. div#end.style-scope.ytd-masthead .yt-spec-icon-badge-shape--style-overlay.yt-spec-icon-badge-shape--type-cart-refresh .yt-spec-icon-badge-shape__badge {
  607. color: #fff !important
  608. }
  609.  
  610. ytd-feed-filter-chip-bar-renderer[frosted-glass] ytd-button-renderer.ytd-feed-filter-chip-bar-renderer {
  611. background-color: transparent !important
  612. }
  613.  
  614. ytd-feed-filter-chip-bar-renderer[frosted-glass] #left-arrow-button.ytd-feed-filter-chip-bar-renderer,
  615. ytd-feed-filter-chip-bar-renderer[frosted-glass] #right-arrow-button.ytd-feed-filter-chip-bar-renderer {
  616. background-color: var(--yt-spec-base-background) !important
  617. }
  618.  
  619. ytd-feed-filter-chip-bar-renderer[frosted-glass] #left-arrow.ytd-feed-filter-chip-bar-renderer:after {
  620. background: linear-gradient(to right, var(--yt-spec-base-background) 20%, rgba(255, 255, 255, 0) 80%) !important
  621. }
  622.  
  623. ytd-feed-filter-chip-bar-renderer[frosted-glass] #right-arrow.ytd-feed-filter-chip-bar-renderer:before {
  624. background: linear-gradient(to left, var(--yt-spec-base-background) 20%, rgba(255, 255, 255, 0) 80%) !important
  625. }
  626. ytd-masthead[frosted-glass=with-chipbar] #background.ytd-masthead,
  627. ytd-masthead[frosted-glass=without-chipbar] #background.ytd-masthead,
  628. ytd-app[frosted-glass-mode=with-chipbar] #frosted-glass.ytd-app,
  629. ytd-app[frosted-glass-mode=without-chipbar] #frosted-glass.ytd-app {
  630. background: var(--yt-spec-base-background) !important;
  631. backdrop-filter: none !important
  632. }
  633.  
  634. .ytp-cairo-refresh-signature-moments .ytp-play-progress, ytd-thumbnail-overlay-resume-playback-renderer[enable-refresh-signature-moments-web] #progress.ytd-thumbnail-overlay-resume-playback-renderer, .YtThumbnailOverlayProgressBarHostWatchedProgressBarSegmentModern, .YtChapteredProgressBarChapteredPlayerBarChapterRefresh, .YtChapteredProgressBarChapteredPlayerBarFillRefresh, .YtProgressBarLineProgressBarPlayedRefresh, yt-page-navigation-progress[enable-refresh-signature-moments-web] #progress.yt-page-navigation-progress, ytd-progress-bar-line[enable-refresh-signature-moments-web] .progress-bar-played.ytd-progress-bar-line, #logo-icon > .yt-spec-icon-shape.yt-icon.style-scope.yt-icon-shape > div > svg > g:first-of-type > path:first-of-type {
  635. background: #ff0000 !important
  636. }
  637.  
  638. [d*="M18 4v15.06l-5.42-3.87-.58-.42-.58.42L6 19.06V4h12m1-1H5v18l7-5 7 5V3z"] {
  639. d: path("M22 13h-4v4h-2v-4h-4v-2h4V7h2v4h4v2zm-8-6H2v1h12V7zM2 12h8v-1H2v1zm0 4h8v-1H2v1z")
  640. }`;
  641. if (typeof GM_addStyle !== "undefined") {
  642. GM_addStyle(css);
  643. } else {
  644. let styleNode = document.createElement("style");
  645. styleNode.appendChild(document.createTextNode(css));
  646. (document.querySelector("head") || document.documentElement).appendChild(styleNode);
  647. }
  648. })();
  649. // Integrate 'YouTube Video Resize Fix' script (special thanks to CY Fung)
  650. /* jshint esversion:8 */
  651. ((__CONTEXT01__) => {
  652. 'use strict';
  653. const win = this instanceof Window ? this : window;
  654. // Create a unique key for the script and check if it is already running
  655. const hkey_script = 'ahceihvpbosz';
  656. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  657. win[hkey_script] = true;
  658. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  659. const indr = o => insp(o).$ || o.$ || 0;
  660. /** @type {globalThis.PromiseConstructor} */
  661. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  662. const cleanContext = async (win) => {
  663. const waitFn = requestAnimationFrame; // shall have been binded to window
  664. try {
  665. let mx = 16; // MAX TRIAL
  666. const frameId = 'vanillajs-iframe-v1'
  667. let frame = document.getElementById(frameId);
  668. let removeIframeFn = null;
  669. if (!frame) {
  670. frame = document.createElement('iframe');
  671. frame.id = frameId;
  672. const blobURL = typeof webkitCancelAnimationFrame === 'function' && typeof kagi === 'undefined' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
  673. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  674. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  675. n.appendChild(frame);
  676. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  677. const root = document.documentElement;
  678. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  679. if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));
  680. removeIframeFn = (setTimeout) => {
  681. const removeIframeOnDocumentReady = (e) => {
  682. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  683. e = n;
  684. n = win = removeIframeFn = 0;
  685. setTimeout ? setTimeout(() => e.remove(), 200) : e.remove();
  686. }
  687. if (!setTimeout || document.readyState !== 'loading') {
  688. removeIframeOnDocumentReady();
  689. } else {
  690. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  691. }
  692. }
  693. }
  694. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  695. const fc = frame.contentWindow;
  696. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  697. try {
  698. const { requestAnimationFrame, setTimeout, clearTimeout } = fc;
  699. const res = { requestAnimationFrame, setTimeout, clearTimeout };
  700. for (let k in res) res[k] = res[k].bind(win); // necessary
  701. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  702. return res;
  703. } catch (e) {
  704. if (removeIframeFn) removeIframeFn();
  705. return null;
  706. }
  707. } catch (e) {
  708. console.warn(e);
  709. return null;
  710. }
  711. };
  712. const isWatchPageURL = (url) => {
  713. url = url || location;
  714. return location.pathname === '/watch' || location.pathname.startsWith('/live/')
  715. };
  716. cleanContext(win).then(__CONTEXT02__ => {
  717. if (!__CONTEXT02__) return null;
  718. const { ResizeObserver } = __CONTEXT01__;
  719. const { requestAnimationFrame, setTimeout, clearTimeout } = __CONTEXT02__;
  720. const elements = {};
  721. let rid1 = 0;
  722. let rid2 = 0;
  723. /** @type {MutationObserver | null} */
  724. let attrObserver = null;
  725. /** @type {ResizeObserver | null} */
  726. let resizeObserver = null;
  727. let isHTMLAttrApplied = false;
  728. const core = {
  729. begin() {
  730. document.addEventListener('yt-player-updated', core.hanlder, true);
  731. document.addEventListener('ytd-navigate-finish', core.hanlder, true);
  732. },
  733. hanlder: () => {
  734. rid1++;
  735. if (rid1 > 1e9) rid1 = 9;
  736. const tid = rid1;
  737. requestAnimationFrame(() => {
  738. if (tid !== rid1) return;
  739. core.runner();
  740. })
  741. },
  742. async runner() {
  743. if (!location.href.startsWith('https://www.youtube.com/')) return;
  744. if (!isWatchPageURL()) return;
  745. elements.ytdFlexy = document.querySelector('ytd-watch-flexy');
  746. elements.video = document.querySelector('ytd-watch-flexy #movie_player video, ytd-watch-flexy #movie_player audio.video-stream.html5-main-video');
  747. if (elements.ytdFlexy && elements.video) { } else return;
  748. elements.moviePlayer = elements.video.closest('#movie_player');
  749. if (!elements.moviePlayer) return;
  750. // resize Video
  751. let { ytdFlexy } = elements;
  752. if (!ytdFlexy.ElYTL) {
  753. ytdFlexy.ElYTL = 1;
  754. const ytdFlexyCnt = insp(ytdFlexy);
  755. if (typeof ytdFlexyCnt.calculateNormalPlayerSize_ === 'function') {
  756. ytdFlexyCnt.calculateNormalPlayerSize_ = core.resizeFunc(ytdFlexyCnt.calculateNormalPlayerSize_, 1);
  757. } else {
  758. console.warn('ytdFlexyCnt.calculateNormalPlayerSize_ is not a function.')
  759. }
  760. if (typeof ytdFlexyCnt.calculateCurrentPlayerSize_ === 'function') {
  761. ytdFlexyCnt.calculateCurrentPlayerSize_ = core.resizeFunc(ytdFlexyCnt.calculateCurrentPlayerSize_, 0);
  762. } else {
  763. console.warn('ytdFlexyCnt.calculateCurrentPlayerSize_ is not a function.')
  764. }
  765. }
  766. ytdFlexy = null;
  767. // when video is fetched
  768. elements.video.removeEventListener('canplay', core.triggerResizeDelayed, false);
  769. elements.video.addEventListener('canplay', core.triggerResizeDelayed, false);
  770. // when video is resized
  771. if (resizeObserver) {
  772. resizeObserver.disconnect();
  773. resizeObserver = null;
  774. }
  775. if (typeof ResizeObserver === 'function') {
  776. resizeObserver = new ResizeObserver(core.triggerResizeDelayed);
  777. resizeObserver.observe(elements.moviePlayer);
  778. }
  779. // MutationObserver:[collapsed] @ ytd-live-chat-frame#chat
  780. if (attrObserver) {
  781. attrObserver.takeRecords();
  782. attrObserver.disconnect();
  783. attrObserver = null;
  784. }
  785. let chat = document.querySelector('ytd-watch-flexy ytd-live-chat-frame#chat');
  786. if (chat) {
  787. // resize due to DOM update
  788. attrObserver = new MutationObserver(core.triggerResizeDelayed);
  789. attrObserver.observe(chat, { attributes: true, attributeFilter: ["collapsed"] });
  790. chat = null;
  791. }
  792. // resize on idle
  793. Promise.resolve().then(core.triggerResizeDelayed);
  794. },
  795. resizeFunc(originalFunc, kb) {
  796. return function () {
  797. rid2++;
  798. if (!isHTMLAttrApplied) {
  799. isHTMLAttrApplied = true;
  800. Promise.resolve(0).then(() => {
  801. document.documentElement.classList.add('youtube-video-resize-fix');
  802. }).catch(console.warn);
  803. }
  804. if (document.fullscreenElement === null) {
  805. // calculateCurrentPlayerSize_ shall be always return NaN to make correct positioning of toolbars
  806. if (!kb) return { width: NaN, height: NaN };
  807. let ret = core.calculateSize();
  808. if (ret.height > 0 && ret.width > 0) {
  809. return ret;
  810. }
  811. }
  812. return originalFunc.apply(this, arguments);
  813. }
  814. },
  815. calculateSize_() {
  816. const { moviePlayer, video } = elements;
  817. const rect1 = { width: video.videoWidth, height: video.videoHeight }; // native values independent of css rules
  818. if (rect1.width > 0 && rect1.height > 0) {
  819. const rect2 = moviePlayer.getBoundingClientRect();
  820. const aspectRatio = rect1.width / rect1.height;
  821. let h2 = rect2.width / aspectRatio;
  822. let w2 = rect2.height * aspectRatio;
  823. return { rect2, h2, w2 };
  824. }
  825. return null;
  826. },
  827. calculateSize() {
  828. let rs = core.calculateSize_();
  829. if (!rs) return { width: NaN, height: NaN };
  830. const { rect2, h2, w2 } = rs;
  831. if (h2 > rect2.height) {
  832. return { width: w2, height: rect2.height };
  833. } else {
  834. return { width: rect2.width, height: h2 };
  835. }
  836. },
  837. triggerResizeDelayed: () => {
  838. rid2++;
  839. if (rid2 > 1e9) rid2 = 9;
  840. const tid = rid2;
  841. requestAnimationFrame(() => {
  842. if (tid !== rid2) return;
  843. const { ytdFlexy } = elements;
  844. let r = false;
  845. const ytdFlexyCnt = insp(ytdFlexy);
  846. const windowSize_ = ytdFlexyCnt.windowSize_;
  847. if (windowSize_ && typeof ytdFlexyCnt.onWindowResized_ === 'function') {
  848. try {
  849. ytdFlexyCnt.onWindowResized_(windowSize_);
  850. r = true;
  851. } catch (e) { }
  852. }
  853. if (!r) window.dispatchEvent(new Event('resize'));
  854. })
  855. }
  856. };
  857. core.begin();
  858. // YouTube Watch Page Reflect (WPR)
  859. // This script enhances the functionality of YouTube pages by reflecting changes in the page state.
  860. (async function youTubeWPR() {
  861. let checkPageVisibilityChanged = false;
  862. // A WeakSet to keep track of elements being monitored for mutations.
  863. const monitorWeakSet = new WeakSet();
  864. /** @type {globalThis.PromiseConstructor} */
  865. const Promise = (async () => { })().constructor;
  866. // Function to reflect the current state of the YouTube page.
  867. async function _reflect() {
  868. await Promise.resolve();
  869. const youtubeWpr = document.documentElement.getAttribute("youtube-wpr");
  870. let s = '';
  871. // Check if the current page is the video watch page.
  872. if (isWatchPageURL()) {
  873. let watch = document.querySelector("ytd-watch-flexy");
  874. let chat = document.querySelector("ytd-live-chat-frame#chat");
  875. if (watch) {
  876. // Determine the state of the chat and video player on the watch page and generate a state string.
  877. s += !chat ? 'h0' : (chat.hasAttribute('collapsed') || !document.querySelector('iframe#chatframe')) ? 'h1' : 'h2';
  878. s += watch.hasAttribute('is-two-columns_') ? 's' : 'S';
  879. s += watch.hasAttribute('fullscreen') ? 'F' : 'f';
  880. s += watch.hasAttribute('theater') ? 'T' : 't';
  881. }
  882. }
  883. // Update the reflected state if it has changed.
  884. if (s !== youtubeWpr) {
  885. document.documentElement.setAttribute("youtube-wpr", s);
  886. }
  887. }
  888. // Function to reflect changes in specific attributes of monitored elements.
  889. async function reflect(nodeName, attrNames, forced) {
  890. await Promise.resolve();
  891. if (!forced) {
  892. let skip = true;
  893. for (const attrName of attrNames) {
  894. if (nodeName === 'ytd-live-chat-frame') {
  895. if (attrName === 'collapsed') skip = false;
  896. } else if (nodeName === 'ytd-watch-flexy') {
  897. if (attrName === 'is-two-columns_') skip = false;
  898. else if (attrName === 'fullscreen') skip = false;
  899. else if (attrName === 'theater') skip = false;
  900. }
  901. }
  902. if (skip) return;
  903. }
  904. // Log the mutated element and its attributes.
  905. // console.log(nodeName, attrNames);
  906. // Call _reflect() to update the reflected state.
  907. _reflect();
  908. }
  909. // Callback function for the MutationObserver that tracks mutations in monitored elements.
  910. function callback(mutationsList) {
  911. const attrNames = new Set();
  912. let nodeName = null;
  913. for (const mutation of mutationsList) {
  914. if (nodeName === null && mutation.target) nodeName = mutation.target.nodeName.toLowerCase();
  915. attrNames.add(mutation.attributeName);
  916. }
  917. reflect(nodeName, attrNames, false);
  918. }
  919. function getParent(element) {
  920. return element.__shady_native_parentNode || element.__shady_parentNode || element.parentNode;
  921. }
  922. let lastPageTypeChanged = 0;
  923. function chatContainerMutationHandler() {
  924. if (Date.now() - lastPageTypeChanged < 800) _reflect();
  925. }
  926. // Function to start monitoring an element for mutations.
  927. function monitor(element) {
  928. if (!element) return;
  929. if (monitorWeakSet.has(element)) {
  930. return;
  931. }
  932. monitorWeakSet.add(element);
  933. const observer = new MutationObserver(callback);
  934. observer.observe(element, { attributes: true });
  935. if (element.id === 'chat') {
  936. const parentNode = getParent(element);
  937. if (parentNode instanceof Element && parentNode.id === 'chat-container' && !monitorWeakSet.has(parentNode)) {
  938. monitorWeakSet.add(parentNode);
  939. const observer = new MutationObserver(chatContainerMutationHandler);
  940. observer.observe(parentNode, { childList: true, subtree: false });
  941. }
  942. }
  943. return 1;
  944. }
  945. let timeout = 0;
  946. // Function to monitor relevant elements and update the reflected state.
  947. let g = async (forced) => {
  948. await Promise.resolve();
  949. let b = 0;
  950. b = b | monitor(document.querySelector("ytd-watch-flexy"));
  951. b = b | monitor(document.querySelector("ytd-live-chat-frame#chat"));
  952. if (b || forced) {
  953. _reflect();
  954. }
  955. }
  956. // let renderId = 0;
  957. // Event handler function that triggers when the page finishes navigation or page data updates.
  958. let eventHandlerFunc = async (evt) => {
  959. checkPageVisibilityChanged = true;
  960. timeout = Date.now() + 800;
  961. g(1);
  962. if (evt.type === 'yt-navigate-finish') {
  963. // delay required when page type is changed for #chat (home -> watch).
  964. setTimeout(() => {
  965. g(1);
  966. }, 80);
  967. } else if (evt.type === 'yt-page-type-changed') {
  968. lastPageTypeChanged = Date.now();
  969. // setTimeout(() => {
  970. // if (renderId > 1e9) renderId = 9;
  971. // const t = ++renderId;
  972. // requestAnimationFrame(() => {
  973. // if (t !== renderId) return;
  974. // g(1);
  975. // });
  976. // }, 180);
  977. if (typeof requestIdleCallback === 'function') {
  978. requestIdleCallback(() => {
  979. g(1);
  980. });
  981. }
  982. }
  983. }
  984. let loadState = 0;
  985. // Function to initialize the script and start monitoring the page.
  986. async function actor() {
  987. if (loadState === 0) {
  988. if (!document.documentElement.hasAttribute("youtube-wpr")) {
  989. loadState = 1;
  990. document.documentElement.setAttribute("youtube-wpr", "");
  991. document.addEventListener("yt-navigate-finish", eventHandlerFunc, false);
  992. document.addEventListener("yt-page-data-updated", eventHandlerFunc, false);
  993. document.addEventListener("yt-page-type-changed", eventHandlerFunc, false);
  994. } else {
  995. loadState = -1;
  996. document.removeEventListener("yt-page-data-fetched", actor, false);
  997. return;
  998. }
  999. }
  1000. if (loadState === 1) {
  1001. timeout = Date.now() + 800;
  1002. // Function to continuously monitor elements and update the reflected state.
  1003. let pf = () => {
  1004. g(0);
  1005. if (Date.now() < timeout) requestAnimationFrame(pf);
  1006. };
  1007. pf();
  1008. }
  1009. }
  1010. // Event listener that triggers when page data is fetched.
  1011. document.addEventListener("yt-page-data-fetched", actor, false);
  1012. // Update after visibility changed (looks like there are bugs due to inactive tab)
  1013. document.addEventListener('visibilitychange', () => {
  1014. if (document.visibilityState !== 'visible') return;
  1015. if (checkPageVisibilityChanged) {
  1016. checkPageVisibilityChanged = false;
  1017. setTimeout(() => {
  1018. g(1);
  1019. }, 100);
  1020. requestAnimationFrame(() => {
  1021. g(1);
  1022. });
  1023. }
  1024. }, false);
  1025. })();
  1026. });
  1027. })({ ResizeObserver });