Dark Mode+

Sketchful dark theme improvements

  1. /* eslint-disable no-undef */
  2. // ==UserScript==
  3. // @name Dark Mode+
  4. // @match https://sketchful.io/
  5. // @grant none
  6. // @version 1.3.1
  7. // @description Sketchful dark theme improvements
  8. // @author bebell
  9. // @run-at body-end
  10. // jshint esversion: 6
  11. // @namespace https://greasyfork.org/users/281093
  12. // ==/UserScript==
  13.  
  14. let grayCanvas = localStorage.grayCanvas
  15. ? JSON.parse(localStorage.grayCanvas)
  16. : true;
  17. const pageHTML = document.querySelector('html');
  18. const canvas = document.querySelector('#canvas');
  19. const ctx = canvas.getContext('2d');
  20. const themes = document.querySelector('#menuSettingsTheme');
  21. const brightnessLayer = document.createElement('div');
  22. const settingsContainer = document.querySelector(
  23. '#menuSettings > div.row.justify-content-center > div'
  24. );
  25.  
  26. wrap(canvas, brightnessLayer);
  27.  
  28. const styleRules = [
  29. '.dark .table { color: white !important; }',
  30. '.table { color: black !important; }',
  31. '.dark { background-color: #1d1f22 !important; }',
  32. '.dark .table th, html.dark .table thead, html.dark .table td { border-color: #454d55 !important; }',
  33. '.table th, .table thead, .table td { border-color: #dee2e6 !important; }',
  34. '.dark .table.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(255,255,255,.05)!important; }',
  35. '.table.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0,0,0,.05)!important; }',
  36. '.dark #gameChatList li:not(.chatAdmin) b { color: #BBB }',
  37. '#gameChatList li:not(.chatAdmin) b { color: black }',
  38. '.dark .gameAvatarRank { color: #148FA2 }',
  39. '.dark #gameWinners li b { color: white !important }',
  40. '.dark .gameSticky { color: white !important }',
  41. '#gameSticky font { color: black!important }',
  42. '.dark #gameSticky font { color: #eee!important }'
  43. ];
  44.  
  45. const sheet = window.document.styleSheets[window.document.styleSheets.length - 1];
  46. styleRules.forEach((rule) => sheet.insertRule(rule));
  47.  
  48. let btn;
  49. const stored = localStorage.dark;
  50. let darkMode = stored
  51. ? JSON.parse(stored)
  52. : JSON.parse(localStorage.getItem('settings')).dark;
  53.  
  54. if (darkMode) pageHTML.classList.add('dark');
  55. else pageHTML.classList.remove('dark');
  56.  
  57. brightnessLayer.style.filter = darkMode
  58. ? `brightness(${localStorage.canvasBrightness})` || ''
  59. : '';
  60.  
  61. themes.onchange = (e) => {
  62. if (e.target.value === 'Dark' && !grayCanvas) {
  63. pageHTML.classList.add('dark');
  64. e.stopImmediatePropagation();
  65. }
  66. };
  67.  
  68. document.querySelector('#menu > div.menuNav > ul > li:nth-child(5) > a').onclick = () => {
  69. themes.value = darkMode ? 'Dark' : 'Light';
  70. };
  71.  
  72. function wrap(toWrap, wrapper) {
  73. toWrap.parentNode.appendChild(wrapper);
  74. wrapper.appendChild(toWrap);
  75. return wrapper;
  76. }
  77.  
  78. function darkClassObserver(mutations) {
  79. for (const mutation of mutations) {
  80. if (mutation.attributeName !== 'class') return;
  81. darkMode = pageHTML.classList.contains('dark');
  82. localStorage.dark = darkMode;
  83. brightnessLayer.style.filter = darkMode
  84. ? `brightness(${localStorage.canvasBrightness})` || ''
  85. : '';
  86. fixColors();
  87. grayCanvas && toggleCanvasDarkMode();
  88. }
  89. }
  90.  
  91. const gameObserver = new MutationObserver(darkClassObserver);
  92.  
  93. gameObserver.observe(pageHTML, { attributes: true });
  94.  
  95. (function addBrightnessSlider() {
  96. const sliderContainer = document
  97. .querySelector('#menuSettingsVolumeIcon')
  98. .parentNode.cloneNode(true);
  99. sliderContainer.getElementsByTagName('h5')[0].textContent =
  100. 'Canvas Brightness';
  101. const icon = sliderContainer.firstChild;
  102. icon.remove();
  103. const newIcon = document.createElement('img');
  104. newIcon.setAttribute('style', 'width: 64px; height: 64px');
  105. newIcon.setAttribute('class', 'mr-3 lazy');
  106. newIcon.src = 'https://i.imgur.com/GkVJWun.gif';
  107. const slider = sliderContainer.querySelector('#menuSettingsVolume');
  108. slider.value = localStorage.canvasBrightness || 1;
  109. slider.onchange = changeBrightness;
  110. sliderContainer.insertBefore(newIcon, sliderContainer.firstChild);
  111.  
  112. const grayCanvasToggle = document.createElement('div');
  113. grayCanvasToggle.setAttribute(
  114. 'style',
  115. 'display: flex; justify-content: space-between'
  116. );
  117. const label = document.createElement('label');
  118.  
  119. label.style.fontSize = '20px';
  120. label.textContent = 'Gray Canvas';
  121.  
  122. const btnSwitch = document.createElement('label');
  123. const input = document.createElement('input');
  124. const span = document.createElement('span');
  125. span.setAttribute('class', 'slider round');
  126. input.type = 'checkbox';
  127. input.checked = grayCanvas;
  128. input.onchange = () => {
  129. grayCanvas = input.checked;
  130. localStorage.grayCanvas = grayCanvas;
  131. if (!grayCanvas && darkMode) {
  132. themes[0].selected = true;
  133. themes.dispatchEvent(new Event('change'));
  134. pageHTML.classList.add('dark');
  135. themes.value = darkMode ? 'Dark' : 'Light';
  136. }
  137. };
  138. btnSwitch.setAttribute('class', 'switch');
  139. btnSwitch.append(input, span);
  140. grayCanvasToggle.append(label, btnSwitch);
  141. settingsContainer.append(sliderContainer, grayCanvasToggle);
  142. })();
  143.  
  144. (function toggleButtons() {
  145. btn = document.querySelector('#saveButton').cloneNode();
  146. btn.style.right = '100px';
  147. btn.setAttribute('data-original-title', 'Toggle Theme');
  148. btn.title = 'Toggle Theme';
  149. updateToggleStyle();
  150. btn.onclick = toggleDarkMode;
  151.  
  152. document.querySelector('#gameInterface').append(btn);
  153. })();
  154.  
  155. function changeBrightness() {
  156. brightnessLayer.style.filter = `brightness(${this.value})`;
  157. localStorage.canvasBrightness = this.value;
  158. }
  159.  
  160. function updateToggleStyle() {
  161. btn.style.backgroundImage = darkMode ? 'url(https://i.imgur.com/imL02Mq.gif)' :
  162. 'url(https://i.imgur.com/2ZdZ9oh.gif)';
  163. }
  164.  
  165. function toggleDarkMode() {
  166. if (!grayCanvas) {
  167. pageHTML.classList.toggle('dark');
  168. }
  169. else {
  170. themes[darkMode ? 0 : 1].selected = true;
  171. themes.dispatchEvent(new Event('change'));
  172. }
  173. }
  174.  
  175. function toggleCanvasDarkMode() {
  176. const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  177. const data = imgData.data;
  178.  
  179. for (let i = 0; i < data.length; i += 4) {
  180. const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
  181.  
  182. if (r === 255 && g === 255 && b === 255 && darkMode) {
  183. const color = 68;
  184. data[i] = color;
  185. data[i + 1] = color;
  186. data[i + 2] = color;
  187. }
  188. else if (r === 68 && g === 68 && b === 68 && !darkMode) {
  189. const color = 255;
  190. data[i] = color;
  191. data[i + 1] = color;
  192. data[i + 2] = color;
  193. }
  194. }
  195.  
  196. ctx.putImageData(imgData, 0, 0);
  197. }
  198.  
  199. function fixColors() {
  200. let names = document.getElementsByClassName('gameAvatarName');
  201. updateToggleStyle();
  202. if (!names.length) {names = document.getElementsByClassName('gameSettingsAvatarName');}
  203. for (const name of names) {
  204. if (darkMode && name.style.color === 'black') {
  205. name.style.color = '#ccc';
  206. }
  207. else if (!darkMode && name.style.color === 'rgb(204, 204, 204)') {
  208. name.style.color = 'black';
  209. }
  210. }
  211. }
  212.  
  213. const colorObserver = new MutationObserver(() => {
  214. fixColors();
  215. });
  216.  
  217. colorObserver.observe(document.querySelector('#gamePlayersList'), {
  218. childList: true
  219. });