Grok.com Theme Switcher

Adds a theme switcher to grok.com with dark, cyberpunk, light, blood red, midnight, deep ocean, celestial, and divine modes, foldable picker with animations, draggable, and script editor compatibility. Enhanced query-bar opacity fix to 75% with broader selectors and debug logging.

  1. // ==UserScript==
  2. // @name Grok.com Theme Switcher
  3. // @namespace http://violentmonkey.github.io/
  4. // @version 1.2
  5. // @description Adds a theme switcher to grok.com with dark, cyberpunk, light, blood red, midnight, deep ocean, celestial, and divine modes, foldable picker with animations, draggable, and script editor compatibility. Enhanced query-bar opacity fix to 75% with broader selectors and debug logging.
  6. // @author virtualdmns
  7. // @license MIT
  8. // @match https://grok.com/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. console.log('Grok Theme Switcher: Script loaded.');
  16.  
  17. // Define theme styles
  18. const themes = {
  19. dark: {
  20. background: '#1a1a1a',
  21. text: '#e0e0e0',
  22. accent: '#00ff88',
  23. buttonBg: '#333',
  24. buttonText: '#fff'
  25. },
  26. cyberpunk: {
  27. background: '#0d0221',
  28. text: '#ff00ff',
  29. accent: '#00f7ff',
  30. buttonBg: '#2a0a3b',
  31. buttonText: '#ff00ff'
  32. },
  33. light: {
  34. background: '#f5f5f5',
  35. text: '#333',
  36. accent: '#007bff',
  37. buttonBg: '#ddd',
  38. buttonText: '#000'
  39. },
  40. bloodred: {
  41. background: '#2e0000',
  42. text: '#ff6666',
  43. accent: '#800000',
  44. buttonBg: '#4a0000',
  45. buttonText: '#ff9999'
  46. },
  47. midnight: {
  48. background: '#0a0a1c',
  49. text: '#c0c0d9',
  50. accent: '#4b4bff',
  51. buttonBg: '#1c1c2e',
  52. buttonText: '#e6e6ff'
  53. },
  54. deepocean: {
  55. background: '#0a2424',
  56. text: '#66cccc',
  57. accent: '#00b7eb',
  58. buttonBg: '#1a3c3c',
  59. buttonText: '#99e6e6'
  60. },
  61. celestial: {
  62. background: '#1b0a3b',
  63. text: '#ffd700',
  64. accent: '#b266ff',
  65. buttonBg: '#2e1a5c',
  66. buttonText: '#ffeb99'
  67. },
  68. divine: {
  69. background: '#f5f6f5',
  70. text: '#4682b4',
  71. accent: '#c0c0c0',
  72. buttonBg: '#e6e6fa',
  73. buttonText: '#191970'
  74. }
  75. };
  76.  
  77. // Debug function to check query-bar opacity
  78. function debugQueryBarOpacity() {
  79. const queryBar = document.querySelector('div[class*="query-bar"]');
  80. if (queryBar) {
  81. const computedStyle = window.getComputedStyle(queryBar);
  82. console.log(`Grok Theme Switcher: Query bar opacity is ${computedStyle.opacity}`);
  83. } else {
  84. console.log('Grok Theme Switcher: Query bar not found in DOM.');
  85. }
  86. }
  87.  
  88. // Inject CSS to avoid CSP issues
  89. function injectStyles(themeName) {
  90. console.log(`Grok Theme Switcher: Injecting styles for theme - ${themeName}`);
  91. const theme = themes[themeName];
  92. let styleTag = document.getElementById('theme-styles');
  93. if (!styleTag) {
  94. styleTag = document.createElement('style');
  95. styleTag.id = 'theme-styles';
  96. document.head.appendChild(styleTag);
  97. }
  98.  
  99. styleTag.textContent = `
  100. @keyframes fade-slide {
  101. from { opacity: 0; transform: translateY(10px); }
  102. to { opacity: 1; transform: translateY(0); }
  103. }
  104. @keyframes scale-pop {
  105. from { transform: scale(0.5); opacity: 0; }
  106. to { transform: scale(1); opacity: 1; }
  107. }
  108. @keyframes pulse {
  109. 0% { transform: scale(1); }
  110. 50% { transform: scale(1.1); }
  111. 100% { transform: scale(1); }
  112. }
  113. body {
  114. background-color: ${theme.background} !important;
  115. color: ${theme.text} !important;
  116. animation: fade-slide 0.5s ease !important;
  117. }
  118. p, span, div:not([class*="editor"]), h1, h2, h3, h4, h5, h6, li, a:not([class*="button"]), img {
  119. color: ${theme.text} !important;
  120. background-color: transparent !important;
  121. border: none !important;
  122. animation: fade-slide 0.5s ease !important;
  123. }
  124. button:not([class*="editor"]), input[type="button"], input[type="submit"], [role="button"]:not([class*="editor"]) {
  125. background-color: ${theme.buttonBg} !important;
  126. color: ${theme.buttonText} !important;
  127. border: none !important;
  128. transition: all 0.3s ease !important;
  129. animation: fade-slide 0.5s ease !important;
  130. }
  131. div[class*="query-bar"] {
  132. opacity: 0.75 !important;
  133. transition: opacity 0.3s ease !important;
  134. background-color: ${theme.background} !important;
  135. }
  136. div[class*="query-bar"] textarea, div[class*="query-bar"] button, div[class*="query-bar"] div {
  137. background-color: ${theme.background} !important;
  138. opacity: 1 !important;
  139. }
  140. #theme-switcher {
  141. position: fixed !important;
  142. z-index: 9999 !important;
  143. background-color: rgba(0, 0, 0, 0.7) !important;
  144. padding: 10px !important;
  145. border-radius: 8px !important;
  146. cursor: move !important;
  147. animation: scale-pop 0.3s ease !important;
  148. display: block !important;
  149. }
  150. #theme-switcher.hidden {
  151. display: none !important;
  152. }
  153. #themeSelect {
  154. padding: 8px !important;
  155. border-radius: 4px !important;
  156. cursor: pointer !important;
  157. background: #fff !important;
  158. color: #000 !important;
  159. }
  160. #theme-toggle-btn {
  161. position: fixed !important;
  162. z-index: 9999 !important;
  163. background-color: rgba(0, 0, 0, 0.7) !important;
  164. color: #fff !important;
  165. padding: 8px 12px !important;
  166. border-radius: 4px !important;
  167. cursor: pointer !important;
  168. font-size: 14px !important;
  169. animation: pulse 2s infinite ease !important;
  170. display: none !important;
  171. }
  172. #theme-toggle-btn.visible {
  173. display: block !important;
  174. }
  175. `;
  176. console.log('Grok Theme Switcher: Applied opacity 0.75 to query-bar');
  177. setTimeout(debugQueryBarOpacity, 500); // Check opacity after styles apply
  178. }
  179.  
  180. // Force toggle state
  181. function forceToggleState(switcher, toggleBtn, isFolded) {
  182. console.log(`Grok Theme Switcher: Forcing toggle state - folded: ${isFolded}`);
  183. switcher.classList.toggle('hidden', isFolded);
  184. toggleBtn.classList.toggle('visible', isFolded);
  185. localStorage.setItem('themePickerFolded', isFolded.toString());
  186. }
  187.  
  188. // Create floating theme switcher UI
  189. function createSwitcher() {
  190. console.log('Grok Theme Switcher: Creating switcher UI.');
  191. if (document.getElementById('theme-switcher') || document.getElementById('theme-toggle-btn')) {
  192. console.log('Grok Theme Switcher: Switcher or toggle already exists, skipping.');
  193. return;
  194. }
  195.  
  196. // Create toggle button
  197. const toggleBtn = document.createElement('button');
  198. toggleBtn.id = 'theme-toggle-btn';
  199. toggleBtn.textContent = 'Theme';
  200. document.body.appendChild(toggleBtn);
  201.  
  202. // Create theme switcher
  203. const switcher = document.createElement('div');
  204. switcher.id = 'theme-switcher';
  205. switcher.innerHTML = `
  206. <select id="themeSelect">
  207. <option value="dark">Dark</option>
  208. <option value="cyberpunk">Cyberpunk</option>
  209. <option value="light">Light</option>
  210. <option value="bloodred">Blood Red</option>
  211. <option value="midnight">Midnight</option>
  212. <option value="deepocean">Deep Ocean</option>
  213. <option value="celestial">Celestial</option>
  214. <option value="divine">Divine</option>
  215. </select>
  216. `;
  217.  
  218. // Load saved position or default
  219. let savedPos = JSON.parse(localStorage.getItem('themePickerPos'));
  220. if (!savedPos || !savedPos.left || !savedPos.top) {
  221. savedPos = { top: 'auto', bottom: '20px', left: 'auto', right: '20px' };
  222. }
  223. Object.assign(switcher.style, savedPos);
  224. Object.assign(toggleBtn.style, savedPos);
  225.  
  226. // Load folded state
  227. const isFolded = localStorage.getItem('themePickerFolded') === 'true';
  228. document.body.appendChild(switcher);
  229. forceToggleState(switcher, toggleBtn, isFolded);
  230.  
  231. // Event listener for theme change
  232. const themeSelect = document.getElementById('themeSelect');
  233. themeSelect.addEventListener('change', (e) => {
  234. console.log(`Grok Theme Switcher: Theme selected - ${e.target.value}`);
  235. injectStyles(e.target.value);
  236. localStorage.setItem('grokTheme', e.target.value);
  237. });
  238.  
  239. // Event listener for toggle button
  240. toggleBtn.addEventListener('click', () => {
  241. console.log('Grok Theme Switcher: Toggle clicked.');
  242. const isCurrentlyFolded = switcher.classList.contains('hidden');
  243. forceToggleState(switcher, toggleBtn, !isCurrentlyFolded);
  244. });
  245.  
  246. // Draggable functionality for both switcher and button
  247. function makeDraggable(element, isButton) {
  248. let isDragging = false;
  249. let offsetX, offsetY;
  250.  
  251. element.addEventListener('mousedown', (e) => {
  252. isDragging = true;
  253. offsetX = e.offsetX;
  254. offsetY = e.offsetY;
  255. console.log(`Grok Theme Switcher: Starting drag on ${isButton ? 'button' : 'switcher'}.`);
  256. });
  257.  
  258. document.addEventListener('mousemove', (e) => {
  259. if (isDragging) {
  260. let newLeft = e.clientX - offsetX;
  261. let newTop = e.clientY - offsetY;
  262.  
  263. // Keep within viewport
  264. const rect = element.getBoundingClientRect();
  265. newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - rect.width));
  266. newTop = Math.max(0, Math.min(newTop, window.innerHeight - rect.height));
  267.  
  268. element.style.left = `${newLeft}px`;
  269. element.style.top = `${newTop}px`;
  270. element.style.right = 'auto';
  271. element.style.bottom = 'auto';
  272.  
  273. // Sync other element
  274. const otherElement = isButton ? switcher : toggleBtn;
  275. otherElement.style.left = `${newLeft}px`;
  276. otherElement.style.top = `${newTop}px`;
  277. otherElement.style.right = 'auto';
  278. otherElement.style.bottom = 'auto';
  279. }
  280. });
  281.  
  282. document.addEventListener('mouseup', () => {
  283. if (isDragging) {
  284. isDragging = false;
  285. const pos = {
  286. top: element.style.top,
  287. bottom: element.style.bottom,
  288. left: element.style.left,
  289. right: element.style.right
  290. };
  291. localStorage.setItem('themePickerPos', JSON.stringify(pos));
  292. console.log(`Grok Theme Switcher: Position saved.`, pos);
  293. }
  294. });
  295. }
  296.  
  297. makeDraggable(switcher, false);
  298. makeDraggable(toggleBtn, true);
  299.  
  300. const savedTheme = localStorage.getItem('grokTheme') || 'bloodred';
  301. themeSelect.value = savedTheme;
  302. injectStyles(savedTheme);
  303. }
  304.  
  305. // Mutation observer for dynamic content
  306. function observeDOM() {
  307. console.log('Grok Theme Switcher: Setting up DOM observer.');
  308. const observer = new MutationObserver(() => {
  309. console.log('Grok Theme Switcher: DOM changed, reapplying theme.');
  310. const savedTheme = localStorage.getItem('grokTheme') || 'bloodred';
  311. injectStyles(savedTheme);
  312. debugQueryBarOpacity();
  313. const switcher = document.getElementById('theme-switcher');
  314. const toggleBtn = document.getElementById('theme-toggle-btn');
  315. if (switcher && toggleBtn) {
  316. const isFolded = localStorage.getItem('themePickerFolded') === 'true';
  317. forceToggleState(switcher, toggleBtn, isFolded);
  318. } else if (!switcher && !toggleBtn) {
  319. console.log('Grok Theme Switcher: Switcher missing, recreating.');
  320. createSwitcher();
  321. }
  322. });
  323.  
  324. observer.observe(document.body, {
  325. childList: true,
  326. subtree: true,
  327. attributes: true,
  328. attributeFilter: ['class', 'style']
  329. });
  330. }
  331.  
  332. // Wait for DOM to be ready
  333. function waitForBody() {
  334. if (document.body) {
  335. initialize();
  336. } else {
  337. console.log('Grok Theme Switcher: Body not ready, polling...');
  338. setTimeout(waitForBody, 100);
  339. }
  340. }
  341.  
  342. // Start when DOM is ready or poll if not
  343. function initialize() {
  344. console.log('Grok Theme Switcher: Initializing.');
  345. createSwitcher();
  346. observeDOM();
  347. }
  348.  
  349. if (document.readyState === 'loading') {
  350. document.addEventListener('DOMContentLoaded', waitForBody);
  351. } else {
  352. waitForBody();
  353. }
  354. })();