xHamster Plus

A kinder xHamster. Because you're worth it.

  1. // ==UserScript==
  2. // @author LD
  3. // @version 1.0
  4. // @name xHamster Plus
  5. // @description A kinder xHamster. Because you're worth it.
  6. // @namespace LD
  7. // @date 2018-08-08
  8. // @include *xhamster.com/*
  9. // @run-at document-start
  10. // @grant none
  11. // @license Public Domain
  12. // @icon https://static-cl.xhcdn.com/xh-tpl3/images/favicon/apple-touch-icon.png
  13. // @grant GM_addStyle
  14. // ==/UserScript==
  15.  
  16. (() => {
  17. /* Available options */
  18. const options = {
  19. autoresizePlayer:
  20. JSON.parse(localStorage.getItem('plus_autoresizePlayer')) || false
  21. }
  22. /* Empty tag function, for VS Code syntax highlight for CSS in literals */
  23. const css = (strings, ...values) => {
  24. /* Re-assemble input string and return untouched */
  25. return strings.reduce((output, string, index) => {
  26. return `${output}${string}${values[index] || ''}`;
  27. }, '');
  28. };
  29. /* Styles - Shared between all "Plus" userscripts */
  30. const sharedStyles = css`
  31. /* Our own elements */
  32.  
  33. .plus-buttons {
  34. background: rgba(67, 67, 67, 0.85);
  35. box-shadow: 0px 0px 12px rgba(20, 111, 223, 0.85);
  36. font-size: 12px;
  37. position: fixed;
  38. bottom: 10px;
  39. padding: 10px 22px 8px 24px;
  40. right: 0;
  41. z-index: 100;
  42. transition: all 0.3s ease;
  43.  
  44. /* Negative margin-right calculated later based on width of buttons */
  45. }
  46.  
  47. .plus-buttons:hover {
  48. box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.3);
  49. }
  50.  
  51. .plus-buttons .plus-button {
  52. margin: 10px 0;
  53. padding: 6px 15px;
  54. border-radius: 4px;
  55. font-weight: 700;
  56. display: block;
  57. position: relative;
  58. text-align: center;
  59. vertical-align: top;
  60. cursor: pointer;
  61. border: none;
  62. text-decoration: none;
  63. }
  64.  
  65. .plus-buttons a.plus-button {
  66. background: rgb(221, 221, 221);
  67. color: rgb(51, 51, 51);
  68. }
  69.  
  70. .plus-buttons a.plus-button:hover {
  71. background: rgb(187, 187, 187);
  72. color: rgb(51, 51, 51);
  73. }
  74.  
  75. .plus-buttons a.plus-button.plus-button-isOn {
  76. background: rgb(20, 111, 223);
  77. color: rgb(255, 255, 255);
  78. }
  79.  
  80. .plus-buttons a.plus-button.plus-button-isOn:hover {
  81. background: rgb(0, 91, 203);
  82. color: rgb(255, 255, 255);
  83. }
  84.  
  85. .plus-hidden {
  86. display: none !important;
  87. }
  88. `;
  89. /* Styles - Color theme */
  90. const themeStyles = css`
  91. .plus-buttons {
  92. box-shadow: 0px 0px 18px rgba(227, 68, 73, 1);
  93. }
  94.  
  95. .plus-buttons:hover {
  96. box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.3);
  97. }
  98.  
  99. .plus-buttons a.plus-button {
  100. background: rgb(218, 218, 218);
  101. color: rgb(48, 48, 48);
  102. }
  103.  
  104. .plus-buttons a.plus-button:hover {
  105. background: rgb(204, 204, 204);
  106. color: rgb(48, 48, 48);
  107. }
  108.  
  109. .plus-buttons a.plus-button.plus-button-isOn {
  110. background: rgb(227, 68, 73);
  111. color: rgb(255, 255, 255);
  112. }
  113.  
  114. .plus-buttons a.plus-button.plus-button-isOn:hover {
  115. background: rgb(212, 32, 37);
  116. color: rgb(255, 255, 255);
  117. }
  118. `;
  119. /* Styles - General site-specific */
  120. const generalStyles = css`
  121. /* Hide elements */
  122.  
  123. .up-arrow,
  124. .premium-overlay,
  125. .bottom-widget-section,
  126. .clipstore-bottom,
  127. .wid-spot-container,
  128. .wid-banner-container {
  129. display: none !important;
  130. }
  131.  
  132. /* Increase large player size */
  133.  
  134. .video-page.video-page--large-mode .player-container__player {
  135. height: 720px;
  136. }
  137.  
  138. /* Show all playlists without scrolling when adding to favorites */
  139.  
  140. .favorites-dropdown__list {
  141. max-height: unset !important;
  142. }
  143.  
  144. .video-page:not(.video-page--large-mode) .player-container {
  145. margin: 10px auto 0;
  146. }
  147.  
  148. .video-page:not(.video-page--large-mode) .entity-container,
  149. .video-page:not(.video-page--large-mode) .comments-wrap {
  150. margin: 0 auto;
  151. }
  152.  
  153. /* Minor stylistic improvements */
  154.  
  155. .entity-container {
  156. margin: 22px 0;
  157. margin-bottom: 22px;
  158. border-top: 1px solid #ccc;
  159. }
  160. `;
  161. /*
  162. * Run after page has loaded
  163. */
  164. window.addEventListener('DOMContentLoaded', () => {
  165. const player = document.querySelector('#player-container');
  166. const video = document.querySelector('#player-container video');
  167. /*
  168. * Use wide player by default
  169. */
  170. if (video && options.autoresizePlayer) {
  171. /* Button is not always available right away -- wait for `canplay` */
  172. video.addEventListener('canplay', function onCanPlay() {
  173. let largePlayerButton = document.querySelector('.large-mode');
  174. /* Click large player button */
  175. largePlayerButton.dispatchEvent(new MouseEvent('click'));
  176. /* Only run once */
  177. video.removeEventListener('canplay', onCanPlay, false);
  178. });
  179. }
  180. /*
  181. * Add buttons for certain options
  182. */
  183. /* Buttons container */
  184. let buttons = document.createElement('div');
  185. let scrollButton = document.createElement('a');
  186. let scrollButtonText = document.createElement('span');
  187. let autoresizeButton = document.createElement('a');
  188. let autoresizeButtonText = document.createElement('span');
  189. let autoresizeButtonState = options.autoresizePlayer ? 'plus-button-isOn' : 'plus-button-isOff';
  190. buttons.classList.add('plus-buttons');
  191. scrollButtonText.textContent = "Scroll to top";
  192. scrollButtonText.classList.add('text');
  193. scrollButton.appendChild(scrollButtonText);
  194. scrollButton.classList.add('plus-button');
  195. scrollButton.addEventListener('click', () => {
  196. window.scrollTo({ top: 0 });
  197. });
  198. buttons.appendChild(scrollButton);
  199. buttons.appendChild(autoresizeButton);
  200. autoresizeButtonText.textContent = 'Auto-resize player';
  201. autoresizeButtonText.classList.add('text');
  202. autoresizeButton.appendChild(autoresizeButtonText);
  203. autoresizeButton.classList.add(autoresizeButtonState, 'plus-button');
  204. autoresizeButton.addEventListener('click', () => {
  205. options.autoresizePlayer = !options.autoresizePlayer;
  206. localStorage.setItem('plus_autoresizePlayer', options.autoresizePlayer);
  207. if (options.autoresizePlayer) {
  208. autoresizeButton.classList.replace('plus-button-isOff', 'plus-button-isOn');
  209. } else {
  210. autoresizeButton.classList.replace('plus-button-isOn', 'plus-button-isOff');
  211. }
  212. });
  213. document.body.appendChild(buttons);
  214. /*
  215. * Add styles
  216. */
  217. GM_addStyle(sharedStyles);
  218. GM_addStyle(themeStyles);
  219. GM_addStyle(generalStyles);
  220. /*
  221. * Add dynamic styles
  222. */
  223. const dynamicStyles = css`
  224. .plus-buttons {
  225. margin-right: -${buttons.getBoundingClientRect().width - 23}px;
  226. }
  227.  
  228. .plus-buttons:hover {
  229. margin-right: 0;
  230. }
  231.  
  232. .video-page.video-page--large-mode .player-container__player {
  233. max-height: ${window.innerHeight - 60}px;
  234. }
  235. `;
  236. GM_addStyle(dynamicStyles);
  237. if (player) {
  238. window.addEventListener('resize', () => {
  239. if (player.classList.contains('xplayer-large-mode')) {
  240. player.style.maxHeight = `${window.innerHeight - 60}px`;
  241. }
  242. });
  243. }
  244. });
  245. })();