xHamster Plus

A kinder xHamster. Because you're worth it.

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