Kastden+ - Enhancement Script for selca.kastden.org

Adds dark mode, download keybind and various UI improvements to kastden. Press 'S' while hovering over a thumbnail or in gallery mode to download

  1. // ==UserScript==
  2. // @name Kastden+ - Enhancement Script for selca.kastden.org
  3. // @version 1.2
  4. // @description Adds dark mode, download keybind and various UI improvements to kastden. Press 'S' while hovering over a thumbnail or in gallery mode to download
  5. // @author kpganon
  6. // @license MIT
  7. // @namespace https://github.com/kpg-anon/scripts
  8. // @match https://selca.kastden.org/*
  9. // @grant GM_addStyle
  10. // @grant GM_addElement
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. var colorCodes = {
  18. 'background-color': '#282A36',
  19. 'text-color': 'lightgrey',
  20. 'link-color': '#FF79C6',
  21. 'link-hover': '#cc609e',
  22. 'group-link': '#ffaedc',
  23. 'member-list': 'grey',
  24. 'button-background': '#44475A',
  25. 'button-hover': '#6272A4',
  26. 'button-text': 'silver',
  27. 'input-text': 'white',
  28. 'form-background': '#3d3f4a',
  29. 'username-color': 'orange',
  30. 'backtotop-button': '#363848',
  31. 'text-highlight': 'rgba(255, 161, 215, 0.45)',
  32. 'thumbnail-border': 'hotpink'
  33. };
  34.  
  35. function createCSSVariables(colorCodes) {
  36. var cssVarString = ':root {';
  37. for (var key in colorCodes) {
  38. if (colorCodes.hasOwnProperty(key)) {
  39. cssVarString += `--${key}: ${colorCodes[key]};`;
  40. }
  41. }
  42. cssVarString += '}';
  43. return cssVarString;
  44. }
  45.  
  46. GM_addStyle(createCSSVariables(colorCodes));
  47.  
  48. var style = document.createElement('style');
  49. style.type = 'text/css';
  50. style.innerHTML = `
  51. body {
  52. background-color: var(--background-color) !important;
  53. }
  54. `;
  55.  
  56. function createBackToTopButton() {
  57. var button = document.createElement('button');
  58. button.innerHTML = '🡅';
  59. button.id = 'back-to-top-btn';
  60. button.onclick = function() {
  61. window.scrollTo({ top: 0, behavior: 'smooth' });
  62. };
  63.  
  64. document.body.appendChild(button);
  65. }
  66.  
  67. function checkURL() {
  68. var mybutton = document.getElementById("back-to-top-btn");
  69. if (!mybutton) return;
  70.  
  71. const currentURL = window.location.href;
  72. if (currentURL.includes('/media/')) {
  73. mybutton.style.display = 'none';
  74. } else {
  75. mybutton.style.display = 'block';
  76. }
  77. }
  78.  
  79. function downloadImage(url) {
  80. const a = document.createElement('a');
  81. a.href = url;
  82. a.download = url.split('/').pop();
  83. document.body.appendChild(a);
  84. a.click();
  85. document.body.removeChild(a);
  86. }
  87.  
  88. let hoveredElement = null;
  89. document.addEventListener('mouseover', function(e) {
  90. const closestAnchor = e.target.closest('.image a');
  91. if (closestAnchor) {
  92. hoveredElement = closestAnchor;
  93. } else {
  94. hoveredElement = null;
  95. }
  96. });
  97.  
  98. function isGalleryActive() {
  99. const currentURL = window.location.href;
  100. const isActive = currentURL.includes('/media/');
  101. return isActive;
  102. }
  103.  
  104. document.addEventListener('keydown', function(e) {
  105. if (e.key === 's' || e.key === 'S') {
  106. e.preventDefault();
  107. let imageUrl = '';
  108.  
  109. if (isGalleryActive()) {
  110. const galleryImage = document.querySelector('#cover_media_frame img');
  111. if (galleryImage && galleryImage.src) {
  112. imageUrl = galleryImage.src;
  113. }
  114. } else if (hoveredElement && hoveredElement.href) {
  115. imageUrl = hoveredElement.href;
  116. }
  117.  
  118. if (imageUrl) {
  119. downloadImage(imageUrl);
  120. } else {
  121. }
  122. }
  123. });
  124.  
  125. var styleRules = `
  126. * {
  127. color: var(--text-color);
  128. }
  129. html {
  130. scroll-behavior: smooth;
  131. }
  132. body {
  133. background-color: var(--background-color)
  134. }
  135. a {
  136. color: var(--link-color);
  137. }
  138. a:hover {
  139. color: var(--link-hover)
  140. }
  141. a:visited {
  142. color: var(--link-hover);
  143. }
  144. a.main_group_link {
  145. color: var(--group-link);
  146. }
  147. #header {
  148. border-bottom: none;
  149. }
  150. .post_info.media_info {
  151. background-color: var(--background-color);
  152. }
  153. #message_div {
  154. background-color: var(--background-color);
  155. border: none;
  156. }
  157. .message_div {
  158. background: none;
  159. }
  160. .header, .post_info, .entry, .image {
  161. background-color: var(--background-color)
  162. border: 1px solid #4B0082;
  163. }
  164. .post_info.media_info {
  165. border-radius: 10px;
  166. overflow: hidden;
  167. }
  168. .prev_button img, .next_button img, .close_button img {
  169. filter: invert(100%);
  170. }
  171. .header_left img {
  172. filter: invert(100%);
  173. }
  174. .close_button img:hover, .prev_button img:hover, .next_button img:hover {
  175. filter: invert(100%) brightness(85%);
  176. }
  177. .close_button img:active, .prev_button img:active, .next_button img:active {
  178. transform: scale(0.95);
  179. }
  180. .button, .submit {
  181. background-color: var(--background-color)
  182. color: var(--button-text);
  183. border: none;
  184. }
  185. input[type='text'], input[type='datetime-local'], input[type='submit'] {
  186. background-color: var(--button-background);
  187. color: var(--input-text);
  188. border: 1px solid var(--button-hover);
  189. }
  190. input[type='submit']:hover {
  191. background-color: var(--button-hover);
  192. color: var(--input-text);
  193. }
  194. .button:hover, .submit:hover {
  195. background-color: var(--button-hover);
  196. }
  197. input, select, textarea, button {
  198. background-color: var(--button-background);
  199. color: var(--input-text);
  200. border: 1px solid var(--button-hover);
  201. }
  202. input:focus, select:focus, textarea:focus, button:focus {
  203. border: 1px solid var(--link-color);
  204. }
  205. input[type='submit']:hover, button:hover {
  206. background-color: var(--form-background);
  207. color: var(--input-text);
  208. }
  209. #fixed_submit_div {
  210. bottom: 15px;
  211. margin: 0.4em;
  212. padding: 0;
  213. border: 2px solid var(--button-background);
  214. }
  215. select {
  216. background-color: var(--button-background);
  217. color: var(--input-text);
  218. border: 1px solid var(--button-hover);
  219. }
  220. #fixed_form_submit {
  221. background-color: var(--background-color);
  222. color: var(--button-text);
  223. padding: 5px 10px;
  224. cursor: pointer;
  225. }
  226. #preferences_form_page_size {
  227. background-color: var(--background-color);
  228. color: var(--button-text);
  229. border: 1px solid var(--button-hover);
  230. cursor: pointer;
  231. }
  232. #fixed_form_submit:hover {
  233. background-color: var(--form-background);
  234. color: var(--input-text);
  235. }
  236. a.table_header {
  237. color: var(--text-color);
  238. text-decoration: none;
  239. }
  240. .member a {
  241. color: var(--member-list);
  242. }
  243. .member.unfollowed, .member.unfollowed a {
  244. color: var(--member-list);
  245. }
  246. ::-webkit-scrollbar {
  247. width: 12px;
  248. }
  249. ::-webkit-scrollbar-thumb {
  250. background: #444;
  251. border-radius: 10px;
  252. }
  253. ::-webkit-scrollbar-thumb:hover {
  254. background: #555;
  255. }
  256. ::-webkit-scrollbar-track {
  257. background: var(--background-color);
  258. border-radius: 10px;
  259. }
  260. ::-webkit-scrollbar-track-piece {
  261. background: transparent;
  262. }
  263. ::-webkit-scrollbar-corner {
  264. background: transparent;
  265. }
  266. ::-webkit-scrollbar-button {
  267. display: none;
  268. }
  269. .glowing-text {
  270. color: var(--username-color);
  271. text-shadow: 0 0 10px var(--username-color);
  272. }
  273. .media_frame {
  274. height: auto !important;
  275. }
  276. #cover_media_frame {
  277. overflow: hidden;
  278. max-height: none;
  279. max-width: 100%;
  280. position: relative;
  281. }
  282. #full_media {
  283. transition: transform 0.3s ease;
  284. max-height: 100%;
  285. max-height: 667px;
  286. max-width: 100%;
  287. border-radius: 21px;
  288. }
  289. #full_media:hover {
  290. transform: scale(1.05);
  291. }
  292. .media_frame img, .media_frame video {
  293. padding-top: none;
  294. top: 10px;
  295. max-width: 100%;
  296. height: auto;
  297. }
  298. .media_frame_wrapper {
  299. padding-top: 12px;
  300. }
  301. .entry {
  302. position: relative;
  303. clip-path: inset(0);
  304. margin: 2px;
  305. }
  306. .entry .thumb.error_handled.loaded.done {
  307. transition: transform 0.3s ease, filter 0.3s ease;
  308. transform-origin: center;
  309. }
  310. .entry:hover::after {
  311. content: '';
  312. position: absolute;
  313. top: 0;
  314. left: 0;
  315. right: 0;
  316. bottom: 0;
  317. box-shadow: inset 0 0 0 1px var(--thumbnail-border);
  318. pointer-events: none;
  319. }
  320. .entry:hover .thumb.error_handled.loaded.done {
  321. filter: brightness(85%);
  322. transform: scale(1.05);
  323. transition: transform 0.3s ease, filter 0.3s ease;
  324. }
  325. #back-to-top-btn {
  326. position: fixed;
  327. bottom: 40px;
  328. right: 3px;
  329. z-index: 99;
  330. border: none;
  331. outline: none;
  332. background-color: var(--backtotop-button);
  333. color: var(--input-text);
  334. cursor: pointer;
  335. padding: 15px;
  336. border-radius: 25%;
  337. align-items: center;
  338. justify-content: center;
  339. width: 80px;
  340. transition: opacity 0.3s ease, background-color 0.3s ease;
  341. font-size: 36px;
  342. opacity: 0;
  343. pointer-events: none;
  344. }
  345.  
  346. #back-to-top-btn:hover {
  347. background-color: var(--button-background);
  348. opacity: 1;
  349. }
  350. #back-to-top-btn:active {
  351. transform: scale(0.95);
  352. }
  353. ::selection {
  354. background: var(--text-highlight);
  355. }
  356. `;
  357. GM_addStyle('body { display: block !important; }');
  358.  
  359. var observer = new MutationObserver(function(mutations, observer) {
  360. if (document.body) {
  361. GM_addStyle(styleRules);
  362. createBackToTopButton();
  363. checkURL();
  364.  
  365. window.onscroll = function() {
  366. checkURL();
  367. var mybutton = document.getElementById("back-to-top-btn");
  368. if (document.body.scrollTop > document.body.scrollHeight * 0.1 ||
  369. document.documentElement.scrollTop > document.documentElement.scrollHeight * 0.1) {
  370. mybutton.style.opacity = "1";
  371. mybutton.style.pointerEvents = "auto";
  372. } else {
  373. mybutton.style.opacity = "0";
  374. mybutton.style.pointerEvents = "none";
  375. }
  376. };
  377.  
  378. setInterval(checkURL, 50);
  379.  
  380. observer.disconnect();
  381. }
  382. });
  383.  
  384. observer.observe(document.documentElement, { childList: true, subtree: true });
  385.  
  386. function usernameGlow(parentElement) {
  387. if (!parentElement) return;
  388.  
  389. for (const node of parentElement.childNodes) {
  390. if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
  391. const span = document.createElement('span');
  392. span.className = 'glowing-text';
  393. span.textContent = '★' + node.textContent.trim();
  394.  
  395. const space = document.createTextNode(' ');
  396.  
  397. parentElement.insertBefore(span, node);
  398. parentElement.insertBefore(space, node.nextSibling);
  399. parentElement.removeChild(node);
  400. break;
  401. }
  402. }
  403. }
  404.  
  405. const headerRightLoggedIn = document.querySelector('.header_right.logged_in');
  406. if (headerRightLoggedIn) {
  407. const headerChild = headerRightLoggedIn.querySelector('.header_child');
  408. usernameGlow(headerChild);
  409. }
  410.  
  411. })();