SigMod Client (Macros)

Ultimate Sigmally-Agar.io mod: macros, friends, tags, themes, visuals & more!

  1. // ==UserScript==
  2. // @name SigMod Client (Macros)
  3. // @version 10.2.1
  4. // @description Ultimate Sigmally-Agar.io mod: macros, friends, tags, themes, visuals & more!
  5. // @description:de Ultimatives Sigmally-Agar.io-Mod: Makros, Freunde, Tags, Themes, Visuals & mehr!
  6. // @description:es Mod definitivo de Sigmally-Agar.io: macros, amigos, etiquetas, temas, visuales ¡y más!
  7. // @description:pt Mod definitivo do Sigmally-Agar.io: macros, amigos, tags, temas, visuais e mais!
  8. // @description:ru Лучший мод для Sigmally-Agar.io: макросы, друзья, теги, темы, визуал и многое другое!
  9. // @description:tr En iyi Sigmally-Agar.io modu: makrolar, arkadaşlar, etiketler, temalar, görseller ve fazlası!
  10. // @author Cursed
  11. // @match https://*.sigmally.com/*
  12. // @icon https://czrsd.com/static/sigmod/SigMod25-rounded.png
  13. // @run-at document-end
  14. // @license MIT
  15. // @grant none
  16. // @namespace https://greatest.deepsurf.us/users/981958
  17. // @homepageURL https://mod.czrsd.com/
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. 'use strict';
  22. const version = 10;
  23. const serverVersion = '4.0.3';
  24. const storageVersion = 1.0;
  25. const storageName = 'SigModClient-settings';
  26. const headerAnim = 'https://czrsd.com/static/sigmod/sigmodclient.gif';
  27.  
  28. const libs = {
  29. chart: 'https://cdn.jsdelivr.net/npm/chart.js',
  30. colorPicker: {
  31. js: 'https://unpkg.com/alwan/dist/js/alwan.min.js',
  32. css: 'https://unpkg.com/alwan/dist/css/alwan.min.css',
  33. },
  34. jszip: 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js',
  35. };
  36.  
  37. const defaultSettings = {
  38. storageVersion,
  39. macros: {
  40. feedSpeed: 40,
  41. keys: {
  42. rapidFeed: 'w',
  43. respawn: 'b',
  44. location: 'y',
  45. saveImage: null,
  46. splits: {
  47. double: 'd',
  48. triple: 'f',
  49. quad: 'g',
  50. doubleTrick: null,
  51. selfTrick: null,
  52. },
  53. line: {
  54. horizontal: 's',
  55. vertical: 't',
  56. fixed: null,
  57. instantSplit: 0,
  58. },
  59. toggle: {
  60. menu: 'v',
  61. chat: 'z',
  62. names: null,
  63. skins: null,
  64. autoRespawn: null,
  65. },
  66. },
  67. mouse: {
  68. left: null,
  69. right: null,
  70. },
  71. },
  72. game: {
  73. font: 'Ubuntu',
  74. borderColor: null,
  75. foodColor: null,
  76. cellColor: null,
  77. virusImage: '/assets/images/viruses/2.png',
  78. shortenNames: false,
  79. removeOutlines: false,
  80. skins: {
  81. original: null,
  82. replacement: null,
  83. },
  84. map: {
  85. color: null,
  86. image: '',
  87. },
  88. name: {
  89. color: null,
  90. gradient: {
  91. enabled: false,
  92. left: null,
  93. right: null,
  94. },
  95. },
  96. },
  97. themes: {
  98. current: 'Dark',
  99. custom: [],
  100. inputBorderRadius: null,
  101. menuBorderRadius: null,
  102. inputBorder: '1px',
  103. hideDiscordBtns: false,
  104. hideLangs: false,
  105. },
  106. settings: {
  107. tag: null,
  108. savedNames: [],
  109. autoRespawn: false,
  110. playTimer: false,
  111. mouseTracker: false,
  112. autoClaimCoins: false,
  113. showChallenges: false,
  114. deathScreenPos: 'center',
  115. removeShopPopup: false,
  116. },
  117. chat: {
  118. bgColor: '#00000040',
  119. textColor: '#ffffff',
  120. compact: false,
  121. themeColor: '#8a25e5',
  122. showTime: true,
  123. showNameColors: true,
  124. showClientChat: false,
  125. showChatButtons: true,
  126. blurTag: false,
  127. locationText: '{pos}',
  128. },
  129. modAccount: {
  130. authorized: false,
  131. },
  132. };
  133.  
  134. let modSettings;
  135. const stored = localStorage.getItem(storageName);
  136. try {
  137. modSettings = stored ? JSON.parse(stored) : defaultSettings;
  138. } catch (e) {
  139. modSettings = defaultSettings;
  140. }
  141.  
  142. // really rare cases, but in case the storage structure changes completely again
  143. if (
  144. !modSettings.storageVersion ||
  145. modSettings.storageVersion !== storageVersion
  146. ) {
  147. localStorage.removeItem(storageName);
  148. location.reload();
  149. }
  150.  
  151. if (!stored) updateStorage();
  152.  
  153. // intercept fetches
  154. let fetchedUser = 0;
  155. const originalFetch = window.fetch;
  156.  
  157. window.fetch = new Proxy(originalFetch, {
  158. async apply(target, thisArg, argumentsList) {
  159. const [url] = argumentsList;
  160. const response = await target.apply(thisArg, argumentsList);
  161.  
  162. if (typeof url === 'string') {
  163. if (url.includes('/server/auth')) {
  164. const data = await response.clone().json();
  165. if (data) mods.handleGoogleAuth(data.body?.user);
  166. }
  167. }
  168.  
  169. return response;
  170. },
  171. });
  172.  
  173. // for development
  174. let isDev = false;
  175. let port = 3001;
  176.  
  177. // global sigmod
  178. window.sigmod = {
  179. version,
  180. server_version: serverVersion,
  181. storageName,
  182. settings: modSettings,
  183. };
  184.  
  185. // Global gameSettings object to store the Sigmally WebSocket, User instance and playing status
  186. /*
  187. * @typedef {Object} User
  188. * @property {string} _id
  189. * @property {number} boost
  190. * @property {Object.<string, number>} cards
  191. * @property {string} clan
  192. * @property {string} createTime
  193. * @property {string} email
  194. * @property {number} exp
  195. * @property {string} fullName
  196. * @property {string} givenName
  197. * @property {number} gold
  198. * @property {string} googleID
  199. * @property {number} hourlyTime
  200. * @property {string} imageURL
  201. * @property {any[]} lastSkinUsed
  202. * @property {number} level
  203. * @property {number} nextLevel
  204. * @property {number} progress
  205. * @property {number} seasonExp
  206. * @property {Object.<string, any>} sigma
  207. * @property {string[]} skins
  208. * @property {number} subscription
  209. * @property {string} updateTime
  210. * @property {string} token
  211. *
  212. * @property {WebSocket} ws
  213. * @property {User} user
  214. * @property {boolean} isPlaying
  215. */
  216. window.gameSettings = {
  217. ws: null,
  218. user: null,
  219. isPlaying: false,
  220. };
  221.  
  222. // --------- HELPER FUNCTIONS --------- \\
  223. // --- General
  224. // --- Colors
  225. // --- Game
  226. // --- Time
  227. // --- (Coordinates)
  228.  
  229. function updateStorage() {
  230. localStorage.setItem(storageName, JSON.stringify(modSettings));
  231. }
  232.  
  233. const byId = (id) => document.getElementById(id);
  234.  
  235. const debounce = (func, delay) => {
  236. let timeoutId;
  237. return function (...args) {
  238. clearTimeout(timeoutId);
  239. timeoutId = setTimeout(() => {
  240. func.apply(this, args);
  241. }, delay);
  242. };
  243. };
  244.  
  245. const wait = async (ms) => {
  246. return new Promise((r) => setTimeout(r, ms));
  247. };
  248.  
  249. const noXSS = (text) => {
  250. return text
  251. .replace(/&/g, '&amp;')
  252. .replace(/</g, '&lt;')
  253. .replace(/>/g, '&gt;')
  254. .replace(/"/g, '&quot;')
  255. .replace(/'/g, '&#039;');
  256. };
  257.  
  258. // generate random string
  259. const rdmString = (length) => {
  260. return [...Array(length)]
  261. .map(() => Math.random().toString(36).charAt(2))
  262. .join('');
  263. };
  264.  
  265. const textEncoder = new TextEncoder();
  266. const textDecoder = new TextDecoder();
  267.  
  268. // --------- Colors --------- //
  269.  
  270. // rgba values to hex color code
  271. const RgbaToHex = (code) => {
  272. const rgbaValues = code.match(/\d+/g);
  273. const [r, g, b] = rgbaValues.slice(0, 3);
  274. return `#${Number(r).toString(16).padStart(2, '0')}${Number(g)
  275. .toString(16)
  276. .padStart(2, '0')}${Number(b).toString(16).padStart(2, '0')}`;
  277. };
  278.  
  279. // --------- Game --------- //
  280.  
  281. const menuClosed = () => {
  282. const menuWrapper = byId('menu-wrapper');
  283.  
  284. return menuWrapper.style.display === 'none';
  285. };
  286.  
  287. const isDeadUI = () => {
  288. const __line2 = byId('__line2');
  289. return !__line2.classList.contains('line--hidden');
  290. };
  291.  
  292. const getGameMode = () => {
  293. const gameMode = byId('gamemode');
  294. if (!gameMode.value) {
  295. return 'Tourney';
  296. }
  297. const options = Object.values(gameMode.querySelectorAll('option'));
  298. const selectedOption = options.filter(
  299. (option) => option.value === gameMode.value
  300. )[0];
  301. return selectedOption.textContent.split(' ')[0];
  302. };
  303.  
  304. function keypress(key, keycode) {
  305. const keyDownEvent = new KeyboardEvent('keydown', {
  306. key: key,
  307. code: keycode,
  308. });
  309. const keyUpEvent = new KeyboardEvent('keyup', {
  310. key: key,
  311. code: keycode,
  312. });
  313.  
  314. window.dispatchEvent(keyDownEvent);
  315. window.dispatchEvent(keyUpEvent);
  316. }
  317.  
  318. const getCoordinates = (border, gridCount = 5) => {
  319. const { left, top, width } = border;
  320. const gridSize = width / gridCount;
  321. const coordinates = {};
  322.  
  323. for (let i = 0; i < gridCount; ++i) {
  324. for (let j = 0; j < gridCount; ++j) {
  325. const label = String.fromCharCode(65 + i) + (j + 1);
  326.  
  327. coordinates[label] = {
  328. min: { x: left + i * gridSize, y: top + j * gridSize },
  329. max: {
  330. x: left + (i + 1) * gridSize,
  331. y: top + (j + 1) * gridSize,
  332. },
  333. };
  334. }
  335. }
  336.  
  337. return coordinates;
  338. };
  339.  
  340. // time formatters
  341. const prettyTime = {
  342. fullDate: (dateTimestamp, time = false) => {
  343. const date = new Date(dateTimestamp);
  344. const day = String(date.getDate()).padStart(2, '0');
  345. const month = String(date.getMonth() + 1).padStart(2, '0');
  346. const year = date.getFullYear();
  347. const formattedDate = `${day}.${month}.${year}`;
  348.  
  349. if (time) {
  350. const hours = String(date.getHours()).padStart(2, '0');
  351. const minutes = String(date.getMinutes()).padStart(2, '0');
  352. const seconds = String(date.getSeconds()).padStart(2, '0');
  353. return `${hours}:${minutes}:${seconds} ${formattedDate}`;
  354. }
  355.  
  356. return formattedDate;
  357. },
  358. am_pm: (date) => {
  359. if (!date) return '';
  360.  
  361. const d = new Date(date);
  362. const hours = d.getHours();
  363. const minutes = String(d.getMinutes()).padStart(2, '0');
  364. const ampm = hours >= 12 ? 'PM' : 'AM';
  365. const formattedHours = (hours % 12 || 12)
  366. .toString()
  367. .padStart(2, '0');
  368.  
  369. return `${formattedHours}:${minutes} ${ampm}`;
  370. },
  371. time_ago: (timestamp, isIso = false) => {
  372. if (!timestamp) return '';
  373. const currentTime = new Date();
  374. const elapsedTime = isIso
  375. ? currentTime - new Date(timestamp)
  376. : currentTime - timestamp;
  377.  
  378. const seconds = Math.floor(elapsedTime / 1000);
  379. const minutes = Math.floor(seconds / 60);
  380. const hours = Math.floor(minutes / 60);
  381. const days = Math.floor(hours / 24);
  382. const years = Math.floor(days / 365);
  383.  
  384. if (years > 0) {
  385. return years === 1 ? '1 year ago' : `${years} years ago`;
  386. } else if (days > 0) {
  387. return days === 1 ? '1 day ago' : `${days} days ago`;
  388. } else if (hours > 0) {
  389. return hours === 1 ? '1 hour ago' : `${hours} hours ago`;
  390. } else if (minutes > 0) {
  391. return minutes === 1
  392. ? '1 minute ago'
  393. : `${minutes} minutes ago`;
  394. } else {
  395. return seconds <= 1 ? '1s>' : `${seconds}s ago`;
  396. }
  397. },
  398. getTimeLeft(timestamp) {
  399. let totalSeconds = Math.max(
  400. 0,
  401. Math.floor((timestamp - Date.now()) / 1000)
  402. );
  403. const timeUnits = [
  404. ['d', 86400],
  405. ['h', 3600],
  406. ['m', 60],
  407. ['s', 1],
  408. ];
  409. let result = '';
  410.  
  411. for (const [unit, seconds] of timeUnits) {
  412. if (totalSeconds >= seconds) {
  413. const value = Math.floor(totalSeconds / seconds);
  414. totalSeconds %= seconds;
  415. result += `${value}${unit}`;
  416. }
  417. }
  418.  
  419. return result || '0s';
  420. },
  421. };
  422.  
  423. const getStringUTF8 = (view, o) => {
  424. const startOffset = o;
  425.  
  426. while (view.getUint8(o) !== 0 && o < view.byteLength) {
  427. o++;
  428. }
  429.  
  430. return o > startOffset
  431. ? [
  432. new TextDecoder().decode(
  433. new DataView(view.buffer, startOffset, o - startOffset)
  434. ),
  435. o + 1,
  436. ]
  437. : ['', o + 1];
  438. };
  439.  
  440. // --------- END HELPER FUNCTIONS --------- //
  441.  
  442. let client = null;
  443. let freezepos = false;
  444.  
  445. // --------- Sigmally WebSocket Handler --------- //
  446. class SigWsHandler {
  447. handshake = false;
  448. C = new Uint8Array(256);
  449. R = new Uint8Array(256);
  450.  
  451. constructor() {
  452. this.overrideWebSocketSend();
  453. }
  454.  
  455. overrideWebSocketSend() {
  456. const handler = this;
  457.  
  458. window.WebSocket = new Proxy(window.WebSocket, {
  459. construct(target, args) {
  460. const wsInstance = new target(...args);
  461.  
  462. if (args[0].includes('sigmally.com')) {
  463. handler.setupWebSocket(wsInstance);
  464. }
  465.  
  466. return wsInstance;
  467. },
  468. });
  469. }
  470.  
  471. setupWebSocket(ws) {
  472. window.gameSettings.ws = ws;
  473.  
  474. // if 'save' is in localstorage, it indicates that you are logged in to Google; if that is the case, it
  475. // will load the client after the authorization to load the modClient correctly.
  476. // This loads the client instantly if you're not logged in to Google
  477. if (!localStorage.getItem('save') && !client) {
  478. client = new modClient();
  479. }
  480.  
  481. ws.addEventListener('close', () => this.handleWebSocketClose());
  482. ws.sendPacket = this.sendPacket.bind(this);
  483.  
  484. window.sendPlay = this.sendPlay.bind(this);
  485. window.sendChat = this.sendChat.bind(this);
  486. window.sendMouseMove = this.sendMouseMove.bind(this);
  487.  
  488. const originalSend = ws.send.bind(ws);
  489. ws.send = (data) => {
  490. try {
  491. const arrayBuffer =
  492. data instanceof ArrayBuffer ? data : data.buffer;
  493. const view = new DataView(arrayBuffer);
  494.  
  495. const r = view.getUint8(0);
  496.  
  497. if (!window.sigfix && freezepos && this.R[r] === 0x10)
  498. return;
  499.  
  500. originalSend(data);
  501. } catch (e) {
  502. console.error(e);
  503. }
  504. };
  505.  
  506. ws.addEventListener('message', (e) => this.handleMessage(e));
  507. }
  508.  
  509. performHandshake(view, _o) {
  510. let [_, o] = getStringUTF8(view, _o);
  511.  
  512. this.C.set(new Uint8Array(view.buffer.slice(o, o + 256)));
  513.  
  514. for (const i in this.C) this.R[this.C[i]] = ~~i;
  515.  
  516. this.handshake = true;
  517. }
  518.  
  519. handleWebSocketClose() {
  520. this.handshake = false;
  521.  
  522. playerPosition.x = null;
  523. playerPosition.y = null;
  524. byId('mod-messages').innerHTML = '';
  525. setTimeout(mods.showOverlays, 500);
  526. }
  527.  
  528. sendPacket(packet) {
  529. if (!window.gameSettings.ws) {
  530. console.error('WebSocket is not defined.');
  531. return;
  532. }
  533.  
  534. window.gameSettings.ws.send(packet);
  535. }
  536.  
  537. sendPlay(playData) {
  538. const { sigfix } = window;
  539. if (sigfix) {
  540. sigfix.net.play(sigfix.world.selected, JSON.parse(playData));
  541. return;
  542. }
  543.  
  544. const json = JSON.stringify(playData);
  545. const encoded = textEncoder.encode(json);
  546. const view = new DataView(new ArrayBuffer(encoded.length + 2));
  547.  
  548. view.setUint8(0, this.C[0x00]);
  549.  
  550. for (let i = 0; i < encoded.byteLength; ++i) {
  551. view.setUint8(1 + i, encoded[i]);
  552. }
  553.  
  554. this.sendPacket(view);
  555. }
  556.  
  557. sendChat(text) {
  558. if (window.sigfix) {
  559. window.sigfix.net.chat(text);
  560. return;
  561. }
  562. if (mods.aboveRespawnLimit && text === mods.respawnCommand) return;
  563.  
  564. const encoded = textEncoder.encode(text);
  565. const view = new DataView(new ArrayBuffer(encoded.byteLength + 3));
  566.  
  567. view.setUint8(0, this.C[0x63]);
  568. for (let i = 0; i < encoded.byteLength; ++i) {
  569. view.setUint8(2 + i, encoded[i]);
  570. }
  571.  
  572. this.sendPacket(view);
  573. }
  574.  
  575. sendMouseMove(x, y) {
  576. const { sigfix } = window;
  577. if (sigfix) {
  578. sigfix.net.move(sigfix.world.selected, x, y);
  579. return;
  580. }
  581. const view = new DataView(new ArrayBuffer(13));
  582.  
  583. view.setUint8(0, this.C[0x10]);
  584. view.setInt32(1, x, true);
  585. view.setInt32(5, y, true);
  586.  
  587. this.sendPacket(view);
  588. }
  589.  
  590. removePlayer(id) {
  591. const index = this.cells.players.indexOf(id);
  592. if (index !== -1) {
  593. this.cells.players.splice(index, 1);
  594. }
  595. }
  596.  
  597. handleMessage(e) {
  598. try {
  599. const view = new DataView(e.data);
  600. let o = 0;
  601.  
  602. if (!this.handshake) return this.performHandshake(view, o);
  603.  
  604. const r = view.getUint8(o++);
  605.  
  606. if (this.R[r] === 0x63) this.handleChatMessage(view, o);
  607. if (this.R[r] === 0x40) this.updateBorder(view, o);
  608. } catch (e) {}
  609. }
  610.  
  611. handleChatMessage(view, o) {
  612. o += 1; // skip flags
  613. const rgb = Array.from(
  614. { length: 3 },
  615. () => view.getUint8(o++) / 255
  616. );
  617. const hex = `#${rgb
  618. .map((c) =>
  619. Math.floor(c * 255)
  620. .toString(16)
  621. .padStart(2, '0')
  622. )
  623. .join('')}`;
  624.  
  625. let name, message;
  626. [name, o] = getStringUTF8(view, o);
  627. [message, o] = getStringUTF8(view, o);
  628.  
  629. if (!name.trim()) name = 'Unnamed';
  630.  
  631. if (
  632. !mods.mutedUsers.includes(name) &&
  633. !mods.spamMessage(name, message) &&
  634. !modSettings.chat.showClientChat
  635. ) {
  636. mods.updateChat({
  637. color: modSettings.chat.showNameColors ? hex : '#fafafa',
  638. name,
  639. message,
  640. time: modSettings.chat.showTime ? Date.now() : null,
  641. });
  642. }
  643. }
  644.  
  645. updateBorder(view, o) {
  646. const [left, top, right, bottom] = [
  647. view.getFloat64(o, true),
  648. view.getFloat64(o + 8, true),
  649. view.getFloat64(o + 16, true),
  650. view.getFloat64(o + 24, true),
  651. ];
  652.  
  653. mods.border = {
  654. left,
  655. top,
  656. right,
  657. bottom,
  658. width: right - left,
  659. height: bottom - top,
  660. };
  661. }
  662. }
  663.  
  664. class SigFixHandler {
  665. constructor() {
  666. this.lastHadCells = false;
  667. this.checkInterval = null;
  668. this.updatePosInterval = null;
  669. this.sendPosInterval = null;
  670. this.init();
  671. }
  672.  
  673. overrideMoveFunction() {
  674. if (!window.sigfix?.net?.move) return;
  675.  
  676. const originalMove = window.sigfix.net.move;
  677. let isHandlingFreeze = false;
  678.  
  679. window.sigfix.net.move = (...args) => {
  680. if (freezepos && !isHandlingFreeze) {
  681. isHandlingFreeze = true;
  682. originalMove.call(this, playerPosition.x, playerPosition.y);
  683. isHandlingFreeze = false;
  684. return;
  685. }
  686. return originalMove.apply(this, args);
  687. };
  688. }
  689.  
  690. calculatePlayerPosition() {
  691. let ownX = 0,
  692. ownY = 0,
  693. ownN = 0;
  694. const ownedCells =
  695. window.sigfix.world.views.get(window.sigfix.world.selected)
  696. ?.owned || [];
  697.  
  698. ownedCells.forEach((id) => {
  699. const cell = window.sigfix.world.cells.get(id);
  700. const frame = window.sigfix.world.synchronized
  701. ? cell?.merged
  702. : cell?.views.get(window.sigfix.world.selected)?.frames[0];
  703. if (frame) {
  704. ownN++;
  705. ownX += frame.nx;
  706. ownY += frame.ny;
  707. }
  708. });
  709.  
  710. return ownN > 0 ? { x: ownX / ownN, y: ownY / ownN } : null;
  711. }
  712.  
  713. updatePlayerPos() {
  714. const newPos = this.calculatePlayerPosition();
  715. if (newPos) {
  716. playerPosition.x = newPos.x;
  717. playerPosition.y = newPos.y;
  718. this.lastHadCells = true;
  719. } else if (this.lastHadCells) {
  720. playerPosition.x = null;
  721. playerPosition.y = null;
  722. this.sendPlayerPos();
  723. this.lastHadCells = false;
  724. }
  725. }
  726.  
  727. sendPlayerPos() {
  728. if (
  729. playerPosition.x !== null &&
  730. playerPosition.y !== null &&
  731. client?.ws?.readyState === 1 &&
  732. modSettings.settings.tag
  733. ) {
  734. client.send({
  735. type: 'position',
  736. content: { x: playerPosition.x, y: playerPosition.y },
  737. });
  738. }
  739. }
  740.  
  741. startIntervals() {
  742. this.updatePosInterval = setInterval(
  743. this.updatePlayerPos.bind(this)
  744. );
  745. this.sendPosInterval = setInterval(
  746. this.sendPlayerPos.bind(this),
  747. 300
  748. );
  749. }
  750.  
  751. checkSigFix() {
  752. if (window.sigfix) {
  753. this.startIntervals();
  754. this.overrideMoveFunction();
  755. clearInterval(this.checkInterval);
  756. }
  757. }
  758.  
  759. init() {
  760. this.checkInterval = setInterval(() => {
  761. if (window.sigfix) {
  762. clearInterval(this.checkInterval);
  763. this.startIntervals();
  764. this.overrideMoveFunction();
  765. } else {
  766. clearInterval(this.checkInterval);
  767. requestAnimationFrame(window.checkPlaying);
  768. }
  769. }, 100);
  770. }
  771. }
  772.  
  773. new SigFixHandler();
  774.  
  775. // --------- Mod Client --------- //
  776. class modClient {
  777. constructor() {
  778. this.ws = null;
  779. this.wsUrl = isDev
  780. ? `ws://localhost:${port}/ws`
  781. : 'wss://mod.czrsd.com/ws';
  782.  
  783. this.retries = 0;
  784. this.maxRetries = 4;
  785. this.updateAvailable = false;
  786.  
  787. this.id = null;
  788. this.connectedAmount = 0;
  789.  
  790. this.connect();
  791. }
  792.  
  793. connect() {
  794. this.ws = new WebSocket(this.wsUrl);
  795. this.ws.binaryType = 'arraybuffer';
  796. window.sigmod.ws = this.ws;
  797.  
  798. this.ws.addEventListener('open', this.onOpen.bind(this));
  799. this.ws.addEventListener('close', this.onClose.bind(this));
  800. this.ws.addEventListener('message', this.onMessage.bind(this));
  801. this.ws.addEventListener('error', this.onError.bind(this));
  802. }
  803.  
  804. async onOpen() {
  805. this.connectedAmount++;
  806.  
  807. this.updateClientInfo();
  808.  
  809. // Send nick if client got disconnected more than one time
  810. if (this.connectedAmount > 1) {
  811. client.send({
  812. type: 'update-nick',
  813. content: mods.nick,
  814. });
  815. }
  816. }
  817.  
  818. updateClientInfo() {
  819. this.updateTagInfo();
  820.  
  821. this.send({
  822. type: 'server-changed',
  823. content: getGameMode(),
  824. });
  825. this.send({
  826. type: 'version',
  827. content: serverVersion,
  828. });
  829. }
  830.  
  831. updateTagInfo() {
  832. const tagElement = document.querySelector('#tag');
  833. const tagText = document.querySelector('.tagText');
  834. const tagValue = this.getTagFromUrl();
  835.  
  836. if (tagValue) {
  837. modSettings.settings.tag = tagValue;
  838. updateStorage();
  839. tagElement.value = tagValue;
  840. tagText.innerText = `Tag: ${tagValue}`;
  841. this.send({
  842. type: 'update-tag',
  843. content: modSettings.settings.tag,
  844. });
  845. } else if (modSettings.settings.tag) {
  846. tagElement.value = modSettings.settings.tag;
  847. tagText.innerText = `Tag: ${modSettings.settings.tag}`;
  848. this.send({
  849. type: 'update-tag',
  850. content: modSettings.settings.tag,
  851. });
  852. }
  853. }
  854.  
  855. getTagFromUrl() {
  856. const urlParams = new URLSearchParams(window.location.search);
  857. const tagValue = urlParams.get('tag');
  858. return tagValue ? tagValue.replace(/\/$/, '') : null;
  859. }
  860.  
  861. onClose() {
  862. if (this.updateAvailable) return;
  863.  
  864. this.retries++;
  865. if (this.retries > this.maxRetries)
  866. throw new Error('SigMod server down.');
  867.  
  868. setTimeout(() => this.connect(), 2000); // auto reconnect with delay
  869. }
  870.  
  871. onMessage(event) {
  872. const message = this.parseMessage(event.data);
  873. if (!message || !message.type) return;
  874.  
  875. switch (message.type) {
  876. case 'sid':
  877. this.handleSidMessage(message.content);
  878. break;
  879. case 'ping':
  880. this.handlePingMessage();
  881. break;
  882. case 'minimap-data':
  883. mods.updData(message.content);
  884. break;
  885. case 'chat-message':
  886. this.handleChatMessage(message.content);
  887. break;
  888. case 'private-message':
  889. mods.updatePrivateChat(message.content);
  890. break;
  891. case 'update-available':
  892. this.handleUpdateAvailable(message.content);
  893. break;
  894. case 'alert':
  895. mods.handleAlert(message.content);
  896. break;
  897. case 'tournament-preview':
  898. mods.tData = message.content;
  899. mods.showTournament(message.content);
  900. break;
  901. case 'tournament-message':
  902. mods.updateChat({
  903. name: '[TOURNAMENT]',
  904. message: message.content,
  905. time: modSettings.chat.showTime ? Date.now() : null,
  906. });
  907. break;
  908. case 'tournament-session':
  909. mods.tournamentSession(message.content);
  910. break;
  911. case 'get-score':
  912. mods.getScore(message.content);
  913. break;
  914. case 'round-end':
  915. mods.roundEnd(message.content);
  916. break;
  917. case 'round-ready':
  918. mods.tournamentReady(message.content);
  919. break;
  920. case 'tournament-data':
  921. mods.handleTournamentData(message.content);
  922. break;
  923. case 'error':
  924. mods.modAlert(message.content.message, 'danger');
  925. break;
  926. default:
  927. console.error('Unknown message type:', message.type);
  928. }
  929. }
  930.  
  931. onError(event) {
  932. console.error('WebSocket error:', event);
  933. }
  934.  
  935. send(data) {
  936. if (!data || this.ws.readyState !== 1) return;
  937. const binaryData = textEncoder.encode(JSON.stringify(data));
  938. this.ws.send(binaryData);
  939. }
  940.  
  941. parseMessage(data) {
  942. try {
  943. const stringData = textDecoder.decode(new Uint8Array(data));
  944. return JSON.parse(stringData);
  945. } catch (error) {
  946. console.error('Failed to parse message:', error);
  947. return null;
  948. }
  949. }
  950.  
  951. handleSidMessage(content) {
  952. this.id = content;
  953. if (!modSettings.modAccount.authorized) return;
  954.  
  955. setTimeout(() => {
  956. mods.auth(content);
  957. }, 1000);
  958. }
  959.  
  960. handlePingMessage() {
  961. mods.ping.latency = Date.now() - mods.ping.start;
  962. mods.ping.end = Date.now();
  963. byId(
  964. 'clientPing'
  965. ).innerHTML = `Client Ping: ${mods.ping.latency}ms`;
  966. }
  967.  
  968. handleChatMessage(content) {
  969. if (!content) return;
  970. let { admin, mod, vip, name, message, color } = content;
  971.  
  972. name = this.formatChatName(admin, mod, vip, name);
  973.  
  974. mods.updateChat({
  975. admin,
  976. mod,
  977. color,
  978. name,
  979. message,
  980. time: modSettings.chat.showTime ? Date.now() : null,
  981. });
  982. }
  983.  
  984. formatChatName(admin, mod, vip, name) {
  985. if (admin) name = '[Owner] ' + name;
  986. if (mod) name = '[Mod] ' + name;
  987. if (vip) name = '[VIP] ' + name;
  988. if (!name) name = 'Unnamed';
  989. return name;
  990. }
  991.  
  992. handleUpdateAvailable(content) {
  993. mods.playBtn.setAttribute('disabled', 'disabled');
  994. byId('spectate-btn').setAttribute('disabled', 'disabled');
  995. this.updateAvailable = true;
  996. this.createModAlert(content);
  997. }
  998.  
  999. createModAlert(content) {
  1000. const modAlert = document.createElement('div');
  1001. modAlert.classList.add('modAlert');
  1002. modAlert.innerHTML = `
  1003. <span>You are using an old mod version. Please update.</span>
  1004. <div class="flex centerXY g-5">
  1005. <button
  1006. class="modButton"
  1007. style="width: 100%"
  1008. onclick="window.open('${content}')"
  1009. >Update</button>
  1010. </div>
  1011. `;
  1012. document.body.append(modAlert);
  1013. }
  1014. }
  1015.  
  1016. function randomPos() {
  1017. let eventOptions = {
  1018. clientX: Math.floor(Math.random() * window.innerWidth),
  1019. clientY: Math.floor(Math.random() * window.innerHeight),
  1020. bubbles: true,
  1021. cancelable: true,
  1022. };
  1023.  
  1024. let event = new MouseEvent('mousemove', eventOptions);
  1025.  
  1026. document.dispatchEvent(event);
  1027. }
  1028.  
  1029. let moveInterval = setInterval(randomPos);
  1030. setTimeout(() => clearInterval(moveInterval), 600);
  1031.  
  1032. // Disable chat before game settings actually load
  1033. localStorage.setItem(
  1034. 'settings',
  1035. JSON.stringify({
  1036. ...JSON.parse(localStorage.getItem('settings')),
  1037. showChat: false,
  1038. })
  1039. );
  1040.  
  1041. function addSettings() {
  1042. const gameSettings = document.querySelector('.checkbox-grid');
  1043. if (!gameSettings) return;
  1044.  
  1045. const div = document.createElement('div');
  1046. div.innerHTML = `
  1047. <input type="checkbox" id="showNames">
  1048. <label>Names</label>
  1049. <input type="checkbox" id="showSkins">
  1050. <label>Skins</label>
  1051. <input type="checkbox" id="autoRespawn">
  1052. <label>Auto Respawn</label>
  1053. <input type="checkbox" id="autoClaimCoins">
  1054. <label>Auto claim coins</label>
  1055. <input type="checkbox" id="showPosition">
  1056. <label>Position</label>
  1057. `;
  1058. while (div.children.length > 0) gameSettings.append(div.children[0]);
  1059. }
  1060. addSettings();
  1061.  
  1062. async function checkDiscordLogin() {
  1063. // popup window from discord login
  1064. const urlParams = new URLSearchParams(window.location.search);
  1065. let accessToken = urlParams.get('access_token');
  1066. let refreshToken = urlParams.get('refresh_token');
  1067.  
  1068. if (!accessToken || !refreshToken) return;
  1069.  
  1070. const url = isDev
  1071. ? `http://localhost:${port}/discord/login/`
  1072. : 'https://mod.czrsd.com/discord/login/';
  1073.  
  1074. const overlay = document.createElement('div');
  1075. overlay.style = `position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #050505; display: flex; justify-content: center; align-items: center; z-index: 999999`;
  1076. overlay.innerHTML = `
  1077. <span style="font-size: 5rem; color: #fafafa;">Login...</span>
  1078. `;
  1079. document.body.append(overlay);
  1080.  
  1081. setTimeout(async () => {
  1082. if (refreshToken.endsWith('/')) {
  1083. refreshToken = refreshToken.substring(
  1084. 0,
  1085. refreshToken.length - 1
  1086. );
  1087. } else {
  1088. return;
  1089. }
  1090.  
  1091. await fetch(
  1092. `${url}?accessToken=${accessToken}&refreshToken=${refreshToken}`,
  1093. {
  1094. method: 'GET',
  1095. credentials: 'include',
  1096. }
  1097. );
  1098.  
  1099. modSettings.modAccount.authorized = true;
  1100. updateStorage();
  1101. window.close();
  1102. }, 500);
  1103. }
  1104. checkDiscordLogin();
  1105.  
  1106. let mods = {};
  1107.  
  1108. let playerPosition = { x: null, y: null };
  1109. let lastPosTime = 0;
  1110.  
  1111. function Mod() {
  1112. this.nick = null;
  1113. this.profile = {};
  1114. this.friends_settings = window.sigmod.friends_settings = {};
  1115. this.friend_names = window.sigmod.friend_names = new Set();
  1116.  
  1117. this.splitKey = {
  1118. keyCode: 32,
  1119. code: 'Space',
  1120. cancelable: true,
  1121. composed: true,
  1122. isTrusted: true,
  1123. which: 32,
  1124. };
  1125.  
  1126. this.ping = {
  1127. latency: NaN,
  1128. intervalId: null,
  1129. start: null,
  1130. end: null,
  1131. };
  1132.  
  1133. this.dayTimer = null;
  1134. this.challenges = [];
  1135.  
  1136. this.gameStats = {};
  1137. this.chartInstance = null;
  1138. this.chartOverlay = null;
  1139.  
  1140. this.scrolling = false;
  1141. this.onContext = false;
  1142. this.mouseDown = false;
  1143.  
  1144. this.mouseX = 0;
  1145. this.mouseY = 0;
  1146.  
  1147. this.renderedMessages = 0;
  1148. this.maxChatMessages = 200;
  1149. this.mutedUsers = [];
  1150. this.blockedChatData = {
  1151. names: [],
  1152. messages: [],
  1153. };
  1154.  
  1155. this.respawnCommand = '/leaveworld';
  1156. this.aboveRespawnLimit = false;
  1157. this.cellSize = 0;
  1158. this.miniMapData = [];
  1159. this.border = {};
  1160.  
  1161. this.dbCache = null;
  1162.  
  1163. this.tourneyPassword = '';
  1164. this.lastOneStanding = false;
  1165.  
  1166. this.skins = [];
  1167.  
  1168. this.virusImageLoaded = false;
  1169. this.skinImageLoaded = false;
  1170.  
  1171. this.routes = {
  1172. discord: {
  1173. auth: isDev
  1174. ? 'https://discord.com/oauth2/authorize?client_id=1067097357780516874&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fdiscord%2Fcallback&scope=identify'
  1175. : 'https://discord.com/oauth2/authorize?client_id=1067097357780516874&response_type=code&redirect_uri=https%3A%2F%2Fmod.czrsd.com%2Fdiscord%2Fcallback&scope=identify',
  1176. },
  1177. };
  1178.  
  1179. // for SigMod specific
  1180. const base = isDev
  1181. ? `http://localhost:${port}`
  1182. : 'https://mod.czrsd.com';
  1183. const r = (path) => `${base}/${path}`;
  1184.  
  1185. this.appRoutes = {
  1186. badge: r('badge'),
  1187. signIn: (path) => r(path),
  1188. auth: r('auth'),
  1189. users: r('users'),
  1190. search: r('search'),
  1191. request: r('request'),
  1192. friends: r('me/friends'),
  1193. profile: (id) => r(`profile/${id}`),
  1194. myRequests: r('me/requests'),
  1195. handleRequest: r('me/handle'),
  1196. logout: r('logout'),
  1197. imgUpload: r('me/upload'),
  1198. removeAvatar: r('me/handle'),
  1199. editProfile: r('me/edit'),
  1200. delProfile: r('me/remove'),
  1201. updateSettings: r('me/update-settings'),
  1202. chatHistory: (id) => r(`me/chat/${id}`),
  1203. onlineUsers: r('onlineUsers'),
  1204. announcements: r('announcements'),
  1205. announcement: (id) => r(`announcement/${id}`),
  1206. fonts: r('fonts'),
  1207. blockedChatData: 'https://mod.czrsd.com/spam.json',
  1208. };
  1209. this.init();
  1210. }
  1211.  
  1212. Mod.prototype = {
  1213. get style() {
  1214. return `
  1215. @import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap');
  1216.  
  1217. .mod_menu {
  1218. position: absolute;
  1219. top: 0;
  1220. left: 0;
  1221. width: 100%;
  1222. height: 100vh;
  1223. background: rgba(0, 0, 0, .6);
  1224. z-index: 999999;
  1225. display: flex;
  1226. justify-content: center;
  1227. align-items: center;
  1228. color: #fff;
  1229. transition: all .3s ease;
  1230. }
  1231.  
  1232. .mod_menu * {
  1233. margin: 0;
  1234. padding: 0;
  1235. font-family: 'Ubuntu';
  1236. box-sizing: border-box;
  1237. }
  1238.  
  1239. .mod_menu_wrapper {
  1240. position: relative;
  1241. display: flex;
  1242. flex-direction: column;
  1243. width: 700px;
  1244. height: 500px;
  1245. background: #111;
  1246. border-radius: 12px;
  1247. overflow: hidden;
  1248. box-shadow: 0 5px 10px #000;
  1249. }
  1250.  
  1251. .mod_menu_header {
  1252. display: flex;
  1253. width: 100%;
  1254. position: relative;
  1255. height: 60px;
  1256. }
  1257.  
  1258. .mod_menu_header .header_img {
  1259. width: 100%;
  1260. height: 60px;
  1261. object-fit: cover;
  1262. object-position: center;
  1263. position: absolute;
  1264. }
  1265.  
  1266. .mod_menu_header button {
  1267. display: flex;
  1268. justify-content: center;
  1269. align-items: center;
  1270. position: absolute;
  1271. right: 10px;
  1272. top: 30px;
  1273. background: rgba(11, 11, 11, .7);
  1274. width: 42px;
  1275. height: 42px;
  1276. font-size: 16px;
  1277. transform: translateY(-50%);
  1278. }
  1279.  
  1280. .mod_menu_header button:hover {
  1281. background: rgba(11, 11, 11, .5);
  1282. }
  1283.  
  1284. .mod_menu_inner {
  1285. display: flex;
  1286. }
  1287.  
  1288. .mod_menu_navbar {
  1289. display: flex;
  1290. flex-direction: column;
  1291. gap: 10px;
  1292. min-width: 132px;
  1293. padding: 10px;
  1294. background: #181818;
  1295. height: 440px;
  1296. }
  1297.  
  1298. .mod_nav_btn, .modButton-black {
  1299. display: flex;
  1300. justify-content: space-evenly;
  1301. align-items: center;
  1302. padding: 5px;
  1303. background: #050505;
  1304. border-radius: 8px;
  1305. font-size: 16px;
  1306. border: 1px solid transparent;
  1307. outline: none;
  1308. width: 100%;
  1309. transition: all .3s ease;
  1310. }
  1311. label.modButton-black {
  1312. font-weight: 400;
  1313. cursor: pointer;
  1314. }
  1315.  
  1316. .modButton-black[disabled] {
  1317. background: #333;
  1318. cursor: default;
  1319. }
  1320.  
  1321. .mod_selected {
  1322. border: 1px solid rgba(89, 89, 89, .9);
  1323. }
  1324.  
  1325. .mod_nav_btn img {
  1326. width: 22px;
  1327. }
  1328.  
  1329. .mod_menu_content {
  1330. width: 100%;
  1331. padding: 10px;
  1332. position: relative;
  1333. max-width: 568px;
  1334. }
  1335.  
  1336. .mod_tab {
  1337. width: 100%;
  1338. height: 100%;
  1339. display: flex;
  1340. flex-direction: column;
  1341. gap: 5px;
  1342. overflow-y: auto;
  1343. overflow-x: hidden;
  1344. max-height: 420px;
  1345. opacity: 1;
  1346. transition: all .2s ease;
  1347. }
  1348. .modColItems {
  1349. display: flex;
  1350. flex-direction: column;
  1351. align-items: center;
  1352. gap: 15px;
  1353. width: 100%;
  1354. }
  1355.  
  1356. .modColItems_2 {
  1357. display: flex;
  1358. flex-direction: column;
  1359. align-items: start;
  1360. justify-content: start;
  1361. background: #050505;
  1362. gap: 8px;
  1363. border-radius: 0.5rem;
  1364. padding: 10px;
  1365. width: 100%;
  1366. }
  1367.  
  1368. .modRowItems {
  1369. display: flex;
  1370. justify-content: center;
  1371. align-items: center;
  1372. background: #050505;
  1373. gap: 58px;
  1374. border-radius: 0.5rem;
  1375. padding: 10px;
  1376. width: 100%;
  1377. }
  1378.  
  1379. .form-control {
  1380. border-width: 1px;
  1381. }
  1382. .form-control.error-border {
  1383. border-color: #AC3D3D;
  1384. }
  1385.  
  1386. .modSlider {
  1387. --thumb-color: #d9d9d9;
  1388. -webkit-appearance: none;
  1389. appearance: none;
  1390. width: 100%;
  1391. height: 8px;
  1392. background: #333;
  1393. border-radius: 5px;
  1394. outline: none;
  1395. transition: all 0.3s ease;
  1396. }
  1397.  
  1398. .modSlider::-webkit-slider-thumb {
  1399. -webkit-appearance: none;
  1400. appearance: none;
  1401. width: 20px;
  1402. height: 20px;
  1403. border-radius: 50%;
  1404. background: var(--thumb-color);
  1405. }
  1406.  
  1407. .modSlider::-moz-range-thumb {
  1408. width: 20px;
  1409. height: 20px;
  1410. border-radius: 50%;
  1411. background: var(--thumb-color);
  1412. }
  1413.  
  1414. .modSlider::-ms-thumb {
  1415. width: 20px;
  1416. height: 20px;
  1417. border-radius: 50%;
  1418. background: var(--thumb-color);
  1419. }
  1420.  
  1421. input:focus, select:focus, button:focus{
  1422. outline: none;
  1423. }
  1424. .macros_wrapper {
  1425. display: flex;
  1426. width: 100%;
  1427. justify-content: center;
  1428. flex-direction: column;
  1429. gap: 10px;
  1430. background: #050505;
  1431. padding: 10px;
  1432. border-radius: 0.75rem;
  1433. }
  1434. .macrosContainer {
  1435. display: flex;
  1436. width: 100%;
  1437. justify-content: center;
  1438. align-items: center;
  1439. gap: 20px;
  1440. }
  1441. .macroRow {
  1442. background: #121212;
  1443. border-radius: 5px;
  1444. padding: 7px;
  1445. display: flex;
  1446. justify-content: space-between;
  1447. align-items: center;
  1448. gap: 10px;
  1449. }
  1450. .keybinding {
  1451. border-radius: 5px;
  1452. background: #242424;
  1453. border: none;
  1454. color: #fff;
  1455. padding: 2px 5px;
  1456. max-width: 50px;
  1457. font-weight: 500;
  1458. text-align: center;
  1459. }
  1460. .closeBtn{
  1461. width: 46px;
  1462. background-color: transparent;
  1463. display: flex;
  1464. justify-content: center;
  1465. align-items: center;
  1466. }
  1467. .select-btn {
  1468. padding: 15px 20px;
  1469. background: #222;
  1470. border-radius: 2px;
  1471. position: relative;
  1472. }
  1473.  
  1474. .select-btn:active {
  1475. scale: 0.95
  1476. }
  1477.  
  1478. .select-btn::before {
  1479. content: "...";
  1480. font-size: 20px;
  1481. color: #fff;
  1482. position: absolute;
  1483. top: 50%;
  1484. left: 50%;
  1485. transform: translate(-50%, -50%);
  1486. }
  1487. .text {
  1488. user-select: none;
  1489. font-weight: 500;
  1490. text-align: left;
  1491. }
  1492. .modButton {
  1493. background-color: #252525;
  1494. border-radius: 4px;
  1495. color: #fff;
  1496. transition: all .3s;
  1497. outline: none;
  1498. padding: 7px;
  1499. font-size: 13px;
  1500. border: none;
  1501. }
  1502. .modButton:hover {
  1503. background-color: #222
  1504. }
  1505. .modInput {
  1506. background-color: #111;
  1507. border: none;
  1508. border-radius: 5px;
  1509. position: relative;
  1510. border-top-right-radius: 4px;
  1511. border-top-left-radius: 4px;
  1512. font-weight: 500;
  1513. padding: 5px;
  1514. color: #fff;
  1515. }
  1516. .modNumberInput:disabled {
  1517. color: #777;
  1518. }
  1519.  
  1520. .modCode {
  1521. background-color: rgba(50, 50, 50, 0.6);
  1522. padding: 4px;
  1523. border-radius: 3px;
  1524. }
  1525.  
  1526. .modCheckbox input[type="checkbox"] {
  1527. display: none;
  1528. visibility: hidden;
  1529. }
  1530. .modCheckbox label {
  1531. display: inline-block;
  1532. }
  1533.  
  1534. .modCheckbox .cbx {
  1535. position: relative;
  1536. top: 1px;
  1537. width: 17px;
  1538. height: 17px;
  1539. margin: 2px;
  1540. border: 1px solid #c8ccd4;
  1541. border-radius: 3px;
  1542. vertical-align: middle;
  1543. transition: background 0.1s ease;
  1544. cursor: pointer;
  1545. }
  1546.  
  1547. .modCheckbox .cbx:after {
  1548. content: '';
  1549. position: absolute;
  1550. top: 1px;
  1551. left: 5px;
  1552. width: 5px;
  1553. height: 11px;
  1554. opacity: 0;
  1555. transform: rotate(45deg) scale(0);
  1556. border-right: 2px solid #fff;
  1557. border-bottom: 2px solid #fff;
  1558. transition: all 0.3s ease;
  1559. transition-delay: 0.15s;
  1560. }
  1561.  
  1562. .modCheckbox input[type="checkbox"]:checked ~ .cbx {
  1563. border-color: transparent;
  1564. background: #6871f1;
  1565. box-shadow: 0 0 10px #2E2D80;
  1566. }
  1567.  
  1568. .modCheckbox input[type="checkbox"]:checked ~ .cbx:after {
  1569. opacity: 1;
  1570. transform: rotate(45deg) scale(1);
  1571. }
  1572.  
  1573. .SettingsButton{
  1574. border: none;
  1575. outline: none;
  1576. margin-right: 10px;
  1577. transition: all .3s ease;
  1578. }
  1579. .SettingsButton:hover {
  1580. scale: 1.1;
  1581. }
  1582. .colorInput{
  1583. background-color: transparent;
  1584. width: 31px;
  1585. height: 35px;
  1586. border-radius: 50%;
  1587. border: none;
  1588. }
  1589. .colorInput::-webkit-color-swatch {
  1590. border-radius: 50%;
  1591. border: 2px solid #fff;
  1592. }
  1593. .whiteBorder_colorInput::-webkit-color-swatch {
  1594. border-color: #fff;
  1595. }
  1596. .menu-center>div>.menu-center-content>div>div>.menu__form-group {
  1597. margin-bottom: 14px !important;
  1598. }
  1599. #dclinkdiv {
  1600. display: flex;
  1601. flex-direction: row;
  1602. margin-top: 10px;
  1603. }
  1604. .dclinks {
  1605. width: calc(50% - 5px);
  1606. height: 36px;
  1607. display: flex;
  1608. justify-content: center;
  1609. align-items: center;
  1610. background-color: rgba(88, 101, 242, 1);
  1611. border-radius: 6px;
  1612. margin: 0 auto;
  1613. color: #fff;
  1614. }
  1615. #cm_close__settings {
  1616. width: 50px;
  1617. transition: all .3s ease;
  1618. }
  1619. #cm_close__settings svg:hover {
  1620. scale: 1.1;
  1621. }
  1622. #cm_close__settings svg {
  1623. transition: all .3s ease;
  1624. }
  1625. .modTitleText {
  1626. text-align: center;
  1627. font-size: 16px;
  1628. }
  1629. .modItem {
  1630. display: flex;
  1631. justify-content: center;
  1632. align-items: center;
  1633. flex-direction: column;
  1634. }
  1635. .accent_row {
  1636. background: #111111;
  1637. }
  1638. .mod_tab-content {
  1639. width: 100%;
  1640. margin: 10px;
  1641. overflow: auto;
  1642. display: flex;
  1643. flex-direction: column;
  1644. }
  1645.  
  1646. #Tab6 .mod_tab-content {
  1647. overflow-y: auto;
  1648. max-height: 230px;
  1649. display: flex;
  1650. flex-wrap: nowrap;
  1651. flex-direction: column;
  1652. gap: 10px;
  1653. }
  1654.  
  1655. .tab-content, #coins-tab, #chests-tab {
  1656. overflow-x: hidden;
  1657. justify-content: center;
  1658. }
  1659.  
  1660. #shop-skins-buttons::after {
  1661. background: #050505;
  1662. }
  1663.  
  1664. #sigma-status {
  1665. background: rgba(0, 0, 0, .6) !important;
  1666. }
  1667.  
  1668. .w-100 {
  1669. width: 100%
  1670. }
  1671. .btn:hover {
  1672. color: unset;
  1673. }
  1674.  
  1675. #savedNames {
  1676. background-color: #000;
  1677. padding: 5px;
  1678. border-radius: 5px;
  1679. overflow-y: auto;
  1680. height: 155px;
  1681. background-image: url("https://raw.githubusercontent.com/Sigmally/SigMod/main/images/purple_gradient.png");
  1682. background-size: cover;
  1683. background-position: center;
  1684. background-repeat: no-repeat;
  1685. box-shadow: 0 0 10px #000;
  1686. }
  1687.  
  1688. .scroll {
  1689. scroll-behavior: smooth;
  1690. }
  1691.  
  1692. /* Chrome, Safari */
  1693. .scroll::-webkit-scrollbar {
  1694. width: 7px;
  1695. }
  1696.  
  1697. .scroll::-webkit-scrollbar-track {
  1698. background: #222;
  1699. border-radius: 5px;
  1700. }
  1701.  
  1702. .scroll::-webkit-scrollbar-thumb {
  1703. background-color: #333;
  1704. border-radius: 5px;
  1705. }
  1706.  
  1707. .scroll::-webkit-scrollbar-thumb:hover {
  1708. background: #353535;
  1709. }
  1710.  
  1711. /* Firefox */
  1712. .scroll {
  1713. scrollbar-width: thin;
  1714. scrollbar-color: #333 #222;
  1715. }
  1716.  
  1717. .scroll:hover {
  1718. scrollbar-color: #353535 #222;
  1719. }
  1720.  
  1721. .themes {
  1722. display: flex;
  1723. flex-direction: row;
  1724. flex: 1;
  1725. flex-wrap: wrap;
  1726. justify-content: center;
  1727. width: 100%;
  1728. min-height: 254px;
  1729. max-height: 420px;
  1730. background: #000;
  1731. border-radius: 5px;
  1732. overflow-y: scroll;
  1733. gap: 10px;
  1734. padding: 5px;
  1735. }
  1736.  
  1737. .themeContent {
  1738. width: 50px;
  1739. height: 50px;
  1740. border: 2px solid #222;
  1741. border-radius: 50%;
  1742. background-position: center;
  1743. }
  1744.  
  1745. .theme {
  1746. height: 75px;
  1747. display: flex;
  1748. align-items: center;
  1749. justify-content: center;
  1750. flex-direction: column;
  1751. cursor: pointer;
  1752. }
  1753. .delName {
  1754. font-weight: 500;
  1755. background: #e17e7e;
  1756. height: 20px;
  1757. border: none;
  1758. border-radius: 5px;
  1759. font-size: 10px;
  1760. margin-left: 5px;
  1761. color: #fff;
  1762. display: flex;
  1763. justify-content: center;
  1764. align-items: center;
  1765. width: 20px;
  1766. }
  1767. .NameDiv {
  1768. display: flex;
  1769. background: #111;
  1770. border-radius: 5px;
  1771. margin: 5px;
  1772. padding: 3px 8px;
  1773. height: 34px;
  1774. align-items: center;
  1775. justify-content: space-between;
  1776. cursor: pointer;
  1777. box-shadow: 0 5px 10px -2px #000;
  1778. }
  1779. .NameLabel {
  1780. cursor: pointer;
  1781. font-weight: 500;
  1782. text-align: center;
  1783. color: #fff;
  1784. }
  1785. .alwan {
  1786. border-radius: 8px;
  1787. }
  1788. .colorpicker-additional {
  1789. display: flex;
  1790. justify-content: space-between;
  1791. width: 100%;
  1792. color: #fafafa;
  1793. padding: 10px;
  1794. }
  1795. .resetButton {
  1796. width: 25px;
  1797. height: 25px;
  1798. background-image: url("https://raw.githubusercontent.com/Sigmally/SigMod/main/images/reset.svg");
  1799. background-color: transparent;
  1800. background-repeat: no-repeat;
  1801. border: none;
  1802. }
  1803.  
  1804. .modAlert {
  1805. position: fixed;
  1806. top: 8%;
  1807. left: 50%;
  1808. transform: translate(-50%, -50%);
  1809. z-index: 99995;
  1810. background: #151515;
  1811. border: 1px solid #333;
  1812. border-radius: 10px;
  1813. display: flex;
  1814. flex-direction: column;
  1815. gap: 5px;
  1816. padding: 10px;
  1817. color: #fff;
  1818. max-width: 360px;
  1819. }
  1820.  
  1821. .alert_overlay {
  1822. position: absolute;
  1823. top: 0;
  1824. left: 0;
  1825. z-index: 999999999;
  1826. pointer-events: none;
  1827. width: 100%;
  1828. height: 100vh;
  1829. display: flex;
  1830. flex-direction: column;
  1831. justify-content: start;
  1832. align-items: center;
  1833. }
  1834.  
  1835. .infoAlert {
  1836. padding: 5px;
  1837. border-radius: 5px;
  1838. margin-top: 5px;
  1839. color: #fff;
  1840. }
  1841.  
  1842. .modAlert-success {
  1843. background: #5BA55C;
  1844. }
  1845. .modAlert-success .modAlert-loader {
  1846. background: #6BD56D;
  1847. }
  1848. .modAlert-default {
  1849. background: #151515;
  1850. }
  1851. .modAlert-default .modAlert-loader {
  1852. background: #222;
  1853. }
  1854. .modAlert-danger {
  1855. background: #D44121;
  1856. }
  1857. .modAlert-danger .modAlert-loader {
  1858. background: #A5361E;
  1859. }
  1860. #free-coins .modAlert-danger {
  1861. background: #fff !important;
  1862. }
  1863.  
  1864. .modAlert-loader {
  1865. width: 100%;
  1866. height: 2px;
  1867. margin-top: 5px;
  1868. transition: all .3s ease-in-out;
  1869. animation: loadAlert 2s forwards;
  1870. }
  1871.  
  1872. @keyframes loadAlert {
  1873. 0% {
  1874. width: 100%;
  1875. }
  1876. 100% {
  1877. width: 0%;
  1878. }
  1879. }
  1880.  
  1881. .themeEditor {
  1882. z-index: 999999999999;
  1883. position: absolute;
  1884. top: 50%;
  1885. left: 50%;
  1886. transform: translate(-50%, -50%);
  1887. background: rgba(0, 0, 0, .85);
  1888. color: #fff;
  1889. padding: 10px;
  1890. border-radius: 10px;
  1891. box-shadow: 0 0 10px #000;
  1892. width: 400px;
  1893. }
  1894.  
  1895. .theme_editor_header {
  1896. display: flex;
  1897. justify-content: space-between;
  1898. align-items: center;
  1899. gap: 10px;
  1900. }
  1901.  
  1902. .theme-editor-tab {
  1903. display: flex;
  1904. justify-content: center;
  1905. align-items: start;
  1906. flex-direction: column;
  1907. margin-top: 10px
  1908. }
  1909.  
  1910. .themes_preview {
  1911. width: 50px;
  1912. height: 50px;
  1913. border: 2px solid #fff;
  1914. border-radius: 2px;
  1915. display: flex;
  1916. justify-content: center;
  1917. align-items: center;
  1918. }
  1919.  
  1920. .sigmod-title {
  1921. display: flex;
  1922. flex-direction: column;
  1923. align-items: center;
  1924. gap: 2px;
  1925. }
  1926. .sigmod-title #title {
  1927. width: fit-content;
  1928. }
  1929. #bycursed {
  1930. font-family: "Titillium Web", sans-serif;
  1931. font-weight: 400;
  1932. font-size: 16px;
  1933. }
  1934. #bycursed a {
  1935. color: #71aee3;
  1936. }
  1937. .stats__item>span, #title, .stats-btn__text {
  1938. color: #fff;
  1939. }
  1940.  
  1941. .top-users {
  1942. overflow: hidden;
  1943. }
  1944. .top-users__inner {
  1945. background: transparent;
  1946. }
  1947. .top-users__inner table {
  1948. background-color: rgba(0, 0, 0, 0.6);
  1949. color: #fff;
  1950. border-radius: 6px;
  1951. padding-top: 4px;
  1952. padding-right: 6px;
  1953. }
  1954.  
  1955. .top-users_buttons button {
  1956. background-color: rgba(0, 0, 0, 0.5);
  1957. color: #fff;
  1958. }
  1959.  
  1960. .top-users_buttons button.active {
  1961. background-color: rgba(0, 0, 0, 0.8);
  1962. }
  1963.  
  1964. .top-users__inner::-webkit-scrollbar-thumb {
  1965. border: none;
  1966. }
  1967. #signInBtn, #nick, #gamemode, .form-control, .profile-header, .coins-num, #clan-members, .member-index, .member-level, #clan-requests {
  1968. background: rgba(0, 0, 0, 0.4) !important;
  1969. color: #fff !important;
  1970. }
  1971. .profile-name, #progress-next, .member-desc > p:first-child, #clan-leave > div, .clans-item > div > b, #clans-input input, #shop-nav button {
  1972. color: #fff !important;
  1973. }
  1974. .head-desc, #shop-nav button {
  1975. border: 1px solid #000;
  1976. }
  1977. #shop-nav button {
  1978. transition: background .1s ease;
  1979. }
  1980. #shop-nav button:hover {
  1981. background: #121212 !important;
  1982. }
  1983. #clan-handler, #request-handler, #clans-list, #clans-input, .clans-item button, #shop-content, .card-particles-bar-bg {
  1984. background: #111 !important;
  1985. color: #fff !important;
  1986. }
  1987. #clans_and_settings {
  1988. height: auto !important;
  1989. }
  1990. .card-body {
  1991. background: linear-gradient(180deg, #000 0%, #1b354c 100%);
  1992. }
  1993. .free-card:hover .card-body {
  1994. background: linear-gradient(180deg, #111 0%, #1b354c 100%);
  1995. }
  1996. #shop-tab-body, #shop-nav, #shop-skins-buttons {
  1997. background: #050505 !important;
  1998. }
  1999. #clan-leave {
  2000. background: #111;
  2001. bottom: -1px;
  2002. }
  2003. .sent {
  2004. position: relative;
  2005. width: 100px;
  2006. }
  2007.  
  2008. .sent::before {
  2009. content: "Sent request";
  2010. width: 100%;
  2011. height: 10px;
  2012. word-spacing: normal;
  2013. white-space: nowrap;
  2014. position: absolute;
  2015. background: #4f79f9;
  2016. display: flex;
  2017. justify-content: center;
  2018. align-items: center;
  2019. }
  2020.  
  2021. .btn, .sign-in-out-btn {
  2022. transition: all .2s ease;
  2023. }
  2024. .free-coins-body > div, .goldmodal-contain {
  2025. background: rgba(0, 0, 0, .5);
  2026. }
  2027. #clan .connecting__content, #clans .connecting__content {
  2028. background: #151515;
  2029. color: #fff;
  2030. box-shadow: 0 0 10px rgba(0, 0, 0, .5);
  2031. }
  2032.  
  2033. .skin-select__icon-text {
  2034. color: #fff;
  2035. }
  2036.  
  2037. .justify-sb {
  2038. display: flex;
  2039. align-items: center;
  2040. justify-content: space-between;
  2041. }
  2042.  
  2043. .macro-extanded_input {
  2044. width: 75px;
  2045. text-align: center;
  2046. }
  2047. .form-control option {
  2048. background: #111;
  2049. }
  2050.  
  2051. .stats-line {
  2052. width: 100%;
  2053. user-select: none;
  2054. margin-bottom: 5px;
  2055. padding: 5px;
  2056. background: #050505;
  2057. border: 1px solid var(--default-mod);
  2058. border-radius: 5px;
  2059. }
  2060.  
  2061. .stats-info-text {
  2062. color: #7d7d7d;
  2063. }
  2064.  
  2065. .setting-card-wrapper {
  2066. margin-right: 10px;
  2067. padding: 10px;
  2068. background: #161616;
  2069. border-radius: 5px;
  2070. display: flex;
  2071. flex-direction: column;
  2072. width: 100%;
  2073. }
  2074.  
  2075. .setting-card {
  2076. display: flex;
  2077. align-items: center;
  2078. justify-content: space-between;
  2079. }
  2080.  
  2081. .setting-card-action {
  2082. display: flex;
  2083. align-items: center;
  2084. gap: 5px;
  2085. cursor: pointer;
  2086. }
  2087.  
  2088. .setting-card-action {
  2089. width: 100%;
  2090. }
  2091.  
  2092. .setting-card-name {
  2093. font-size: 16px;
  2094. user-select: none;
  2095. width: 100%;
  2096. }
  2097.  
  2098. .mod-small-modal {
  2099. display: flex;
  2100. flex-direction: column;
  2101. gap: 10px;
  2102. position: absolute;
  2103. z-index: 99999;
  2104. top: 50%;
  2105. left: 50%;
  2106. transform: translate(-50%, -50%);
  2107. background: #191919;
  2108. box-shadow: 0 5px 15px -2px #000;
  2109. padding: 10px;
  2110. border-radius: 5px;
  2111. }
  2112.  
  2113. .mod-small-modal-header {
  2114. display: flex;
  2115. justify-content: space-between;
  2116. align-items: center;
  2117. }
  2118.  
  2119. .mod-small-modal-header h1 {
  2120. font-size: 20px;
  2121. font-weight: 500;
  2122. margin: 0;
  2123. }
  2124.  
  2125. .mod-small-modal-content {
  2126. display: flex;
  2127. flex-direction: column;
  2128. width: 100%;
  2129. align-items: center;
  2130. }
  2131.  
  2132. .mod-small-modal-content_selectImage {
  2133. display: flex;
  2134. flex-direction: column;
  2135. gap: 10px;
  2136. }
  2137.  
  2138. .imagePreview {
  2139. min-width: 34px;
  2140. width: 34px;
  2141. height: 34px;
  2142. border: 2px solid #353535;
  2143. border-radius: 5px;
  2144. background-size: contain;
  2145. background-repeat: no-repeat;
  2146. background-position: center;
  2147.  
  2148. /* for preview text */
  2149. display: flex;
  2150. justify-content: center;
  2151. align-items: center;
  2152. font-size: 7px;
  2153. overflow: hidden;
  2154. text-align: center;
  2155. }
  2156.  
  2157. .modChat {
  2158. min-width: 450px;
  2159. max-width: 450px;
  2160. min-height: 285px;
  2161. max-height: 285px;
  2162. color: #fafafa;
  2163. padding: 10px;
  2164. position: absolute;
  2165. bottom: 10px;
  2166. left: 10px;
  2167. z-index: 999;
  2168. border-radius: .5rem;
  2169. overflow: hidden;
  2170. opacity: 1;
  2171. transition: all .3s ease;
  2172. }
  2173.  
  2174. .modChat__inner {
  2175. min-width: 430px;
  2176. max-width: 430px;
  2177. min-height: 265px;
  2178. max-height: 265px;
  2179. height: 100%;
  2180. display: flex;
  2181. flex-direction: column;
  2182. gap: 5px;
  2183. justify-content: flex-end;
  2184. opacity: 1;
  2185. transition: all .3s ease;
  2186. }
  2187.  
  2188. .mod-compact {
  2189. transform: scale(0.78);
  2190. }
  2191. .mod-compact.modChat {
  2192. left: -40px;
  2193. bottom: -20px;
  2194. }
  2195. .mod-compact.chatAddedContainer {
  2196. left: 350px;
  2197. bottom: -17px;
  2198. }
  2199.  
  2200. #scroll-down-btn {
  2201. position: absolute;
  2202. bottom: 60px;
  2203. left: 50%;
  2204. transform: translateX(-50%);
  2205. width: 80px;
  2206. display:none;
  2207. box-shadow:0 0 5px #000;
  2208. z-index: 5;
  2209. }
  2210.  
  2211. .modchat-chatbuttons {
  2212. margin-bottom: auto;
  2213. display: flex;
  2214. gap: 5px;
  2215. }
  2216.  
  2217. .chat-context {
  2218. position: absolute;
  2219. z-index: 999999;
  2220. width: 100px;
  2221. display: flex;
  2222. flex-direction: column;
  2223. justify-content: center;
  2224. align-items: center;
  2225. gap: 5px;
  2226. background: #181818;
  2227. border-radius: 5px;
  2228. }
  2229.  
  2230. .chat-context span {
  2231. color: #fff;
  2232. user-select: none;
  2233. padding: 5px;
  2234. white-space: nowrap;
  2235. }
  2236.  
  2237. .chat-context button {
  2238. width: 100%;
  2239. background-color: transparent;
  2240. border: none;
  2241. border-top: 2px solid #747474;
  2242. outline: none;
  2243. color: #fff;
  2244. transition: all .3s ease;
  2245. }
  2246.  
  2247. .chat-context button:hover {
  2248. backgrokund-color: #222;
  2249. }
  2250.  
  2251. .tagText {
  2252. margin-left: auto;
  2253. font-size: 14px;
  2254. }
  2255.  
  2256. #mod-messages {
  2257. position: relative;
  2258. display: flex;
  2259. flex-direction: column;
  2260. max-height: 185px;
  2261. overflow-y: auto;
  2262. direction: rtl;
  2263. scroll-behavior: smooth;
  2264. }
  2265. .message {
  2266. direction: ltr;
  2267. margin: 2px 0 0 5px;
  2268. text-overflow: ellipsis;
  2269. max-width: 100%;
  2270. display: flex;
  2271. justify-content: space-between;
  2272. }
  2273.  
  2274. .message_name {
  2275. user-select: none;
  2276. }
  2277.  
  2278. .chatMessage-text {
  2279. max-width: 310px;
  2280. }
  2281.  
  2282. .message .time {
  2283. color: rgba(255, 255, 255, 0.7);
  2284. font-size: 12px;
  2285. }
  2286.  
  2287. #chatInputContainer {
  2288. display: flex;
  2289. gap: 5px;
  2290. align-items: center;
  2291. padding: 5px;
  2292. background: rgba(25,25,25, .6);
  2293. border-radius: .5rem;
  2294. overflow: hidden;
  2295. }
  2296.  
  2297. .chatInput {
  2298. flex-grow: 1;
  2299. border: none;
  2300. background: transparent;
  2301. color: #fff;
  2302. padding: 5px;
  2303. outline: none;
  2304. max-width: 100%;
  2305. }
  2306.  
  2307. .chatButton {
  2308. background: #8a25e5;
  2309. border: none;
  2310. border-radius: 5px;
  2311. padding: 5px 10px;
  2312. height: 100%;
  2313. color: #fff;
  2314. transition: all 0.3s;
  2315. cursor: pointer;
  2316. display: flex;
  2317. align-items: center;
  2318. height: 28px;
  2319. justify-content: center;
  2320. gap: 5px;
  2321. }
  2322. .chatButton:hover {
  2323. background: #7a25e5;
  2324. }
  2325. .chatCloseBtn {
  2326. position: absolute;
  2327. top: 50%;
  2328. left: 50%;
  2329. transform: translate(-50%, -50%);
  2330. }
  2331.  
  2332. .emojisContainer {
  2333. display: flex;
  2334. flex-direction: column;
  2335. gap: 5px;
  2336. }
  2337. .chatAddedContainer {
  2338. position: absolute;
  2339. bottom: 10px;
  2340. left: 465px;
  2341. z-index: 9999;
  2342. padding: 10px;
  2343. background: #151515;
  2344. border-radius: .5rem;
  2345. min-width: 172px;
  2346. max-width: 172px;
  2347. min-height: 250px;
  2348. max-height: 250px;
  2349. }
  2350. #categories {
  2351. overflow-y: auto;
  2352. max-height: calc(250px - 50px);
  2353. gap: 2px;
  2354. }
  2355. .category {
  2356. width: 100%;
  2357. display: flex;
  2358. flex-direction: column;
  2359. gap: 2px;
  2360. }
  2361. .category span {
  2362. color: #fafafa;
  2363. font-size: 14px;
  2364. text-align: center;
  2365. }
  2366.  
  2367. .emojiContainer {
  2368. display: flex;
  2369. flex-wrap: wrap;
  2370. align-items: center;
  2371. justify-content: center;
  2372. }
  2373.  
  2374. #categories .emoji {
  2375. padding: 2px;
  2376. border-radius: 5px;
  2377. font-size: 16px;
  2378. user-select: none;
  2379. cursor: pointer;
  2380. }
  2381.  
  2382. .chatSettingsContainer {
  2383. padding: 10px 3px;
  2384. }
  2385. .chatSettingsContainer .scroll {
  2386. display: flex;
  2387. flex-direction: column;
  2388. gap: 10px;
  2389. max-height: 235px;
  2390. overflow-y: auto;
  2391. padding: 0 10px;
  2392. }
  2393.  
  2394. .csBlock {
  2395. border: 2px solid #050505;
  2396. border-radius: .5rem;
  2397. color: #fff;
  2398. display: flex;
  2399. align-items: center;
  2400. flex-direction: column;
  2401. gap: 5px;
  2402. padding-bottom: 5px;
  2403. }
  2404.  
  2405. .csBlock .csBlockTitle {
  2406. background: #080808;
  2407. width: 100%;
  2408. padding: 3px;
  2409. text-align: center;
  2410. }
  2411.  
  2412. .csRow {
  2413. display: flex;
  2414. justify-content: space-between;
  2415. align-items: center;
  2416. padding: 0 5px;
  2417. width: 100%;
  2418. }
  2419.  
  2420. .csRowName {
  2421. display: flex;
  2422. gap: 5px;
  2423. align-items: start;
  2424. }
  2425.  
  2426. .csRowName .infoIcon {
  2427. width: 14px;
  2428. cursor: pointer;
  2429. }
  2430.  
  2431. .modInfoPopup {
  2432. position: absolute;
  2433. top: 2px;
  2434. left: 58%;
  2435. text-align: center;
  2436. background: #151515;
  2437. border: 1px solid #607bff;
  2438. border-radius: 10px;
  2439. transform: translateX(-50%);
  2440. white-space: nowrap;
  2441. padding: 5px;
  2442. z-index: 99999;
  2443. }
  2444.  
  2445. .modInfoPopup::after {
  2446. content: '';
  2447. display: block;
  2448. position: absolute;
  2449. bottom: -7px;
  2450. background: #151515;
  2451. right: 50%;
  2452. transform: translateX(-50%) rotate(-45deg);
  2453. width: 12px;
  2454. height: 12px;
  2455. border-left: 1px solid #607bff;
  2456. border-bottom: 1px solid #607bff;
  2457. }
  2458.  
  2459. .modInfoPopup p {
  2460. margin: 0;
  2461. font-size: 12px;
  2462. color: #fff;
  2463. }
  2464.  
  2465. .minimapContainer {
  2466. display: flex;
  2467. flex-direction: column;
  2468. align-items: end;
  2469. pointer-events: none;
  2470. position: absolute;
  2471. bottom: 0;
  2472. right: 0;
  2473. z-index: 99999;
  2474. }
  2475.  
  2476. #tag {
  2477. width: 50px;
  2478. }
  2479.  
  2480. .blur {
  2481. color: transparent!important;
  2482. text-shadow: 0 0 6px hsl(0deg 0% 90% / 70%);
  2483. transition: all .2s;
  2484. }
  2485.  
  2486. .blur:focus, .blur:hover {
  2487. color: #fafafa!important;
  2488. text-shadow: none;
  2489. }
  2490. .progress-row button {
  2491. background: transparent;
  2492. }
  2493.  
  2494. #mod_home .justify-sb {
  2495. z-index: 2;
  2496. }
  2497.  
  2498. .modTitleText {
  2499. font-size: 15px;
  2500. color: #fafafa;
  2501. text-align: start;
  2502. }
  2503. .modDescText {
  2504. text-align: start;
  2505. font-size: 12px;
  2506. color: #777;
  2507. }
  2508. .modButton-secondary {
  2509. background-color: #171717;
  2510. color: #fff;
  2511. border: none;
  2512. padding: 5px 15px;
  2513. border-radius: 15px;
  2514. }
  2515. .vr {
  2516. width: 2px;
  2517. height: 250px;
  2518. background-color: #fafafa;
  2519. }
  2520. .vr2 {
  2521. width: 1px;
  2522. height: 26px;
  2523. background-color: #202020;
  2524. }
  2525.  
  2526. .home-card-row {
  2527. display: flex;
  2528. flex-wrap: nowrap;
  2529. justify-content: space-between;
  2530. gap: 18px;
  2531. }
  2532. .home-card-wrapper {
  2533. display: flex;
  2534. flex: 1;
  2535. flex-direction: column;
  2536. gap: 5px;
  2537. width: 50%;
  2538. }
  2539. .home-card {
  2540. position: relative;
  2541. display: flex;
  2542. flex-direction: column;
  2543. justify-content: center;
  2544. gap: 7px;
  2545. border-radius: 5px;
  2546. background: #050505;
  2547. min-height: 164px;
  2548. max-height: 164px;
  2549. max-width: 256px;
  2550. padding: 5px 10px;
  2551. }
  2552. .quickAccess {
  2553. gap: 5px;
  2554. max-height: 164px;
  2555. overflow-y: auto;
  2556. justify-content: start;
  2557. }
  2558.  
  2559. .quickAccess div.modRowItems {
  2560. padding: 2px!important;
  2561. }
  2562.  
  2563. #my-profile-badges {
  2564. display: flex;
  2565. flex-wrap: wrap;
  2566. gap: 5px;
  2567. }
  2568. #my-profile-bio {
  2569. overflow-y: scroll;
  2570. max-height: 75px;
  2571. }
  2572.  
  2573. .brand_wrapper {
  2574. position: relative;
  2575. height: 72px;
  2576. width: 100%;
  2577. display: flex;
  2578. justify-content: center;
  2579. align-items: center;
  2580. }
  2581. .brand_wrapper span {
  2582. font-size: 24px;
  2583. z-index: 2;
  2584. font-family: "Titillium Web", sans-serif;
  2585. font-weight: 600;
  2586. letter-spacing: 3px;
  2587. }
  2588.  
  2589. .brand_img {
  2590. position: absolute;
  2591. top: 0;
  2592. left: 0;
  2593. width: 100%;
  2594. height: 72px;
  2595. border-radius: 10px;
  2596. object-fit: cover;
  2597. object-position: center;
  2598. z-index: 1;
  2599. box-shadow: 0 0 10px #000;
  2600. }
  2601. .brand_credits {
  2602. position: relative;
  2603. font-size: 16px;
  2604. color: #D3A7FF;
  2605. list-style: none;
  2606. width: 100%;
  2607. display: flex;
  2608. justify-content: space-between;
  2609. padding: 0 24px;
  2610. }
  2611.  
  2612. .brand_credits li {
  2613. position: relative;
  2614. display: inline-block;
  2615. text-shadow: 0px 0px 8px #D3A7FF;
  2616. }
  2617.  
  2618. .brand_credits li:not(:last-child)::after {
  2619. content: '•';
  2620. position: absolute;
  2621. right: -20px;
  2622. color: #D3A7FF;
  2623. }
  2624. .brand_yt {
  2625. display: flex;
  2626. justify-content: center;
  2627. align-items: center;
  2628. gap: 20px;
  2629. }
  2630. .yt_wrapper {
  2631. display: flex;
  2632. justify-content: center;
  2633. align-items: center;
  2634. gap: 10px;
  2635. width: 122px;
  2636. padding: 5px;
  2637. background-color: #B63333;
  2638. border-radius: 15px;
  2639. cursor: pointer;
  2640. }
  2641. .yt_wrapper span {
  2642. user-select: none;
  2643. }
  2644.  
  2645. .hidden_full {
  2646. display: none !important;
  2647. visibility: hidden;
  2648. }
  2649.  
  2650. .mod_overlay {
  2651. position: absolute;
  2652. top: 0;
  2653. left: 0;
  2654. width: 100%;
  2655. height: 100vh;
  2656. background: rgba(0, 0, 0, .7);
  2657. z-index: 9999999;
  2658. display: flex;
  2659. justify-content: center;
  2660. align-items: center;
  2661. transition: all .3s ease;
  2662. }
  2663.  
  2664. .black_overlay {
  2665. background: rgba(0, 0, 0, 0);
  2666. z-index: 99999999;
  2667. opacity: 1;
  2668. animation: 2s ease fadeInBlack forwards;
  2669. }
  2670.  
  2671. @keyframes fadeInBlack {
  2672. 0% {
  2673. background: rgba(0, 0, 0, 0);
  2674. }
  2675. 100% {
  2676. background: rgba(0, 0, 0, 1);
  2677. }
  2678. }
  2679.  
  2680. .default-modal {
  2681. position: relative;
  2682. display: flex;
  2683. flex-direction: column;
  2684. min-width: 300px;
  2685. background: #111;
  2686. color: #fafafa;
  2687. border-radius: 12px;
  2688. overflow: hidden;
  2689. box-shadow: 0 5px 10px #000;
  2690. }
  2691.  
  2692. .default-modal-header {
  2693. display: flex;
  2694. justify-content: space-between;
  2695. align-items: center;
  2696. background: #1c1c1c;
  2697. padding: 5px 10px;
  2698. }
  2699.  
  2700. .default-modal-header h2 {
  2701. margin: 0;
  2702. }
  2703.  
  2704. .default-modal-body {
  2705. display: flex;
  2706. flex-direction: column;
  2707. gap: 6px;
  2708. padding: 15px;
  2709. }
  2710.  
  2711. .tournament-overlay-info {
  2712. display: flex;
  2713. flex-direction: column;
  2714. align-items: center;
  2715. color: white;
  2716. font-size: 28px;
  2717. line-height: 1.4;
  2718. }
  2719.  
  2720. .tournament-overlay-info img {
  2721. margin-bottom: 26px;
  2722. }
  2723.  
  2724. .tournaments-wrapper {
  2725. font-family: 'Titillium Web', sans-serif;
  2726. font-weight: 400;
  2727. position: absolute;
  2728. top: 60%;
  2729. left: 50%;
  2730. transform: translate(-50%, -50%);
  2731. background: #000;
  2732. border: 2px solid #222222;
  2733. border-radius: 1.125rem;
  2734. padding: 1.5rem;
  2735. color: #fafafa;
  2736. display: flex;
  2737. flex-direction: column;
  2738. align-items: center;
  2739. gap: 10px;
  2740. min-width: 632px;
  2741. opacity: 0;
  2742. transition: all .3s ease;
  2743. animation: 0.5s ease fadeIn forwards;
  2744. }
  2745. @keyframes fadeIn {
  2746. 0% {
  2747. top: 60%;
  2748. opacity: 0;
  2749. }
  2750. 100% {
  2751. top: 50%;
  2752. opacity: 1;
  2753. }
  2754. }
  2755. .tournaments h1 {
  2756. margin: 0;
  2757. font-weight: 600;
  2758. }
  2759.  
  2760. .teamCards {
  2761. display: flex;
  2762. gap: 5px;
  2763. position: absolute;
  2764. top: 50%;
  2765. transform: translate(-50%, -50%);
  2766. }
  2767. .teamCard {
  2768. display: flex;
  2769. flex-direction: column;
  2770. align-items: center;
  2771. background: rgba(0, 0, 0, 0.4);
  2772. border-radius: 12px;
  2773. padding: 6px;
  2774. height: fit-content;
  2775. }
  2776. .teamCard.userReady {
  2777. border: 2px solid var(--green);
  2778. }
  2779. .teamCard img {
  2780. border-radius: 50%;
  2781. }
  2782. .teamCard span {
  2783. font-size: 12px;
  2784. }
  2785.  
  2786. .redTeam {
  2787. left: 81%;
  2788. }
  2789.  
  2790. .blueTeam {
  2791. left: 24%;
  2792. }
  2793.  
  2794. .lastOneStanding_list {
  2795. display: flex;
  2796. flex-wrap: wrap;
  2797. gap: 8px;
  2798. width: 100%;
  2799. height: 240px;
  2800. max-height: 240px;
  2801. background: #050505;
  2802. }
  2803.  
  2804. .tournament_timer {
  2805. position: absolute;
  2806. top: 20px;
  2807. left: 50%;
  2808. transform: translateX(-50%);
  2809. color: #fff;
  2810. font-size: 15px;
  2811. z-index: 99999;
  2812. user-select: none;
  2813. pointer-events: none;
  2814. }
  2815.  
  2816. details {
  2817. border: 1px solid #aaa;
  2818. border-radius: 4px;
  2819. padding: 0.5em 0.5em 0;
  2820. user-select: none;
  2821. text-align: start;
  2822. }
  2823.  
  2824. summary {
  2825. font-weight: bold;
  2826. margin: -0.5em -0.5em 0;
  2827. padding: 0.5em;
  2828. }
  2829.  
  2830. details[open] {
  2831. padding: 0.5em;
  2832. }
  2833.  
  2834. details[open] summary {
  2835. border-bottom: 1px solid #aaa;
  2836. margin-bottom: 0.5em;
  2837. }
  2838. button[disabled] {
  2839. filter: grayscale(1);
  2840. }
  2841.  
  2842. .tournament-text-lost,
  2843. .tournament-text-won {
  2844. font-size: 6rem;
  2845. font-weight: 600;
  2846. font-family: 'Titillium Web', sans-serif;
  2847. }
  2848.  
  2849. .tournament-text-lost {
  2850. color: var(--red);
  2851. }
  2852. .tournament-text-won {
  2853. color: var(--green);
  2854. }
  2855.  
  2856. .tournament_alert {
  2857. position: absolute;
  2858. top: 20px;
  2859. left: 50%;
  2860. transform: translateX(-50%);
  2861. background: #151515;
  2862. color: #fff;
  2863. text-align: center;
  2864. padding: 20px;
  2865. z-index: 999999;
  2866. border-radius: 10px;
  2867. box-shadow: 0 0 10px #000;
  2868. display: flex;
  2869. gap: 10px;
  2870. }
  2871. .tournament-profile {
  2872. width: 50px;
  2873. height: 50px;
  2874. border-radius: 50%;
  2875. box-shadow: 0 0 10px #000;
  2876. }
  2877.  
  2878. .tournament-text {
  2879. color: #fff;
  2880. font-size: 24px;
  2881. }
  2882.  
  2883. .claimedBadgeWrapper {
  2884. background: linear-gradient(232deg, #020405 1%, #04181E 100%);
  2885. border-radius: 10px;
  2886. width: 320px;
  2887. height: 330px;
  2888. box-shadow: 0 0 40px -20px #39bdff;
  2889. display: flex;
  2890. flex-direction: column;
  2891. gap: 10px;
  2892. align-items: center;
  2893. justify-content: center;
  2894. color: #fff;
  2895. padding: 10px;
  2896. }
  2897.  
  2898. .btn-cyan {
  2899. background: #53B6CC;
  2900. border: none;
  2901. border-radius: 5px;
  2902. font-size: 16px;
  2903. color: #fff;
  2904. font-weight: 500;
  2905. width: fit-content;
  2906. padding: 5px 10px;
  2907. }
  2908.  
  2909. .stats-additional {
  2910. display: flex;
  2911. flex-direction: column;
  2912. position: absolute;
  2913. left: 4px;
  2914. top: 156px;
  2915. z-index: 2;
  2916. color: #8d8d8d;
  2917. font-size: 15px;
  2918. font-weight: 500;
  2919. user-select: none;
  2920. pointer-events: none;
  2921. line-height: 1;
  2922. -webkit-font-smoothing: none;
  2923. -moz-osx-font-smoothing: grayscale;
  2924. text-rendering: auto;
  2925. }
  2926.  
  2927. .modInput-wrapper {
  2928. position: relative;
  2929. display: inline-block;
  2930. width: 100%;
  2931. }
  2932.  
  2933. .modInput-secondary {
  2934. display: inline-block;
  2935. width: 100%;
  2936. padding: 10px 0 10px 15px;
  2937. font-weight: 400;
  2938. color: #E9E9E9;
  2939. background: #050505;
  2940. border: 0;
  2941. border-radius: 3px;
  2942. outline: 0;
  2943. text-indent: 70px;
  2944. transition: all .3s ease-in-out;
  2945. }
  2946. .modInput-secondary.t-indent-120 {
  2947. text-indent: 120px;
  2948. }
  2949. .modInput-secondary::-webkit-input-placeholder {
  2950. color: #050505;
  2951. text-indent: 0;
  2952. font-weight: 300;
  2953. }
  2954. .modInput-secondary + label {
  2955. display: inline-block;
  2956. position: absolute;
  2957. top: 8px;
  2958. left: 0;
  2959. bottom: 8px;
  2960. padding: 5px 15px;
  2961. color: #E9E9E9;
  2962. font-size: 11px;
  2963. font-weight: 700;
  2964. text-transform: uppercase;
  2965. text-shadow: 0 1px 0 rgba(19, 74, 70, 0);
  2966. transition: all .3s ease-in-out;
  2967. border-radius: 3px;
  2968. background: rgba(122, 134, 184, 0);
  2969. }
  2970. .modInput-secondary + label:after {
  2971. position: absolute;
  2972. content: "";
  2973. width: 0;
  2974. height: 0;
  2975. top: 100%;
  2976. left: 50%;
  2977. margin-left: -3px;
  2978. border-left: 3px solid transparent;
  2979. border-right: 3px solid transparent;
  2980. border-top: 3px solid rgba(122, 134, 184, 0);
  2981. transition: all .3s ease-in-out;
  2982. }
  2983.  
  2984. .modInput-secondary:focus,
  2985. .modInput-secondary:active {
  2986. color: #E9E9E9;
  2987. text-indent: 0;
  2988. background: #050505;
  2989. }
  2990. .modInput-secondary:focus::-webkit-input-placeholder,
  2991. .modInput-secondary:active::-webkit-input-placeholder {
  2992. color: #aaa;
  2993. }
  2994. .modInput-secondary:focus + label,
  2995. .modInput-secondary:active + label {
  2996. color: #fff;
  2997. text-shadow: 0 1px 0 rgba(19, 74, 70, 0.4);
  2998. background: #7A86B8;
  2999. transform: translateY(-40px);
  3000. }
  3001. .modInput-secondary:focus + label:after,
  3002. .modInput-secondary:active + label:after {
  3003. border-top: 4px solid #7A86B8;
  3004. }
  3005.  
  3006. /* Friends & account */
  3007.  
  3008. .signIn-overlay {
  3009. position: absolute;
  3010. top: 0;
  3011. left: 0;
  3012. width: 100%;
  3013. height: 100vh;
  3014. background: rgba(0, 0, 0, .4);
  3015. z-index: 999999;
  3016. display: flex;
  3017. justify-content: center;
  3018. align-items: center;
  3019. color: #E3E3E3;
  3020. opacity: 0;
  3021. transition: all .3s ease;
  3022. }
  3023.  
  3024. .signIn-wrapper {
  3025. background: #111111;
  3026. width: 450px;
  3027. display: flex;
  3028. flex-direction: column;
  3029. align-items: center;
  3030. border-radius: 10px;
  3031. color: #fafafa;
  3032. }
  3033.  
  3034. .signIn-header {
  3035. background: #181818;
  3036. width: 100%;
  3037. display: flex;
  3038. justify-content: space-between;
  3039. align-items: center;
  3040. padding: 8px;
  3041. border-radius: 10px 10px 0 0;
  3042. }
  3043. .signIn-header span {
  3044. font-weight: 500;
  3045. font-size: 20px;
  3046. }
  3047.  
  3048. .signIn-body {
  3049. display: flex;
  3050. flex-direction: column;
  3051. gap: 10px;
  3052. align-items: center;
  3053. justify-content: start;
  3054. padding: 40px 40px 5px 40px;
  3055. width: 100%;
  3056. }
  3057.  
  3058. #errMessages {
  3059. color: #AC3D3D;
  3060. flex-direction: column;
  3061. gap: 5px;
  3062. }
  3063.  
  3064. .friends_header {
  3065. display: flex;
  3066. flex-direction: row;
  3067. justify-content: space-between;
  3068. align-items: center;
  3069. gap: 10px;
  3070. width: 100%;
  3071. padding: 10px;
  3072. }
  3073.  
  3074. .friends_body {
  3075. position: relative;
  3076. display: flex;
  3077. flex-direction: column;
  3078. align-items: center;
  3079. gap: 6px;
  3080. width: 100%;
  3081. height: 360px;
  3082. max-height: 360px;
  3083. overflow-y: auto;
  3084. padding-right: 10px;
  3085. }
  3086. .allusers {
  3087. padding: 0;
  3088. }
  3089.  
  3090. #users-container {
  3091. position: relative;
  3092. display: flex;
  3093. flex-direction: column;
  3094. align-items: center;
  3095. gap: 6px;
  3096. width: 100%;
  3097. height: 340px;
  3098. max-height: 340px;
  3099. overflow-y: auto;
  3100. padding-right: 10px;
  3101. }
  3102.  
  3103. .profile-img {
  3104. position: relative;
  3105. width: 52px;
  3106. height: 52px;
  3107. border-radius: 100%;
  3108. border: 1px solid #C8C9D9;
  3109. }
  3110.  
  3111. .profile-img img {
  3112. width: 100%;
  3113. height: 100%;
  3114. border-radius: 100%;
  3115. }
  3116.  
  3117. .status_icon {
  3118. position: absolute;
  3119. width: 15px;
  3120. height: 15px;
  3121. top: 0;
  3122. left: 0;
  3123. border-radius: 50%;
  3124. }
  3125.  
  3126. .online_icon {
  3127. background-color: #3DB239;
  3128. }
  3129. .offline_icon {
  3130. background-color: #B23939;
  3131. }
  3132. .Owner_role {
  3133. color: #3979B2;
  3134. }
  3135. .Moderator_role {
  3136. color: #39B298;
  3137. }
  3138. .Vip_role {
  3139. color: #E1A33E;
  3140. }
  3141.  
  3142. .friends_row {
  3143. display: flex;
  3144. flex-direction: row;
  3145. width: 100%;
  3146. justify-content: space-between;
  3147. align-items: center;
  3148. background: #090909;
  3149. border-radius: 8px;
  3150. padding: 10px;
  3151. }
  3152.  
  3153. .friends_row .val {
  3154. width: fit-content;
  3155. height: fit-content;
  3156. padding: 5px 20px;
  3157. box-sizing: content-box;
  3158. }
  3159.  
  3160. .user-profile-wrapper {
  3161. cursor: pointer;
  3162. }
  3163. .user-profile-wrapper > .centerY.g-5 {
  3164. pointer-events: none;
  3165. user-select: none;
  3166. cursor: pointer;
  3167. }
  3168.  
  3169. .textarea-container {
  3170. position: relative;
  3171. width: 100%;
  3172. }
  3173. .textarea-container textarea {
  3174. width: 100%;
  3175. height: 120px;
  3176. resize: none;
  3177. }
  3178. .char-counter {
  3179. position: absolute;
  3180. bottom: 5px;
  3181. right: 5px;
  3182. color: gray;
  3183. }
  3184.  
  3185. .mod_badges {
  3186. display: flex;
  3187. flex-wrap: wrap;
  3188. gap: 5px;
  3189. }
  3190.  
  3191. .mod_badge {
  3192. width: fit-content;
  3193. background: #222;
  3194. color: #fafafa;
  3195. padding: 2px 7px;
  3196. border-radius: 9px;
  3197. }
  3198.  
  3199. .friends-chat-wrapper {
  3200. position: absolute;
  3201. top: 0;
  3202. left: 0;
  3203. width: 100%;
  3204. height: 100%;
  3205. background: #111111;
  3206. display: flex;
  3207. flex-direction: column;
  3208. z-index: 999;
  3209. opacity: 0;
  3210. transition: all .3s ease;
  3211. }
  3212.  
  3213. .friends-chat-header {
  3214. display: flex;
  3215. justify-content: space-between;
  3216. align-items: center;
  3217. background: #050505;
  3218. width: 100%;
  3219. padding: 8px;
  3220. height: 68px;
  3221. }
  3222.  
  3223. .friends-chat-body {
  3224. height: calc(100% - 68px);
  3225. display: flex;
  3226. flex-direction: column;
  3227. }
  3228.  
  3229. .friends-chat-messages {
  3230. height: 300px;
  3231. overflow-y: auto;
  3232. display: flex;
  3233. flex-direction: column;
  3234. gap: 5px;
  3235. padding: 10px;
  3236. }
  3237. .friends-message {
  3238. background: linear-gradient(180deg, rgb(12 12 12), #000);
  3239. padding: 8px;
  3240. border-radius: 12px;
  3241. display: flex;
  3242. flex-direction: column;
  3243. min-width: 80px;
  3244. max-width: 200px;
  3245. width: fit-content;
  3246. }
  3247.  
  3248. .message-date {
  3249. color: #8a8989;
  3250. font-size: 11px;
  3251. }
  3252.  
  3253. .message-right {
  3254. align-self: flex-end;
  3255. }
  3256.  
  3257. .messenger-wrapper {
  3258. width: 100%;
  3259. height: 60px;
  3260. padding: 10px;
  3261. }
  3262. .messenger-wrapper .container {
  3263. display: flex;
  3264. flex-direction: row;
  3265. gap: 10px;
  3266. width: 100%;
  3267. background: #0a0a0a;
  3268. padding: 10px 5px;
  3269. border-radius: 10px;
  3270. }
  3271.  
  3272. .messenger-wrapper .container input {
  3273. padding: 5px;
  3274. }
  3275. .messenger-wrapper .container button {
  3276. width: 150px;
  3277. }
  3278.  
  3279. /* deathscreen challenges */
  3280.  
  3281. #menu-wrapper {
  3282. overflow-x: visible;
  3283. overflow-y: visible;
  3284. }
  3285.  
  3286. .challenges_deathscreen {
  3287. width: 450px;
  3288. background: rgb(21, 21, 21);
  3289. display: flex;
  3290. flex-direction: column;
  3291. gap: 8px;
  3292. padding: 7px;
  3293. border-radius: 10px;
  3294. margin-bottom: 15px;
  3295. }
  3296.  
  3297. .challenges-col {
  3298. display: flex;
  3299. flex-direction: column;
  3300. gap: 4px;
  3301. align-items: center;
  3302. width: 100%;
  3303. }
  3304. .challenge-row {
  3305. display: flex;
  3306. align-items: center;
  3307. background: rgba(0, 0, 0, .4);
  3308. justify-content: space-between;
  3309. padding: 5px 10px;
  3310. border-radius: 4px;
  3311. width: 100%;
  3312. }
  3313.  
  3314. .challenges-title {
  3315. font-size: 16px;
  3316. font-weight: 600;
  3317. }
  3318.  
  3319. .challenge-best-secondary {
  3320. background: #1d1d1d;
  3321. border-radius: 10px;
  3322. text-align: center;
  3323. padding: 5px 8px;
  3324. min-width: 130px;
  3325. }
  3326. .challenge-collect-secondary {
  3327. background: #4C864B;
  3328. border-radius: 10px;
  3329. text-align: center;
  3330. padding: 5px 8px;
  3331. outline: none;
  3332. border: none;
  3333. min-width: 130px;
  3334. }
  3335.  
  3336. .alwan__reference {
  3337. border-width: 2px !important;
  3338. }
  3339.  
  3340. #mod-announcements {
  3341. display: flex;
  3342. flex-direction: column;
  3343. gap: 6px;
  3344. max-height: 144px;
  3345. overflow-y: auto;
  3346. }
  3347.  
  3348. .mod-announcement {
  3349. background: #111111;
  3350. border-radius: 4px;
  3351. display: flex;
  3352. gap: 3px;
  3353. width: 100%;
  3354. cursor: pointer;
  3355. padding: 5px 8px;
  3356. }
  3357.  
  3358. .mod-announcement-icon {
  3359. border-radius: 50%;
  3360. width: 35px;
  3361. height: 35px;
  3362. align-self: center;
  3363. }
  3364.  
  3365. .mod-announcement-text {
  3366. display: flex;
  3367. flex-direction: column;
  3368. gap: 3px;
  3369. overflow: hidden;
  3370. flex-grow: 1;
  3371. }
  3372.  
  3373. .mod-announcement-text > * {
  3374. overflow: hidden;
  3375. white-space: nowrap;
  3376. text-overflow: ellipsis;
  3377. }
  3378.  
  3379. .mod-announcement-text span {
  3380. font-size: 14px;
  3381. color: #ffffff;
  3382. flex-shrink: 0;
  3383. }
  3384.  
  3385. .mod-announcement-text p {
  3386. font-size: 10px;
  3387. color: #898989;
  3388. flex-shrink: 0;
  3389. margin: 0;
  3390. }
  3391.  
  3392. .mod-announcements-wrapper {
  3393. display: flex;
  3394. flex-direction: column;
  3395. gap: 10px;
  3396. }
  3397.  
  3398. .mod-announcement-content {
  3399. display: flex;
  3400. justify-content: space-between;
  3401. gap: 10px;
  3402. background-color: #050505;
  3403. padding: 10px;
  3404. border-radius: 10px;
  3405. background-image: url('https://czrsd.com/static/general/bg_blur.png');
  3406. background-repeat: no-repeat;
  3407. background-position: 300px 40%;
  3408. background-size: cover;
  3409. }
  3410.  
  3411. .mod-announcement-content p {
  3412. text-align: justify;
  3413. max-height: 330px;
  3414. overflow-y: auto;
  3415. padding-right: 10px;
  3416. }
  3417.  
  3418. .mod-announcement-images {
  3419. display: flex;
  3420. flex-direction: column;
  3421. gap: 20px;
  3422. min-width: 30%;
  3423. width: 30%;
  3424. max-height: 330px;
  3425. padding-right: 10px;
  3426. overflow-y: auto;
  3427. }
  3428.  
  3429. .mod-announcement-images img {
  3430. border-radius: 5px;
  3431. cursor: pointer;
  3432. }
  3433.  
  3434. #image-gallery {
  3435. display: flex;
  3436. flex-wrap: wrap;
  3437. gap: 5px;
  3438. }
  3439.  
  3440. .image-container {
  3441. display: flex;
  3442. flex-direction: column;
  3443. }
  3444.  
  3445. .image-container img {
  3446. width: 172px;
  3447. height: auto;
  3448. aspect-ratio: 16 / 9;
  3449. border-radius: 5px;
  3450. cursor: pointer;
  3451. }
  3452.  
  3453. .download_btn {
  3454. background: url('https://czrsd.com/static/sigmod/icons/download.svg');
  3455.  
  3456. }
  3457.  
  3458. .delete_btn {
  3459. background: url('https://czrsd.com/static/sigmod/icons/trash-bin.svg');
  3460. }
  3461.  
  3462. .operation_btn {
  3463. background-size: contain;
  3464. background-repeat: no-repeat;
  3465. border: none;
  3466. width: 22px;
  3467. height: 22px;
  3468. }
  3469.  
  3470. .sigmod-community {
  3471. display: flex;
  3472. flex-direction: column;
  3473. margin: auto;
  3474. border-radius: 10px;
  3475. width: 50%;
  3476. align-self: center;
  3477. font-size: 19px;
  3478. overflow: hidden;
  3479. }
  3480.  
  3481. .community-header {
  3482. background: linear-gradient(179deg, #000000, #0c0c0c);
  3483. text-align: center;
  3484. font-family: "Titillium Web", sans-serif;
  3485. padding: 6px;
  3486. }
  3487.  
  3488. .community-discord-logo {
  3489. background: rgb(88, 101, 242);
  3490. padding: 5px 12px;
  3491. }
  3492. .community-discord {
  3493. text-align: center;
  3494. padding: 10px;
  3495. background: #0c0c0c;
  3496. width: 100%;
  3497. }
  3498. .community-discord a {
  3499. font-family: "Titillium Web", sans-serif;
  3500. }
  3501.  
  3502. /* common */
  3503. .flex {
  3504. display: flex;
  3505. }
  3506. .centerX {
  3507. display: flex;
  3508. justify-content: center;
  3509. }
  3510. .centerY {
  3511. display: flex;
  3512. align-items: center;
  3513. }
  3514. .centerXY {
  3515. display: flex;
  3516. align-items: center;
  3517. justify-content: center
  3518. }
  3519. .f-column {
  3520. display: flex;
  3521. flex-direction: column;
  3522. }
  3523. mx-5 {
  3524. margin: 0 5px;
  3525. }
  3526. .my-5 {
  3527. margin: 5px 0;
  3528. }
  3529. .mt-auto {
  3530. margin-top: auto !important;
  3531. }
  3532. .g-2 {
  3533. gap: 2px;
  3534. }
  3535. .g-5 {
  3536. gap: 5px;
  3537. }
  3538. .g-10 {
  3539. gap: 10px;
  3540. }
  3541. .p-2 {
  3542. padding: 2px;
  3543. }
  3544. .p-5 {
  3545. padding: 5px;
  3546. }
  3547. .p-10 {
  3548. padding: 10px;
  3549. }
  3550. .rounded {
  3551. border-radius: 6px;
  3552. }
  3553. .text-center {
  3554. text-align: center;
  3555. }
  3556. .f-big {
  3557. font-size: 18px;
  3558. }
  3559. .hidden {
  3560. display: none;
  3561. }
  3562. `;
  3563. },
  3564. playBtn: byId('play-btn'),
  3565. respawnTime: Date.now(),
  3566. respawnCooldown: 1000,
  3567. get friends_settings() {
  3568. return this._friends_settings;
  3569. },
  3570. set friends_settings(value) {
  3571. this._friends_settings = value;
  3572. window.sigmod.friends_settings = value;
  3573. },
  3574. get friend_names() {
  3575. return this._friend_names;
  3576. },
  3577.  
  3578. set friend_names(value) {
  3579. this._friend_names = value;
  3580. window.sigmod.friend_names = value;
  3581. },
  3582.  
  3583. async game() {
  3584. const { fillRect, fillText, strokeText, arc, drawImage } =
  3585. CanvasRenderingContext2D.prototype;
  3586.  
  3587. // add a small delay so it works properly
  3588. setTimeout(() => {
  3589. const showPosition = byId('showPosition');
  3590. if (showPosition && !showPosition.checked) showPosition.click();
  3591. }, 1000);
  3592.  
  3593. const loadStorage = () => {
  3594. if (modSettings.virusImage) {
  3595. loadVirusImage(modSettings.virusImage);
  3596. }
  3597.  
  3598. if (modSettings.game.skins.original !== null) {
  3599. loadSkinImage(
  3600. modSettings.game.skins.original,
  3601. modSettings.game.skins.replacement
  3602. );
  3603. }
  3604. };
  3605.  
  3606. loadStorage();
  3607.  
  3608. let cachedPattern = null;
  3609. let patternCanvas = null;
  3610. let isUpdatingPattern = false;
  3611.  
  3612. function updatePattern(ctx) {
  3613. isUpdatingPattern = true;
  3614. loadPattern(ctx)
  3615. .then((pattern) => {
  3616. if (mods.mapImageLoaded) {
  3617. cachedPattern = pattern;
  3618. ctx.fillStyle = cachedPattern;
  3619. ctx.fillRect(
  3620. 0,
  3621. 0,
  3622. ctx.canvas.width,
  3623. ctx.canvas.height
  3624. );
  3625. } else {
  3626. clearPattern(ctx);
  3627. }
  3628. isUpdatingPattern = false;
  3629. })
  3630. .catch((e) => {
  3631. console.error('Error loading map image:', e);
  3632. clearPattern(ctx);
  3633. isUpdatingPattern = false;
  3634. });
  3635. }
  3636.  
  3637. function loadPattern(ctx) {
  3638. return new Promise((resolve, reject) => {
  3639. const img = new Image();
  3640. img.src = modSettings.game.map.image;
  3641. img.crossOrigin = 'Anonymous';
  3642.  
  3643. img.onload = () => {
  3644. if (!patternCanvas) {
  3645. patternCanvas = document.createElement('canvas');
  3646. }
  3647. patternCanvas.width = img.width;
  3648. patternCanvas.height = img.height;
  3649. const patternCtx = patternCanvas.getContext('2d');
  3650. patternCtx.drawImage(img, 0, 0);
  3651.  
  3652. const imageData = patternCtx.getImageData(
  3653. 0,
  3654. 0,
  3655. patternCanvas.width,
  3656. patternCanvas.height
  3657. );
  3658. const data = imageData.data;
  3659. mods.mapImageLoaded = !Array.from(data).some(
  3660. (_, i) => i % 4 === 3 && data[i] < 255
  3661. );
  3662.  
  3663. if (mods.mapImageLoaded) {
  3664. resolve(
  3665. ctx.createPattern(patternCanvas, 'no-repeat')
  3666. );
  3667. } else {
  3668. resolve(null);
  3669. }
  3670. };
  3671.  
  3672. img.onerror = () =>
  3673. reject(new Error('Failed to load image.'));
  3674. });
  3675. }
  3676.  
  3677. function clearPattern(ctx) {
  3678. isUpdatingPattern = true;
  3679. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  3680. ctx.fillStyle = modSettings.game.map.color || '#111111';
  3681. ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  3682. isUpdatingPattern = false;
  3683. }
  3684.  
  3685. window.addEventListener('resize', () => {
  3686. const canvas = document.getElementById('canvas');
  3687. if (canvas && modSettings.game.map.image) {
  3688. updatePattern(canvas.getContext('2d'));
  3689. }
  3690. });
  3691.  
  3692. /* CanvasRenderingContext2D.prototype */
  3693.  
  3694. CanvasRenderingContext2D.prototype.fillRect = function (
  3695. x,
  3696. y,
  3697. width,
  3698. height
  3699. ) {
  3700. if (this.canvas.id !== 'canvas')
  3701. return fillRect.apply(this, arguments);
  3702.  
  3703. const isCanvasSize =
  3704. (width + height) / 2 ===
  3705. (window.innerWidth + window.innerHeight) / 2;
  3706.  
  3707. if (isCanvasSize) {
  3708. if (modSettings.game.map.image && !mods.mapImageLoaded) {
  3709. mods.mapImageLoaded = true;
  3710. updatePattern(this);
  3711. } else if (
  3712. !modSettings.game.map.image ||
  3713. !mods.mapImageLoaded
  3714. ) {
  3715. if (!isUpdatingPattern) {
  3716. clearPattern(this);
  3717. }
  3718. } else {
  3719. this.fillStyle =
  3720. cachedPattern ||
  3721. modSettings.game.map.color ||
  3722. '#111111';
  3723. }
  3724. }
  3725.  
  3726. fillRect.apply(this, arguments);
  3727. };
  3728.  
  3729. CanvasRenderingContext2D.prototype.arc = function (x, y, radius) {
  3730. if (this.canvas.id !== 'canvas')
  3731. return arc.apply(this, arguments);
  3732.  
  3733. if (radius >= 86 && modSettings.game.cellColor) {
  3734. this.fillStyle = modSettings.game.cellColor;
  3735. } else if (
  3736. radius <= 20 &&
  3737. modSettings.game.foodColor !== null
  3738. ) {
  3739. this.fillStyle = modSettings.game.foodColor;
  3740. this.strokeStyle = modSettings.game.foodColor;
  3741. }
  3742.  
  3743. arc.apply(this, arguments);
  3744. };
  3745.  
  3746. let sentDead = false;
  3747. window.sawScoreThisFrame = false;
  3748. window.checkPlaying = () => {
  3749. if (window.sawScoreThisFrame) {
  3750. if (!window.gameSettings.isPlaying)
  3751. window.gameSettings.isPlaying = true;
  3752. } else {
  3753. if (window.gameSettings.isPlaying)
  3754. window.gameSettings.isPlaying = false;
  3755. }
  3756. window.sawScoreThisFrame = false;
  3757. requestAnimationFrame(checkPlaying);
  3758. };
  3759.  
  3760. CanvasRenderingContext2D.prototype.fillText = function (
  3761. text,
  3762. x,
  3763. y
  3764. ) {
  3765. if (this.canvas.id !== 'canvas')
  3766. return fillText.apply(this, arguments);
  3767.  
  3768. const currentFontSizeMatch = this.font.match(/^(\d+)px/);
  3769. const fontSize = currentFontSizeMatch
  3770. ? currentFontSizeMatch[0]
  3771. : '';
  3772.  
  3773. this.font = `${fontSize} ${modSettings.game.font || 'Ubuntu'}`;
  3774.  
  3775. if (
  3776. text === mods.nick &&
  3777. !modSettings.game.name.gradient.enabled &&
  3778. modSettings.game.name.color !== null
  3779. ) {
  3780. this.fillStyle = modSettings.game.name.color;
  3781. }
  3782.  
  3783. if (
  3784. text === mods.nick &&
  3785. modSettings.game.name.gradient.enabled
  3786. ) {
  3787. const width = this.measureText(text).width;
  3788. const fontSize = 8;
  3789. const gradient = this.createLinearGradient(
  3790. x - width / 2 + fontSize / 2,
  3791. y,
  3792. x + width / 2 - fontSize / 2,
  3793. y + fontSize
  3794. );
  3795.  
  3796. const color1 =
  3797. modSettings.game.name.gradient.left ?? '#ffffff';
  3798. const color2 =
  3799. modSettings.game.name.gradient.right ?? '#ffffff';
  3800.  
  3801. gradient.addColorStop(0, color1);
  3802. gradient.addColorStop(1, color2);
  3803.  
  3804. this.fillStyle = gradient;
  3805. }
  3806.  
  3807. if (!window.sigifx && text.startsWith('Score')) {
  3808. const score = parseInt(text.split(': ')[1]);
  3809. mods.cellSize = score;
  3810. mods.aboveRespawnLimit = score >= 5500;
  3811. window.sawScoreThisFrame = true;
  3812. }
  3813.  
  3814. if (!window.sigfix && text.startsWith('X:')) {
  3815. this.fillStyle = 'transparent';
  3816.  
  3817. const parts = text.split(', ');
  3818. const x = parseFloat(parts[0].slice(3));
  3819. const y = parseFloat(parts[1].slice(3));
  3820. if (isNaN(x) || isNaN(y)) return;
  3821.  
  3822. if (window.gameSettings.isPlaying) {
  3823. if (x === 0 && y === 0) return;
  3824.  
  3825. if (playerPosition.x !== x || playerPosition.y !== y) {
  3826. playerPosition.x = x;
  3827. playerPosition.y = y;
  3828.  
  3829. if (Date.now() - lastPosTime >= 300) {
  3830. if (
  3831. modSettings.settings.tag &&
  3832. client?.ws?.readyState === 1
  3833. ) {
  3834. sentDead = false;
  3835. client.send({
  3836. type: 'position',
  3837. content: { x, y },
  3838. });
  3839. }
  3840. lastPosTime = Date.now();
  3841. }
  3842. }
  3843. } else {
  3844. if (
  3845. playerPosition.x !== null ||
  3846. playerPosition.y !== null
  3847. ) {
  3848. playerPosition.x = null;
  3849. playerPosition.y = null;
  3850.  
  3851. if (
  3852. modSettings.settings.tag &&
  3853. client?.ws?.readyState === 1 &&
  3854. !sentDead
  3855. ) {
  3856. sentDead = true;
  3857. client.send({
  3858. type: 'position',
  3859. content: {
  3860. x: null,
  3861. y: null,
  3862. },
  3863. });
  3864. }
  3865. }
  3866. }
  3867. }
  3868.  
  3869. if (modSettings.game.removeOutlines) {
  3870. this.shadowBlur = 0;
  3871. this.shadowColor = 'transparent';
  3872. }
  3873.  
  3874. if (text.length > 18 && modSettings.game.shortenNames) {
  3875. arguments[0] = text.slice(0, 18) + '...';
  3876. }
  3877.  
  3878. // only for leaderboard
  3879. const name = text.match(/\d+\.\s*(.+)/)?.[1];
  3880.  
  3881. if (
  3882. name &&
  3883. mods.friend_names.has(name) &&
  3884. mods.friends_settings.highlight_friends
  3885. ) {
  3886. this.fillStyle = mods.friends_settings.highlight_color;
  3887. }
  3888.  
  3889. fillText.apply(this, arguments);
  3890. };
  3891.  
  3892. CanvasRenderingContext2D.prototype.strokeText = function (
  3893. text,
  3894. x,
  3895. y
  3896. ) {
  3897. if (this.canvas.id !== 'canvas')
  3898. return strokeText.apply(this, arguments);
  3899.  
  3900. const currentFontSizeMatch = this.font.match(/^(\d+)px/);
  3901. const fontSize = currentFontSizeMatch
  3902. ? currentFontSizeMatch[0]
  3903. : '';
  3904.  
  3905. this.font = `${fontSize} ${modSettings.game.font || 'Ubuntu'}`;
  3906.  
  3907. if (text.length > 18 && modSettings.game.shortenNames) {
  3908. arguments[0] = text.slice(0, 18) + '...';
  3909. }
  3910.  
  3911. if (modSettings.game.removeOutlines) {
  3912. this.shadowBlur = 0;
  3913. this.shadowColor = 'transparent';
  3914. } else {
  3915. this.shadowBlur = 7;
  3916. this.shadowColor = '#000';
  3917. }
  3918.  
  3919. strokeText.apply(this, arguments);
  3920. };
  3921.  
  3922. CanvasRenderingContext2D.prototype.drawImage = function (
  3923. image,
  3924. ...args
  3925. ) {
  3926. if (this.canvas.id !== 'canvas')
  3927. return drawImage.call(this, image, ...args);
  3928.  
  3929. if (
  3930. image.src &&
  3931. (image.src.endsWith('2.png') ||
  3932. image.src.endsWith('2-min.png')) &&
  3933. modSettings.game.virusImage
  3934. ) {
  3935. if (mods.virusImageLoaded) {
  3936. return drawImage.call(this, mods.virusImage, ...args);
  3937. } else {
  3938. loadVirusImage(modSettings.game.virusImage).then(() => {
  3939. drawImage.call(this, mods.virusImage, ...args);
  3940. });
  3941. return;
  3942. }
  3943. }
  3944.  
  3945. if (
  3946. image instanceof HTMLImageElement &&
  3947. modSettings.game.skins.original &&
  3948. image.src.includes(`${modSettings.game.skins.original}.png`)
  3949. ) {
  3950. if (mods.skinImageLoaded) {
  3951. return drawImage.call(this, mods.skinImage, ...args);
  3952. } else {
  3953. loadSkinImage(
  3954. modSettings.game.skins.original,
  3955. modSettings.game.skins.replacement
  3956. ).then(() => {
  3957. drawImage.call(this, mods.skinImage, ...args);
  3958. });
  3959. return;
  3960. }
  3961. }
  3962.  
  3963. drawImage.call(this, image, ...args);
  3964. };
  3965.  
  3966. function loadVirusImage(imgSrc) {
  3967. return new Promise((resolve, reject) => {
  3968. const replacementVirus = new Image();
  3969. replacementVirus.src = imgSrc;
  3970. replacementVirus.crossOrigin = 'Anonymous';
  3971.  
  3972. replacementVirus.onload = () => {
  3973. mods.virusImage = replacementVirus;
  3974. mods.virusImageLoaded = true;
  3975. resolve();
  3976. };
  3977.  
  3978. replacementVirus.onerror = () => {
  3979. console.error('Failed to load virus image.');
  3980. reject(new Error('Failed to load virus image.'));
  3981. };
  3982. });
  3983. }
  3984.  
  3985. function loadSkinImage(originalSkinName, replacementImgSrc) {
  3986. return new Promise((resolve, reject) => {
  3987. const replacementSkin = new Image();
  3988. replacementSkin.src = replacementImgSrc;
  3989. replacementSkin.crossOrigin = 'Anonymous';
  3990.  
  3991. replacementSkin.onload = () => {
  3992. mods.skinImage = replacementSkin;
  3993. mods.skinImageLoaded = true;
  3994. resolve();
  3995. };
  3996.  
  3997. replacementSkin.onerror = () =>
  3998. reject(new Error('Failed to load skin image.'));
  3999. });
  4000. }
  4001.  
  4002. const modals = {
  4003. map: {
  4004. title: 'Map Image',
  4005. applyId: 'apply-map-image',
  4006. resetId: 'reset-map-image',
  4007. previewId: 'preview-mapImage',
  4008. modalId: 'map-modal',
  4009. storagePath: 'game.map.image',
  4010. },
  4011. virus: {
  4012. title: 'Virus Image',
  4013. applyId: 'apply-virus-image',
  4014. resetId: 'reset-virus-image',
  4015. previewId: 'preview-virusImage',
  4016. modalId: 'virus-modal',
  4017. storagePath: 'game.virusImage',
  4018. },
  4019. skin: {
  4020. title: 'Skin Replacement',
  4021. applyId: 'apply-skin-image',
  4022. resetId: 'reset-skin-image',
  4023. previewId: 'preview-skinImage',
  4024. modalId: 'skin-modal',
  4025. storagePath: [
  4026. 'game.skins.original',
  4027. 'game.skins.replacement',
  4028. ],
  4029. additional: true,
  4030. },
  4031. };
  4032.  
  4033. function createModal({
  4034. title,
  4035. applyId,
  4036. resetId,
  4037. previewId,
  4038. modalId,
  4039. additional,
  4040. }) {
  4041. const additionalContent = additional
  4042. ? `
  4043. <span>Select a skin that should be replaced:</span>
  4044. <select class="form-control" id="skin-list"></select>
  4045. <span style="font-size: 12px;">Replacement image - Enter an image URL:</span>
  4046. `
  4047. : `
  4048. <span>Enter an image URL:</span>
  4049. `;
  4050.  
  4051. return `
  4052. <div class="default-modal">
  4053. <div class="default-modal-header">
  4054. <h2>${title}</h2>
  4055. <button class="btn closeBtn" id="closeCustomModal">
  4056. <svg width="22" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  4057. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  4058. </svg>
  4059. </button>
  4060. </div>
  4061. <div class="default-modal-body">
  4062. ${additionalContent}
  4063. <div class="centerXY g-10">
  4064. <input type="text" placeholder="https://i.imgur/..." class="form-control" id="image-url" />
  4065. <div class="imagePreview" id="${previewId}" title="Image Preview">
  4066. <span class="no-preview">No Preview Available</span>
  4067. </div>
  4068. </div>
  4069. <div class="centerXY g-10">
  4070. <button type="button" class="modButton-black" id="${applyId}">Apply Image</button>
  4071. <button type="button" class="resetButton" title="Reset ${title.toLowerCase()}" id="${resetId}"></button>
  4072. </div>
  4073. </div>
  4074. </div>
  4075. `;
  4076. }
  4077.  
  4078. function setupModalEvents(id, type) {
  4079. byId(id).addEventListener('click', async () => {
  4080. mods.customModal(
  4081. createModal(modals[type]),
  4082. modals[type].modalId
  4083. );
  4084. document
  4085. .querySelector('#closeCustomModal')
  4086. .addEventListener('click', () => closeModal(type));
  4087.  
  4088. const modal = modals[type];
  4089. const imageUrlInput = byId('image-url');
  4090.  
  4091. let initialUrl = '';
  4092. if (modal.additional && type === 'skin') {
  4093. const [originalPath, replacementPath] =
  4094. modal.storagePath;
  4095. initialUrl =
  4096. getNestedValue(modSettings, replacementPath) || '';
  4097. byId('skin-list').value =
  4098. getNestedValue(modSettings, originalPath) || '';
  4099. } else {
  4100. initialUrl =
  4101. getNestedValue(modSettings, modal.storagePath) ||
  4102. '';
  4103. }
  4104. imageUrlInput.value = initialUrl;
  4105. updatePreview(initialUrl);
  4106.  
  4107. imageUrlInput.addEventListener('input', (e) => {
  4108. updatePreview(e.target.value);
  4109. });
  4110.  
  4111. byId(modal.applyId).addEventListener('click', () =>
  4112. applyChanges(type)
  4113. );
  4114. byId(modal.resetId).addEventListener('click', () =>
  4115. resetChanges(type)
  4116. );
  4117.  
  4118. if (type === 'skin') {
  4119. const skinList = byId('skin-list');
  4120. const skins =
  4121. mods.skins.length > 0
  4122. ? mods.skins
  4123. : await fetch(
  4124. 'https://one.sigmally.com/api/skins'
  4125. )
  4126. .then((response) => response.json())
  4127. .then((data) => {
  4128. const skinNames = data.data.map(
  4129. (item) =>
  4130. item.name.replace('.png', '')
  4131. );
  4132. mods.skins = skinNames;
  4133. return skinNames;
  4134. });
  4135.  
  4136. skinList.innerHTML = skins
  4137. .map(
  4138. (skin) =>
  4139. `<option value="${skin}" ${
  4140. skin === modSettings.game.skins.original
  4141. ? 'selected'
  4142. : ''
  4143. }>${skin}</option>`
  4144. )
  4145. .join('');
  4146. }
  4147. });
  4148. }
  4149.  
  4150. function getNestedValue(obj, path) {
  4151. return path
  4152. .split('.')
  4153. .reduce((acc, part) => acc && acc[part], obj);
  4154. }
  4155.  
  4156. function setNestedValue(obj, path, value) {
  4157. const parts = path.split('.');
  4158. const last = parts.pop();
  4159. const target = parts.reduce(
  4160. (acc, part) => (acc[part] = acc[part] || {}),
  4161. obj
  4162. );
  4163. target[last] = value;
  4164. }
  4165.  
  4166. function updatePreview(url) {
  4167. const preview = document.querySelector('.imagePreview');
  4168. const noPreviewSpan = document.querySelector('.no-preview');
  4169.  
  4170. const updateVisibility = (showPreview) => {
  4171. preview.style.backgroundImage = showPreview
  4172. ? `url(${url})`
  4173. : 'none';
  4174. noPreviewSpan.style.display = showPreview
  4175. ? 'none'
  4176. : 'block';
  4177. };
  4178.  
  4179. if (url) {
  4180. const img = new Image();
  4181. img.src = url;
  4182. img.onload = () => updateVisibility(true);
  4183. img.onerror = () => updateVisibility(false);
  4184. } else {
  4185. updateVisibility(false);
  4186. }
  4187. }
  4188.  
  4189. function applyChanges(type) {
  4190. let { title, storagePath, additional } = modals[type];
  4191. const url = byId('image-url').value;
  4192.  
  4193. mods[`${type}ImageLoaded`] = false;
  4194.  
  4195. if (additional && type === 'skin') {
  4196. const selectedSkin = byId('skin-list').value;
  4197. setNestedValue(modSettings, storagePath[0], selectedSkin);
  4198. setNestedValue(modSettings, storagePath[1], url);
  4199. } else {
  4200. setNestedValue(modSettings, storagePath, url);
  4201. }
  4202.  
  4203. updateStorage();
  4204.  
  4205. mods.modAlert(`Successfully applied ${title}.`, 'success');
  4206. }
  4207.  
  4208. function resetChanges(type) {
  4209. const { title, storagePath, additional } = modals[type];
  4210.  
  4211. if (additional && type === 'skin') {
  4212. setNestedValue(modSettings, storagePath[0], null);
  4213. setNestedValue(modSettings, storagePath[1], null);
  4214. } else {
  4215. setNestedValue(modSettings, storagePath, null);
  4216. }
  4217.  
  4218. mods[`${type}ImageLoaded`] = false;
  4219.  
  4220. updateStorage();
  4221.  
  4222. mods.modAlert(
  4223. `The ${title} has been successfully reset.`,
  4224. 'success'
  4225. );
  4226. }
  4227.  
  4228. function closeModal(type) {
  4229. const overlay = byId(`${type}-modal`);
  4230. overlay.style.opacity = 0;
  4231. setTimeout(() => overlay.remove(), 300);
  4232. }
  4233.  
  4234. setupModalEvents('mapImageSelect', 'map');
  4235. setupModalEvents('virusImageSelect', 'virus');
  4236. setupModalEvents('skinReplaceSelect', 'skin');
  4237.  
  4238. const shortenNames = byId('shortenNames');
  4239. const removeOutlines = byId('removeOutlines');
  4240.  
  4241. shortenNames.addEventListener('change', () => {
  4242. modSettings.game.shortenNames = shortenNames.checked;
  4243. updateStorage();
  4244. });
  4245. removeOutlines.addEventListener('change', () => {
  4246. modSettings.game.removeOutlines = removeOutlines.checked;
  4247. updateStorage();
  4248. });
  4249.  
  4250. const showNames = byId('mod-showNames');
  4251. const showSkins = byId('mod-showSkins');
  4252.  
  4253. const originalShowNames = byId('showNames');
  4254. const originalShowSkins = byId('showSkins');
  4255.  
  4256. function syncCheckboxes() {
  4257. if (showNames.checked !== originalShowNames.checked) {
  4258. originalShowNames.click();
  4259. }
  4260. if (showSkins.checked !== originalShowSkins.checked) {
  4261. originalShowSkins.click();
  4262. }
  4263. }
  4264.  
  4265. showNames.addEventListener('change', syncCheckboxes);
  4266. showSkins.addEventListener('change', syncCheckboxes);
  4267. syncCheckboxes();
  4268.  
  4269. const deathScreenPos = byId('deathScreenPos');
  4270. const deathScreen = byId('__line2');
  4271.  
  4272. const applyMargin = (position) => {
  4273. switch (position) {
  4274. case 'left':
  4275. deathScreen.style.marginLeft = '0';
  4276. break;
  4277. case 'right':
  4278. deathScreen.style.marginRight = '0';
  4279. break;
  4280. case 'top':
  4281. deathScreen.style.marginTop = '20px';
  4282. break;
  4283. case 'bottom':
  4284. deathScreen.style.marginBottom = '20px';
  4285. break;
  4286. default:
  4287. deathScreen.style.margin = 'auto';
  4288. }
  4289. };
  4290.  
  4291. deathScreenPos.addEventListener('change', () => {
  4292. const selected = deathScreenPos.value;
  4293. applyMargin(selected);
  4294. modSettings.deathScreenPos = selected;
  4295. updateStorage();
  4296. });
  4297.  
  4298. const defaultPosition =
  4299. modSettings.settings.deathScreenPos || 'center';
  4300.  
  4301. applyMargin(defaultPosition);
  4302. deathScreenPos.value = defaultPosition;
  4303. },
  4304.  
  4305. customModal(children, id, zindex = '999999') {
  4306. const overlay = document.createElement('div');
  4307. overlay.classList.add('mod_overlay');
  4308. id && overlay.setAttribute('id', id);
  4309. overlay.innerHTML = children;
  4310. overlay.style.zIndex = zindex;
  4311. overlay.style.opacity = 0;
  4312.  
  4313. document.body.append(overlay);
  4314.  
  4315. setTimeout(() => {
  4316. overlay.style.opacity = '1';
  4317. });
  4318.  
  4319. const handleClickOutside = (e) => {
  4320. if (e.target === overlay) {
  4321. overlay.style.opacity = 0;
  4322. setTimeout(() => {
  4323. overlay.remove();
  4324. document.removeEventListener(
  4325. 'click',
  4326. handleClickOutside
  4327. );
  4328. }, 300);
  4329. }
  4330. };
  4331.  
  4332. document.addEventListener('click', handleClickOutside);
  4333. },
  4334.  
  4335. handleGoogleAuth(user) {
  4336. fetchedUser++;
  4337. window.gameSettings.user = user;
  4338.  
  4339. const chatSendInput = document.querySelector('#chatSendInput');
  4340. if (chatSendInput) {
  4341. chatSendInput.placeholder = 'message...';
  4342. chatSendInput.disabled = false;
  4343. }
  4344.  
  4345. if (!client) client = new modClient();
  4346.  
  4347. const waitForInit = () =>
  4348. new Promise((res) => {
  4349. if (client.ws?.readyState === 1 && mods.nick)
  4350. return res(null);
  4351. const i = setInterval(
  4352. () =>
  4353. client.ws?.readyState === 1 &&
  4354. mods.nick &&
  4355. (clearInterval(i), res(null)),
  4356. 50
  4357. );
  4358. });
  4359.  
  4360. waitForInit().then(() => {
  4361. client.send({
  4362. type: 'user',
  4363. content: { ...user, nick: mods.nick },
  4364. });
  4365. });
  4366.  
  4367. const claim = document.getElementById('free-chest-button');
  4368. if (
  4369. fetchedUser === 1 &&
  4370. modSettings.settings.autoClaimCoins &&
  4371. claim?.style.display !== 'none'
  4372. ) {
  4373. setTimeout(() => claim.click(), 500);
  4374. }
  4375. },
  4376.  
  4377. async menu() {
  4378. const mod_menu = document.createElement('div');
  4379. mod_menu.classList.add('mod_menu');
  4380. mod_menu.style.display = 'none';
  4381. mod_menu.style.opacity = '0';
  4382. mod_menu.innerHTML = `
  4383. <div class="mod_menu_wrapper">
  4384. <div class="mod_menu_header">
  4385. <img alt="Header image" src="${headerAnim}" draggable="false" class="header_img" />
  4386. <button type="button" class="modButton" id="closeBtn">
  4387. <svg width="18" height="20" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
  4388. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  4389. </svg>
  4390. </button>
  4391. </div>
  4392. <div class="mod_menu_inner">
  4393. <div class="mod_menu_navbar">
  4394. <button class="mod_nav_btn mod_selected" id="tab_home_btn">
  4395. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/home%20(1).png" alt="Home Icon" />
  4396. Home
  4397. </button>
  4398. <button class="mod_nav_btn" id="tab_macros_btn">
  4399. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/keyboard%20(1).png" alt="Keyboard Icon" />
  4400. Macros
  4401. </button>
  4402. <button class="mod_nav_btn" id="tab_game_btn">
  4403. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/games.png" alt="Game Icon" />
  4404. Game
  4405. </button>
  4406. <button class="mod_nav_btn" id="tab_name_btn">
  4407. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/836ca0f4c25fc6de2e429ee3583be5f860884a0c/images/icons/name.svg" alt="Name Icon" />
  4408. Name
  4409. </button>
  4410. <button class="mod_nav_btn" id="tab_themes_btn">
  4411. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/theme.png" alt="Themes Icon" />
  4412. Themes
  4413. </button>
  4414. <button class="mod_nav_btn" id="tab_gallery_btn">
  4415. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="22"><path fill="#ffffff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>
  4416. Gallery
  4417. </button>
  4418.  
  4419. <button class="mod_nav_btn" id="tab_friends_btn">
  4420. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/friends%20(1).png" alt="Friends Icon" />
  4421. Friends
  4422. </button>
  4423. <button class="mod_nav_btn mt-auto" id="tab_info_btn">
  4424. <img src="https://raw.githubusercontent.com/Sigmally/SigMod/main/images/icons/info.png" alt="Info Icon" />
  4425. Info
  4426. </button>
  4427. </div>
  4428. <div class="mod_menu_content">
  4429. <div class="mod_tab" id="mod_home">
  4430. <span class="text-center f-big" id="welcomeUser">Welcome ${
  4431. this.nick || 'Guest'
  4432. }, to the SigMod Client!</span>
  4433. <div class="home-card-row">
  4434. <!-- CARD.1 -->
  4435. <div class="home-card-wrapper">
  4436. <span>Your stats</span>
  4437. <div class="home-card">
  4438. <canvas id="sigmod-stats" width="200" height="100"></canvas>
  4439. </div>
  4440. </div>
  4441. <!-- CARD.2 -->
  4442. <div class="home-card-wrapper">
  4443. <span>Announcements</span>
  4444. <div class="home-card" style="justify-content: start;">
  4445. <div id="mod-announcements">No announcements yet...</div>
  4446. </div>
  4447. </div>
  4448. </div>
  4449. <div class="home-card-row">
  4450. <!-- CARD.3 -->
  4451. <div class="home-card-wrapper">
  4452. <span>Quick access</span>
  4453. <div class="home-card quickAccess scroll" id="mod_qaccess"></div>
  4454. </div>
  4455. <!-- CARD.4 -->
  4456. <div class="home-card-wrapper">
  4457. <span>Mod profile</span>
  4458. <div class="home-card">
  4459. <div class="justify-sb">
  4460. <div class="flex" style="align-items: center; gap: 5px;">
  4461. <img src="https://sigmally.com/assets/images/agario-profile.png" alt="Agar-io profile" width="50" height="50" id="my-profile-img" style="border-radius: 50%;" draggable="false" />
  4462. <span id="my-profile-name">Guest</span>
  4463. </div>
  4464. <span id="my-profile-role">Guest</span>
  4465. </div>
  4466. <div id="my-profile-badges"></div>
  4467. <hr />
  4468. <span id="my-profile-bio" class="scroll">No Bio.</span>
  4469. </div>
  4470. </div>
  4471. </div>
  4472. </div>
  4473. <div class="mod_tab scroll" id="mod_macros" style="display: none">
  4474. <div class="modColItems">
  4475. <div class="macros_wrapper">
  4476. <span class="text-center f-big">Keybindings</span>
  4477. <hr style="border-color: #3F3F3F">
  4478. <div style="justify-content: center;">
  4479. <div class="f-column g-10" style="align-items: center; justify-content: center;">
  4480. <div class="macrosContainer">
  4481. <div class="f-column g-10">
  4482. <label class="macroRow">
  4483. <span class="text">Rapid Feed</span>
  4484. <input type="text" name="rapidFeed" data-label="Rapid Feed" id="modinput1" class="keybinding" value="${
  4485. modSettings.macros
  4486. .keys
  4487. .rapidFeed ||
  4488. ''
  4489. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4490. </label>
  4491. <label class="macroRow">
  4492. <span class="text">Double Split</span>
  4493. <input type="text" name="splits.double" data-label="Double split" id="modinput2" class="keybinding" value="${
  4494. modSettings.macros
  4495. .keys.splits
  4496. .double || ''
  4497. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4498. </label>
  4499. <label class="macroRow">
  4500. <span class="text">Triple Split</span>
  4501. <input type="text" name="splits.triple" data-label="Triple split" id="modinput3" class="keybinding" value="${
  4502. modSettings.macros
  4503. .keys.splits
  4504. .triple || ''
  4505. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4506. </label>
  4507. <label class="macroRow">
  4508. <span class="text">Respawn</span>
  4509. <input type="text" name="respawn" data-label="Respawn" id="modinput15" class="keybinding" value="${
  4510. modSettings.macros
  4511. .keys
  4512. .respawn || ''
  4513. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4514. </label>
  4515. </div>
  4516. <div class="f-column g-10">
  4517. <label class="macroRow">
  4518. <span class="text">Quad Split</span>
  4519. <input type="text" name="splits.quad" data-label="Quad split" id="modinput4" class="keybinding" value="${
  4520. modSettings.macros
  4521. .keys.splits
  4522. .quad || ''
  4523. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4524. </label>
  4525. <label class="macroRow">
  4526. <span class="text">Horizontal Line</span>
  4527. <input type="text" name="line.horizontal" data-label="Horizontal line" id="modinput5" class="keybinding" value="${
  4528. modSettings.macros
  4529. .keys.line
  4530. .horizontal ||
  4531. ''
  4532. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4533. </label>
  4534. <label class="macroRow">
  4535. <span class="text">Vertical Line</span>
  4536. <input type="text" name="line.vertical" data-label="Vertical line" id="modinput7" class="keybinding" value="${
  4537. modSettings.macros
  4538. .keys.line
  4539. .vertical ||
  4540. ''
  4541. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4542. </label>
  4543. <label class="macroRow">
  4544. <span class="text">Fixed Line</span>
  4545. <input type="text" name="line.fixed" data-label="Fixed line" id="modinput16" class="keybinding" value="${
  4546. modSettings.macros
  4547. .keys.line
  4548. .fixed || ''
  4549. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4550. </label>
  4551. </div>
  4552. </div>
  4553. </div>
  4554. </div>
  4555. </div>
  4556. <div class="macros_wrapper">
  4557. <span class="text-center f-big">Advanced Keybinding options</span>
  4558. <div class="setting-card-wrapper">
  4559. <div class="setting-card">
  4560. <div class="setting-card-action">
  4561. <span class="setting-card-name">Mouse macros</span>
  4562. </div>
  4563. </div>
  4564. <div class="setting-parameters" style="display: none;">
  4565. <div class="my-5">
  4566. <span class="stats-info-text">Feed or Split with mouse buttons</span>
  4567. </div>
  4568. <div class="stats-line justify-sb">
  4569. <span class="centerXY g-5">
  4570. Mouse Button 1
  4571. <svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" width="24px" height="24px" viewBox="0 0 356.57 356.57"><path d="M181.563,0C120.762,0,59.215,30.525,59.215,88.873V237.5c0,65.658,53.412,119.071,119.071,119.071 c65.658,0,119.07-53.413,119.07-119.071V88.873C297.356,27.809,237.336,0,181.563,0z M274.945,237.5 c0,53.303-43.362,96.657-96.659,96.657c-53.299,0-96.657-43.354-96.657-96.657v-69.513c20.014,6.055,57.685,15.215,102.221,15.215 c28.515,0,59.831-3.809,91.095-14.567V237.5z M274.945,144.794c-81.683,31.233-168.353,7.716-193.316-0.364V88.873 c0-43.168,51.489-66.46,99.934-66.46c46.481,0,93.382,20.547,93.382,66.46V144.794z M190.893,48.389v81.248 c0,6.187-5.023,11.208-11.206,11.208c-6.185,0-11.207-5.021-11.207-11.208V48.389c0-6.186,5.021-11.207,11.207-11.207 C185.869,37.182,190.893,42.203,190.893,48.389z M154.938,40.068V143.73c-15.879,2.802-62.566-10.271-62.566-10.271 C80.233,41.004,154.938,40.068,154.938,40.068z"></path></svg>
  4572. </span>
  4573. <select class="form-control macro-extanded_input" style="padding: 2px; text-align: left; width: 100px" id="m1_macroSelect">
  4574. <option value="none">None</option>
  4575. <option value="fastfeed">Fast Feed</option>
  4576. <option value="split">Split (1)</option>
  4577. <option value="split2">Double Split</option>
  4578. <option value="split3">Triple Split</option>
  4579. <option value="split4">Quad Split</option>
  4580. <option value="freeze">Horizontal Line</option>
  4581. <option value="dTrick">Double Trick</option>
  4582. <option value="sTrick">Self Trick</option>
  4583. </select>
  4584. </div>
  4585. <div class="stats-line justify-sb">
  4586. <span class="centerXY g-5">
  4587. Mouse Button 2
  4588. <svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" width="24px" height="24px" viewBox="0 0 356.57 356.57"><path d="M181.563,0C120.762,0,59.215,30.525,59.215,88.873V237.5c0,65.658,53.412,119.071,119.071,119.071 c65.658,0,119.07-53.413,119.07-119.071V88.873C297.356,27.809,237.336,0,181.563,0z M274.945,237.5 c0,53.303-43.362,96.657-96.659,96.657c-53.299,0-96.657-43.354-96.657-96.657v-69.513c20.014,6.055,57.685,15.215,102.221,15.215 c28.515,0,59.831-3.809,91.095-14.567V237.5z M274.945,144.794c-81.683,31.233-168.353,7.716-193.316-0.364V88.873 c0-43.168,51.489-66.46,99.934-66.46c46.481,0,93.382,20.547,93.382,66.46V144.794z M190.893,48.389v81.248 c0,6.187-5.023,11.208-11.206,11.208c-6.185,0-11.207-5.021-11.207-11.208V48.389c0-6.186,5.021-11.207,11.207-11.207 C185.869,37.182,190.893,42.203,190.893,48.389z M154.938,40.068V143.73c-15.879,2.802-62.566-10.271-62.566-10.271 C80.233,41.004,154.938,40.068,154.938,40.068z"></path></svg>
  4589. </span>
  4590. <select class="form-control" style="padding: 2px; text-align: left; width: 100px" id="m2_macroSelect">
  4591. <option value="none">None</option>
  4592. <option value="fastfeed">Fast Feed</option>
  4593. <option value="split">Split (1)</option>
  4594. <option value="split2">Double Split</option>
  4595. <option value="split3">Triple Split</option>
  4596. <option value="split4">Quad Split</option>
  4597. <option value="freeze">Horizontal Line</option>
  4598. <option value="dTrick">Double Trick</option>
  4599. <option value="sTrick">Self Trick</option>
  4600. </select>
  4601. </div>
  4602. </div>
  4603. </div>
  4604. <div class="setting-card-wrapper">
  4605. <div class="setting-card">
  4606. <div class="setting-card-action">
  4607. <span class="setting-card-name">Rapid feed</span>
  4608. </div>
  4609. </div>
  4610.  
  4611. <div class="setting-parameters" style="display: none;">
  4612. <div class="my-5">
  4613. <span class="stats-info-text">Customize feeding</span>
  4614. </div>
  4615. <div class="stats-line justify-sb">
  4616. <span>Speed</span>
  4617. <div class="justify-sb g-5" style="width: 200px;">
  4618. <span class="mod_badge" id="macroSpeedText">${
  4619. modSettings.macros
  4620. .feedSpeed ||
  4621. '50'
  4622. }ms</span>
  4623. <input
  4624. type="range"
  4625. class="modSlider"
  4626. id="macroSpeed"
  4627. min="5"
  4628. max="100"
  4629. value="${modSettings.macros.feedSpeed || 50}"
  4630. step="5"
  4631. style="width: 134px;"
  4632. />
  4633. </div>
  4634. </div>
  4635. </div>
  4636. </div>
  4637. <div class="setting-card-wrapper">
  4638. <div class="setting-card">
  4639. <div class="setting-card-action">
  4640. <span class="setting-card-name">Linesplits</span>
  4641. </div>
  4642. </div>
  4643.  
  4644. <div class="setting-parameters" style="display: none;">
  4645. <div class="my-5">
  4646. <span class="stats-info-text">Customize linesplits</span>
  4647. </div>
  4648.  
  4649. <div class="stats-line justify-sb">
  4650. <span>Instant split</span>
  4651. <div class="centerXY g-5">
  4652. <span class="modDescText">Splits - </span>
  4653. <input type="text" class="modInput modNumberInput text-center" placeholder="0" title="Splits" style="width: 30px;" id="instant-split-amount" onclick="this.select()" />
  4654. <div class="modCheckbox">
  4655. <input id="toggle-instant-split" type="checkbox" checked />
  4656. <label class="cbx" for="toggle-instant-split"></label>
  4657. </div>
  4658. </div>
  4659. </div>
  4660. </div>
  4661. </div>
  4662. <div class="setting-card-wrapper">
  4663. <div class="setting-card">
  4664. <div class="setting-card-action">
  4665. <span class="setting-card-name">Toggle Settings</span>
  4666. </div>
  4667. </div>
  4668.  
  4669. <div class="setting-parameters" style="display: none;">
  4670. <div class="my-5">
  4671. <span class="stats-info-text">Toggle settings with a keybind.</span>
  4672. </div>
  4673.  
  4674. <div class="stats-line justify-sb">
  4675. <span>Toggle Menu</span>
  4676. <input type="text" name="toggle.menu" data-label="Toggle menu" id="modinput6" class="keybinding" value="${
  4677. modSettings.macros.keys
  4678. .toggle.menu || ''
  4679. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4680. </div>
  4681.  
  4682. <div class="stats-line justify-sb">
  4683. <span>Toggle Names</span>
  4684. <input value="${
  4685. modSettings.macros.keys
  4686. .toggle.names || ''
  4687. }" placeholder="..." readonly id="modinput10" name="toggle.names" data-label="Toggle names" class="keybinding" onfocus="this.select();">
  4688. </div>
  4689.  
  4690. <div class="stats-line justify-sb">
  4691. <span>Toggle Skins</span>
  4692. <input value="${
  4693. modSettings.macros.keys
  4694. .toggle.skins || ''
  4695. }" placeholder="..." readonly id="modinput11" name="toggle.skins" data-label="Toggle skins" class="keybinding" onfocus="this.select();">
  4696. </div>
  4697.  
  4698. <div class="stats-line justify-sb">
  4699. <span>Toggle Autorespawn</span>
  4700. <input value="${
  4701. modSettings.macros.keys
  4702. .toggle
  4703. .autoRespawn || ''
  4704. }" placeholder="..." readonly id="modinput12" name="toggle.autoRespawn" data-label="Toggle Auto respawn" class="keybinding" onfocus="this.select();">
  4705. </div>
  4706. </div>
  4707. </div>
  4708. <div class="setting-card-wrapper">
  4709. <div class="setting-card">
  4710. <div class="setting-card-action">
  4711. <span class="setting-card-name">Tricksplits</span>
  4712. </div>
  4713. </div>
  4714. <div class="setting-parameters" style="display: none;">
  4715. <div class="my-5">
  4716. <span class="stats-info-text">Other split options - splits with delay</span>
  4717. </div>
  4718. <div class="stats-line justify-sb">
  4719. <span>Double Trick</span>
  4720. <input value="${
  4721. modSettings.macros.keys
  4722. .splits
  4723. .doubleTrick || ''
  4724. }" placeholder="..." readonly id="modinput13" name="splits.doubleTrick" data-label="Double tricksplit" class="keybinding" onfocus="this.select();">
  4725. </div>
  4726. <div class="stats-line justify-sb">
  4727. <span>Self Trick</span>
  4728. <input value="${
  4729. modSettings.macros.keys
  4730. .splits.selfTrick ||
  4731. ''
  4732. }" placeholder="..." readonly id="modinput14" name="splits.selfTrick" data-label="Self tricksplit" class="keybinding" onfocus="this.select();">
  4733. </div>
  4734. </div>
  4735. </div>
  4736. </div>
  4737. </div>
  4738. </div>
  4739. <div class="mod_tab scroll" id="mod_game" style="display: none">
  4740. <div class="modColItems">
  4741. <div class="modRowItems" style="align-items: start;">
  4742. <div class="modColItems_2">
  4743. <span style="font-style: italic;">~ Game Colors</span>
  4744. <div class="justify-sb w-100 p-5 rounded">
  4745. <span class="text">Map</span>
  4746. <div id="mapColor"></div>
  4747. </div>
  4748. <div class="justify-sb w-100 accent_row p-5 rounded">
  4749. <span class="text">Border</span>
  4750. <div id="borderColor"></div>
  4751. </div>
  4752. <div class="justify-sb w-100 p-5 rounded">
  4753. <span class="text" title="Does not work with jelly physics">Food</span>
  4754. <div id="foodColor"></div>
  4755. </div>
  4756. <div class="justify-sb w-100 accent_row p-5 rounded">
  4757. <span class="text" title="Does not work with jelly physics">Cells</span>
  4758. <div id="cellColor"></div>
  4759. </div>
  4760. </div>
  4761. <div class="modColItems_2">
  4762. <span style="font-style: italic;">~ Game Images</span>
  4763. <div class="justify-sb w-100 p-5 rounded">
  4764. <span class="text">Map Image</span>
  4765. <button class="btn select-btn" id="mapImageSelect"></button>
  4766. </div>
  4767. <div class="justify-sb w-100 accent_row p-5 rounded">
  4768. <span class="text">Virus Image</span>
  4769. <button class="btn select-btn" id="virusImageSelect"></button>
  4770. </div>
  4771. <div class="justify-sb w-100 p-5 rounded">
  4772. <span class="text">Replace Skins</span>
  4773. <button class="btn select-btn" id="skinReplaceSelect"></button>
  4774. </div>
  4775. </div>
  4776. </div>
  4777. <div class="modColItems_2">
  4778. <span style="font-style: italic;">~ Game Settings</span>
  4779. <div class="justify-sb w-100 accent_row p-10 rounded">
  4780. <span class="text">Font</span>
  4781. <div id="font-select-container"></div>
  4782. </div>
  4783. <div class="justify-sb w-100 p-10">
  4784. <span class="text">Names</span>
  4785. <div class="modCheckbox">
  4786. <input id="mod-showNames" type="checkbox" ${
  4787. JSON.parse(
  4788. localStorage.getItem(
  4789. 'settings'
  4790. )
  4791. )?.showNames
  4792. ? 'checked'
  4793. : ''
  4794. } />
  4795. <label class="cbx" for="mod-showNames"></label>
  4796. </div>
  4797. </div>
  4798. <div class="justify-sb w-100 accent_row p-10 rounded">
  4799. <span class="text">Skins</span>
  4800. <div class="modCheckbox">
  4801. <input id="mod-showSkins" type="checkbox" ${
  4802. JSON.parse(
  4803. localStorage.getItem(
  4804. 'settings'
  4805. )
  4806. )?.showSkins
  4807. ? 'checked'
  4808. : ''
  4809. } />
  4810. <label class="cbx" for="mod-showSkins"></label>
  4811. </div>
  4812. </div>
  4813. <div class="justify-sb w-100 p-10 rounded">
  4814. <span title="Long nicknames will be shorten on the leaderboard & ingame">Shorten names</span>
  4815. <div class="modCheckbox">
  4816. <input id="shortenNames" type="checkbox" ${
  4817. modSettings.game
  4818. .shortenNames && 'checked'
  4819. } />
  4820. <label class="cbx" for="shortenNames"></label>
  4821. </div>
  4822. </div>
  4823. <div class="justify-sb w-100 accent_row p-10">
  4824. <span>Text outlines & shadows</span>
  4825. <div class="modCheckbox">
  4826. <input id="removeOutlines" type="checkbox" ${
  4827. modSettings.gameShortenNames &&
  4828. 'checked'
  4829. } />
  4830. <label class="cbx" for="removeOutlines"></label>
  4831. </div>
  4832. </div>
  4833. <div class="justify-sb w-100 rounded" style="padding: 5px 10px;">
  4834. <span class="text">Death screen Position</span>
  4835. <select id="deathScreenPos" class="form-control" style="width: 30%">
  4836. <option value="center" selected>Center</option>
  4837. <option value="left">Left</option>
  4838. <option value="right">Right</option>
  4839. <option value="top">Top</option>
  4840. <option value="bottom">Bottom</option>
  4841. </select>
  4842. </div>
  4843. <div class="justify-sb w-100 accent_row p-10 rounded">
  4844. <span class="text">Play timer</span>
  4845. <div class="modCheckbox">
  4846. <input type="checkbox" id="playTimerToggle" ${
  4847. modSettings.settings
  4848. .playTimer && 'checked'
  4849. } />
  4850. <label class="cbx" for="playTimerToggle"></label>
  4851. </div>
  4852. </div>
  4853. <div class="justify-sb w-100 p-10 rounded">
  4854. <span class="text">Mouse tracker</span>
  4855. <div class="modCheckbox">
  4856. <input type="checkbox" id="mouseTrackerToggle" ${
  4857. modSettings.settings
  4858. .mouseTracker && 'checked'
  4859. } />
  4860. <label class="cbx" for="mouseTrackerToggle"></label>
  4861. </div>
  4862. </div>
  4863. </div>
  4864. <div class="modRowItems justify-sb">
  4865. <span class="text">Reset settings: </span>
  4866. <button class="modButton-secondary" id="resetModSettings" type="button">Reset mod settings</button>
  4867. <button class="modButton-secondary" id="resetGameSettings" type="button">Reset game settings</button>
  4868. </div>
  4869. </div>
  4870. </div>
  4871.  
  4872. <div class="mod_tab scroll" id="mod_name" style="display: none">
  4873. <div class="modColItems">
  4874. <div class="modRowItems justify-sb" style="align-items: start;">
  4875. <div class="f-column g-5" style="align-items: start; justify-content: start;">
  4876. <span class="modTitleText">Name fonts & special characters</span>
  4877. <span class="modDescText">Customize your name with special characters or fonts</span>
  4878. </div>
  4879. <div class="f-column g-5">
  4880. <button class="modButton-secondary" onclick="window.open('https://nickfinder.com', '_blank')">Nickfinder</button>
  4881. <button class="modButton-secondary" onclick="window.open('https://www.stylishnamemaker.com', '_blank')">Stylish Name</button>
  4882. <button class="modButton-secondary" onclick="window.open('https://www.tell.wtf', '_blank')">Tell.wtf</button>
  4883. </div>
  4884. </div>
  4885. <div class="modRowItems justify-sb">
  4886. <div class="f-column g-5">
  4887. <span class="modTitleText">Save names</span>
  4888. <span class="modDescText">Save your names locally</span>
  4889. <div class="flex g-5">
  4890. <input class="modInput" placeholder="Enter a name..." id="saveNameValue" />
  4891. <button id="saveName" class="modButton-secondary f-big" style="border-radius: 5px; background: url('https://sigmally.com/assets/images/icon/plus.svg'); background-color: #111; background-size: 50% auto; background-repeat: no-repeat; background-position: center;"></button>
  4892. </div>
  4893. <div id="savedNames" class="f-column scroll"></div>
  4894. </div>
  4895. <div class="vr"></div>
  4896. <div class="f-column g-5">
  4897. <span class="modTitleText">Name Color</span>
  4898. <span class="modDescText">Customize your name color</span>
  4899. <div class="justify-sb">
  4900. <input type="color" value="#ffffff" id="nameColor" class="colorInput">
  4901. </div>
  4902. <span class="modTitleText">Gradient Name</span>
  4903. <span class="modDescText">Customize your name with a gradient color</span>
  4904. <div class="justify-sb">
  4905. <div class="flex g-2" style="align-items: center">
  4906. <input type="color" value="#ffffff" id="gradientNameColor1" class="colorInput">
  4907. <span>➜ First color</span>
  4908. </div>
  4909. </div>
  4910. <div class="justify-sb">
  4911. <div class="flex g-2" style="align-items: center">
  4912. <input type="color" value="#ffffff" id="gradientNameColor2" class="colorInput">
  4913. <span>➜ Second color</span>
  4914. </div>
  4915. </div>
  4916. </div>
  4917. </div>
  4918. </div>
  4919. </div>
  4920. <div class="mod_tab scroll" id="mod_themes" style="display: none">
  4921. <span>Background presets</span>
  4922. <div class="themes scroll" id="themes">
  4923. <div class="theme" id="createTheme">
  4924. <div class="themeContent" style="background: url('https://sigmally.com/assets/images/icon/plus.svg'); background-size: 50% auto; background-repeat: no-repeat; background-position: center;"></div>
  4925. <div class="themeName text" style="color: #fff">Create</div>
  4926. </div>
  4927. </div>
  4928.  
  4929. <div class="modColItems_2" style="margin-top: 5px;">
  4930. <div class="justify-sb w-100 p-10">
  4931. <span>Input border radius</span>
  4932. <div class="centerXY g-10" style="width: 40%">
  4933. <button id="reset_input_radius" class="resetButton"></button>
  4934. <input type="range" class="modSlider" id="theme-inputBorderRadius" max="20" step="2" />
  4935. </div>
  4936. </div>
  4937. <div class="justify-sb w-100 p-10 accent_row rounded">
  4938. <span>Menu border radius</span>
  4939. <div class="centerXY g-10" style="width: 40%">
  4940. <button id="reset_menu_radius" class="resetButton"></button>
  4941. <input type="range" class="modSlider"id="theme-menuBorderRadius" max="50" step="2" />
  4942. </div>
  4943. </div>
  4944. <div class="justify-sb w-100 p-10">
  4945. <span>Input border</span>
  4946. <div class="modCheckbox">
  4947. <input id="theme-inputBorder" type="checkbox" />
  4948. <label class="cbx" for="theme-inputBorder"></label>
  4949. </div>
  4950. </div>
  4951. <div class="justify-sb w-100 p-10 accent_row rounded">
  4952. <span>Challenges on deathscreen</span>
  4953. <div class="modCheckbox">
  4954. <input id="showChallenges" type="checkbox" />
  4955. <label class="cbx" for="showChallenges"></label>
  4956. </div>
  4957. </div>
  4958. <div class="justify-sb w-100 p-10">
  4959. <span>Remove shop popup</span>
  4960. <div class="modCheckbox">
  4961. <input id="removeShopPopup" type="checkbox" />
  4962. <label class="cbx" for="removeShopPopup"></label>
  4963. </div>
  4964. </div>
  4965. <div class="justify-sb w-100 p-10 accent_row rounded">
  4966. <span>Hide Discord Buttons</span>
  4967. <div class="modCheckbox">
  4968. <input id="hideDiscordBtns" type="checkbox" />
  4969. <label class="cbx" for="hideDiscordBtns"></label>
  4970. </div>
  4971. </div>
  4972. <div class="justify-sb w-100 p-10">
  4973. <span>Hide Language Buttons</span>
  4974. <div class="modCheckbox">
  4975. <input id="hideLangs" type="checkbox" />
  4976. <label class="cbx" for="hideLangs"></label>
  4977. </div>
  4978. </div>
  4979. </div>
  4980. </div>
  4981. <div class="mod_tab scroll" id="mod_gallery" style="display: none">
  4982. <div class="modColItems_2">
  4983. <label class="macroRow w-100">
  4984. <span class="text">Keybind to save image</span>
  4985. <input type="text" name="saveImage" data-label="Save image" id="modinput17" class="keybinding" value="${
  4986. modSettings.macros.keys.saveImage ||
  4987. ''
  4988. }" maxlength="1" onfocus="this.select()" placeholder="..." />
  4989. </label>
  4990. </div>
  4991. <div class="modColItems_2">
  4992. <span>Image gallery</span>
  4993. <div class="flex g-5">
  4994. <button class="modButton" id="gallery-download">Download all</button>
  4995. <button class="modButton" id="gallery-delete">Delete all</button>
  4996. </div>
  4997. <div id="image-gallery"></div>
  4998. </div>
  4999. </div>
  5000. <div class="mod_tab scroll centerXY" id="mod_friends" style="display: none">
  5001. <div class="centerXY f-big" style="margin-top: 10px;">Connect and discover new friends with SigMod.</div>
  5002. <div class="centerXY">Do you have problems with your account? Create a support ticket in our <a href="https://discord.gg/RjxeZ2eRGg" target="_blank">Discord server</a>.</div>
  5003.  
  5004. <div class="centerXY f-column g-5" style="height: 300px; width: 165px;">
  5005. <button class="modButton-black" id="createAccount">
  5006. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16"><path fill="#ffffff" d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.7 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160V416c0 53 43 96 96 96H352c53 0 96-43 96-96V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H96z"/></svg>
  5007. Create account
  5008. </button>
  5009. <button class="modButton-black" id="login">
  5010. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16"><path fill="#ffffff" d="M217.9 105.9L340.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L217.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1L32 320c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM352 416l64 0c17.7 0 32-14.3 32-32l0-256c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c53 0 96 43 96 96l0 256c0 53-43 96-96 96l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>
  5011. Login
  5012. </button>
  5013. </div>
  5014. </div>
  5015. <div class="mod_tab scroll f-column g-5 text-center" id="mod_info" style="display: none">
  5016. <div class="brand_wrapper">
  5017. <img src="https://czrsd.com/static/sigmod/info_bg_2.jpeg" alt="Info background" class="brand_img" />
  5018. <span>SigMod V${version} by Cursed</span>
  5019. </div>
  5020. <span>Thanks to</span>
  5021. <ul class="brand_credits">
  5022. <li>Jb</li>
  5023. <li>Black</li>
  5024. <li>8y8x</li>
  5025. <li>Dreamz</li>
  5026. <li>Ultra</li>
  5027. <li>Xaris</li>
  5028. <li>Benzofury</li>
  5029. </ul>
  5030. <p>for contributing to the mods evolution into what it is today.</p>
  5031.  
  5032. <div class="sigmod-community">
  5033. <div class="community-header">
  5034. Community
  5035. </div>
  5036. <div class="flex">
  5037. <div class="community-discord-logo">
  5038. <svg width="31" height="30" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-top: 3px;">
  5039. <path d="M19.4566 5.35132C21.7154 8.83814 22.8309 12.7712 22.4139 17.299C22.4121 17.3182 22.4026 17.3358 22.3876 17.3473C20.6771 18.666 19.0199 19.4663 17.3859 19.9971C17.3732 20.0011 17.3596 20.0009 17.347 19.9964C17.3344 19.992 17.3234 19.9835 17.3156 19.9721C16.9382 19.4207 16.5952 18.8393 16.2947 18.2287C16.2774 18.1928 16.2932 18.1495 16.3287 18.1353C16.8734 17.9198 17.3914 17.6615 17.8896 17.3557C17.9289 17.3316 17.9314 17.2725 17.8951 17.2442C17.7894 17.1617 17.6846 17.0751 17.5844 16.9885C17.5656 16.9725 17.5404 16.9693 17.5191 16.9801C14.2844 18.5484 10.7409 18.5484 7.46792 16.9801C7.44667 16.9701 7.42142 16.9735 7.40317 16.9893C7.30317 17.0759 7.19817 17.1617 7.09342 17.2442C7.05717 17.2725 7.06017 17.3316 7.09967 17.3557C7.59792 17.6557 8.11592 17.9198 8.65991 18.1363C8.69517 18.1505 8.71192 18.1928 8.69442 18.2287C8.40042 18.8401 8.05742 19.4215 7.67292 19.9729C7.65617 19.9952 7.62867 20.0055 7.60267 19.9971C5.97642 19.4663 4.31917 18.666 2.60868 17.3473C2.59443 17.3358 2.58418 17.3174 2.58268 17.2982C2.23418 13.3817 2.94442 9.41613 5.53717 5.35053C5.54342 5.33977 5.55292 5.33137 5.56392 5.32638C6.83967 4.71165 8.20642 4.25939 9.63491 4.00111C9.66091 3.99691 9.68691 4.00951 9.70041 4.03365C9.87691 4.36176 10.0787 4.78252 10.2152 5.12637C11.7209 4.88489 13.2502 4.88489 14.7874 5.12637C14.9239 4.78987 15.1187 4.36176 15.2944 4.03365C15.3007 4.02167 15.3104 4.01208 15.3221 4.00623C15.3339 4.00039 15.3471 3.99859 15.3599 4.00111C16.7892 4.26018 18.1559 4.71244 19.4306 5.32638C19.4419 5.33137 19.4511 5.33977 19.4566 5.35132ZM10.9807 12.798C10.9964 11.6401 10.1924 10.6821 9.18316 10.6821C8.18217 10.6821 7.38592 11.6317 7.38592 12.798C7.38592 13.9639 8.19792 14.9136 9.18316 14.9136C10.1844 14.9136 10.9807 13.9639 10.9807 12.798ZM17.6261 12.798C17.6419 11.6401 16.8379 10.6821 15.8289 10.6821C14.8277 10.6821 14.0314 11.6317 14.0314 12.798C14.0314 13.9639 14.8434 14.9136 15.8289 14.9136C16.8379 14.9136 17.6261 13.9639 17.6261 12.798Z" fill="white"></path>
  5040. </svg>
  5041. </div>
  5042. <div class="community-discord">
  5043. <a href="https://dsc.gg/sigmodz" target="_blank">dsc.gg/sigmodz</a>
  5044. </div>
  5045. </div>
  5046. </div>
  5047. <div>
  5048. Install <a href="https://greatest.deepsurf.us/scripts/483587-sigmally-fixes-v2" target="_blank">Sigmally Fixes</a> for better performance!
  5049. </div>
  5050.  
  5051. <div class="mt-auto flex f-column g-10">
  5052. <div class="brand_yt">
  5053. <div class="yt_wrapper" onclick="window.open('https://www.youtube.com/@sigmallyCursed')">
  5054. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="26" height="26">
  5055. <path d="M12 39c-.549 0-1.095-.15-1.578-.447A3.008 3.008 0 0 1 9 36V12c0-1.041.54-2.007 1.422-2.553a3.014 3.014 0 0 1 2.919-.132l24 12a3.003 3.003 0 0 1 0 5.37l-24 12c-.42.21-.885.315-1.341.315z" fill="#ffffff"></path>
  5056. </svg>
  5057. <span style="font-size: 16px;">Cursed</span>
  5058. </div>
  5059. <div class="yt_wrapper" onclick="window.open('https://www.youtube.com/@sigmally')">
  5060. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="26" height="26">
  5061. <path d="M12 39c-.549 0-1.095-.15-1.578-.447A3.008 3.008 0 0 1 9 36V12c0-1.041.54-2.007 1.422-2.553a3.014 3.014 0 0 1 2.919-.132l24 12a3.003 3.003 0 0 1 0 5.37l-24 12c-.42.21-.885.315-1.341.315z" fill="#ffffff"></path>
  5062. </svg>
  5063. <span style="font-size: 16px;">Sigmally</span>
  5064. </div>
  5065. </div>
  5066. <div class="w-100 centerXY">
  5067. <div class="justify-sb" style="width: 50%;">
  5068. <a href="https://mod.czrsd.com/" target="_blank">Website</a>
  5069. <a href="https://mod.czrsd.com/changelog" target="_blank">Changelog</a>
  5070. <a href="https://mod.czrsd.com/tos" target="_blank">Terms of Service</a>
  5071. </div>
  5072. </div>
  5073. </div>
  5074. </div>
  5075. </div>
  5076. </div>
  5077. </div>
  5078. `;
  5079. document.body.append(mod_menu);
  5080.  
  5081. const styleTag = document.createElement('style');
  5082. styleTag.innerHTML = this.style;
  5083. document.head.append(styleTag);
  5084.  
  5085. this.getSettings();
  5086. this.smallMods();
  5087.  
  5088. mod_menu.addEventListener('click', (event) => {
  5089. if (event.target.closest('.mod_menu_wrapper')) return;
  5090.  
  5091. mod_menu.style.opacity = 0;
  5092. setTimeout(() => {
  5093. mod_menu.style.display = 'none';
  5094. }, 300);
  5095. });
  5096.  
  5097. function openModTab(tabId) {
  5098. const allTabs = document.getElementsByClassName('mod_tab');
  5099. const allTabButtons = document.querySelectorAll('.mod_nav_btn');
  5100.  
  5101. for (const tab of allTabs) {
  5102. tab.style.opacity = 0;
  5103. setTimeout(() => {
  5104. tab.style.display = 'none';
  5105. }, 200);
  5106. }
  5107.  
  5108. allTabButtons.forEach((tabBtn) =>
  5109. tabBtn.classList.remove('mod_selected')
  5110. );
  5111.  
  5112. if (tabId === 'mod_gallery') {
  5113. mods.updateGallery();
  5114. }
  5115.  
  5116. const selectedTab = byId(tabId);
  5117. setTimeout(() => {
  5118. selectedTab.style.display = 'flex';
  5119. setTimeout(() => {
  5120. selectedTab.style.opacity = 1;
  5121. }, 10);
  5122. }, 200);
  5123. this.id && this.classList.add('mod_selected');
  5124. }
  5125.  
  5126. window.openModTab = openModTab;
  5127.  
  5128. document.querySelectorAll('.mod_nav_btn').forEach((tabBtn) => {
  5129. tabBtn.addEventListener('click', function () {
  5130. openModTab.call(
  5131. this,
  5132. this.id.replace('tab_', 'mod_').replace('_btn', '')
  5133. );
  5134. });
  5135. });
  5136.  
  5137. const openMenu = document.querySelectorAll(
  5138. '#clans_and_settings button'
  5139. )[1];
  5140. openMenu.removeAttribute('onclick');
  5141. openMenu.addEventListener('click', () => {
  5142. mod_menu.style.display = 'flex';
  5143. setTimeout(() => {
  5144. mod_menu.style.opacity = 1;
  5145. }, 10);
  5146. });
  5147.  
  5148. const closeModal = byId('closeBtn');
  5149. closeModal.addEventListener('click', () => {
  5150. mod_menu.style.opacity = 0;
  5151. setTimeout(() => {
  5152. mod_menu.style.display = 'none';
  5153. }, 300);
  5154. });
  5155.  
  5156. const fontSelectContainer = document.querySelector(
  5157. '#font-select-container'
  5158. );
  5159.  
  5160. try {
  5161. const fonts = await this.getGoogleFonts();
  5162.  
  5163. const { container: selectElement, selectButton } =
  5164. this.render_sm_select(
  5165. 'Select Font',
  5166. fonts,
  5167. { width: '200px' },
  5168. modSettings.game.font
  5169. );
  5170.  
  5171. const resetFont = document.createElement('button');
  5172. resetFont.classList.add('resetButton');
  5173.  
  5174. resetFont.addEventListener('click', () => {
  5175. if (modSettings.game.font === 'Ubuntu') return;
  5176.  
  5177. modSettings.game.font = 'Ubuntu';
  5178. updateStorage();
  5179. selectElement.value = 'Ubuntu';
  5180.  
  5181. // just change the text of the selectButton
  5182. selectButton.querySelector('span').textContent = 'Ubuntu';
  5183. });
  5184.  
  5185. const container = document.createElement('div');
  5186. container.classList.add('centerXY', 'g-5');
  5187. container.append(resetFont, selectElement);
  5188.  
  5189. selectElement.addEventListener('change', (e) => {
  5190. const font = e.detail;
  5191. const link = document.createElement('link');
  5192. link.href = `https://fonts.googleapis.com/css2?family=${font}&display=swap`;
  5193. link.rel = 'stylesheet';
  5194. document.head.appendChild(link);
  5195.  
  5196. modSettings.game.font = font;
  5197. updateStorage();
  5198. });
  5199.  
  5200. fontSelectContainer.replaceWith(container);
  5201. } catch (e) {
  5202. fontSelectContainer.replaceWith(
  5203. "Couldn't load fonts, try again later."
  5204. );
  5205. console.error(e);
  5206. }
  5207.  
  5208. if (modSettings.game.font !== 'Ubuntu') {
  5209. const link = document.createElement('link');
  5210. link.href = `https://fonts.googleapis.com/css2?family=${modSettings.game.font}&display=swap`;
  5211. link.rel = 'stylesheet';
  5212. document.head.appendChild(link);
  5213. }
  5214.  
  5215. const macroSettings = () => {
  5216. const allSettingNames =
  5217. document.querySelectorAll('.setting-card-name');
  5218.  
  5219. for (const settingName of Object.values(allSettingNames)) {
  5220. settingName.addEventListener('click', (event) => {
  5221. const settingCardWrappers = document.querySelectorAll(
  5222. '.setting-card-wrapper'
  5223. );
  5224. const currentWrapper = Object.values(
  5225. settingCardWrappers
  5226. ).filter(
  5227. (wrapper) =>
  5228. wrapper.querySelector('.setting-card-name')
  5229. .textContent === settingName.textContent
  5230. )[0];
  5231. const settingParameters = currentWrapper.querySelector(
  5232. '.setting-parameters'
  5233. );
  5234.  
  5235. settingParameters.style.display =
  5236. settingParameters.style.display === 'none'
  5237. ? 'block'
  5238. : 'none';
  5239. });
  5240. }
  5241. };
  5242. macroSettings();
  5243.  
  5244. const playTimerToggle = byId('playTimerToggle');
  5245. playTimerToggle.addEventListener('change', () => {
  5246. modSettings.settings.playTimer = playTimerToggle.checked;
  5247. updateStorage();
  5248. });
  5249.  
  5250. const mouseTrackerToggle = byId('mouseTrackerToggle');
  5251. mouseTrackerToggle.addEventListener('change', () => {
  5252. modSettings.settings.mouseTracker = mouseTrackerToggle.checked;
  5253. updateStorage();
  5254. });
  5255.  
  5256. const macroSpeed = byId('macroSpeed');
  5257. const macroSpeedText = byId('macroSpeedText');
  5258.  
  5259. macroSpeed.addEventListener('input', () => {
  5260. modSettings.macros.feedSpeed = macroSpeed.value;
  5261. macroSpeedText.textContent = `${modSettings.macros.feedSpeed.toString()}ms`;
  5262. updateStorage();
  5263. });
  5264.  
  5265. // Reset settings - Mod
  5266. const resetModSettings = byId('resetModSettings');
  5267. resetModSettings.addEventListener('click', () => {
  5268. if (
  5269. confirm(
  5270. 'Are you sure you want to reset the mod settings? A reload is required.'
  5271. )
  5272. ) {
  5273. this.removeStorage(storageName);
  5274. location.reload();
  5275. }
  5276. });
  5277.  
  5278. // Reset settings - Game
  5279. const resetGameSettings = byId('resetGameSettings');
  5280. resetGameSettings.addEventListener('click', () => {
  5281. if (
  5282. confirm(
  5283. 'Are you sure you want to reset the game settings? Your nick and more settings will be lost. A reload is required.'
  5284. )
  5285. ) {
  5286. window.settings.gameSettings = null;
  5287. this.removeStorage('settings');
  5288. location.reload();
  5289. }
  5290. });
  5291.  
  5292. // EventListeners for auth buttons
  5293. const createAccountBtn = byId('createAccount');
  5294. const loginBtn = byId('login');
  5295.  
  5296. createAccountBtn.addEventListener('click', () => {
  5297. this.createSignInWrapper(false);
  5298. });
  5299. loginBtn.addEventListener('click', () => {
  5300. this.createSignInWrapper(true);
  5301. });
  5302. },
  5303.  
  5304. render_sm_select(label, items, style = {}, defaultValue) {
  5305. const createElement = (tag, styles, text = '') => {
  5306. const el = document.createElement(tag);
  5307. el.textContent = text;
  5308. Object.assign(el.style, styles);
  5309. return el;
  5310. };
  5311.  
  5312. const defaultcontainerStyles = {
  5313. position: 'relative',
  5314. display: 'inline-block',
  5315. };
  5316.  
  5317. const container = createElement('div', {
  5318. ...defaultcontainerStyles,
  5319. ...style,
  5320. });
  5321.  
  5322. const selectButton = document.createElement('div');
  5323. selectButton.style.cssText =
  5324. 'background: rgba(0, 0, 0, 0.4); color: #fff; border: 1px solid #A2A2A2; border-radius: 5px; padding: 8px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;';
  5325.  
  5326. selectButton.innerHTML = `
  5327. <span>${label}</span>
  5328. <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="#000000" width="20">
  5329. <path fill="#fafafa" d="M13.098 8H6.902c-.751 0-1.172.754-.708 1.268L9.292 12.7c.36.399 1.055.399 1.416 0l3.098-3.433C14.27 8.754 13.849 8 13.098 8Z"></path>
  5330. </svg>
  5331. `;
  5332.  
  5333. if (defaultValue && items.includes(defaultValue)) {
  5334. selectButton.innerHTML = `<span>${defaultValue}</span> ${
  5335. selectButton.innerHTML.split('</svg>')[1]
  5336. }`;
  5337. }
  5338.  
  5339. container.appendChild(selectButton);
  5340.  
  5341. const dropdown = createElement('div', {
  5342. display: 'none',
  5343. position: 'absolute',
  5344. background: '#111',
  5345. color: '#fafafa',
  5346. borderRadius: '0 0 10px 10px',
  5347. padding: '5px 0',
  5348. zIndex: '999999',
  5349. maxHeight: '200px',
  5350. overflowY: 'auto',
  5351. });
  5352.  
  5353. const searchBox = createElement('input', {
  5354. width: '100%',
  5355. padding: '5px',
  5356. marginBottom: '5px',
  5357. background: '#222',
  5358. border: 'none',
  5359. color: '#fff',
  5360. });
  5361. searchBox.placeholder = 'Search...';
  5362.  
  5363. dropdown.append(searchBox);
  5364.  
  5365. items.forEach((item) => {
  5366. const option = createElement(
  5367. 'div',
  5368. { padding: '5px', cursor: 'pointer' },
  5369. item
  5370. );
  5371. option.onclick = () => {
  5372. selectButton.innerHTML = `
  5373. <span>${item}</span>
  5374. <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="#000000" width="20">
  5375. <path fill="#fafafa" d="M13.098 8H6.902c-.751 0-1.172.754-.708 1.268L9.292 12.7c.36.399 1.055.399 1.416 0l3.098-3.433C14.27 8.754 13.849 8 13.098 8Z"></path>
  5376. </svg>`;
  5377. dropdown.style.display = 'none';
  5378. container.dispatchEvent(
  5379. new CustomEvent('change', { detail: item })
  5380. );
  5381. };
  5382. option.onmouseover = (e) =>
  5383. (e.target.style.background = '#555');
  5384. option.onmouseout = (e) =>
  5385. (e.target.style.background = 'transparent');
  5386. dropdown.append(option);
  5387. });
  5388.  
  5389. container.append(dropdown);
  5390.  
  5391. selectButton.onclick = () => {
  5392. dropdown.style.display =
  5393. dropdown.style.display === 'none' ? 'block' : 'none';
  5394. };
  5395.  
  5396. document.onclick = (e) => {
  5397. if (!container.contains(e.target))
  5398. dropdown.style.display = 'none';
  5399. };
  5400.  
  5401. searchBox.addEventListener('input', () => {
  5402. const filter = searchBox.value.toLowerCase();
  5403. Array.from(dropdown.children)
  5404. .slice(1)
  5405. .forEach((item) => {
  5406. item.style.display = item.textContent
  5407. .toLowerCase()
  5408. .includes(filter)
  5409. ? 'block'
  5410. : 'none';
  5411. });
  5412. });
  5413.  
  5414. return { container, selectButton };
  5415. },
  5416.  
  5417. setProfile(user) {
  5418. const img = byId('my-profile-img');
  5419. const name = byId('my-profile-name');
  5420. const role = byId('my-profile-role');
  5421. const bioText = byId('my-profile-bio');
  5422. const badges = byId('my-profile-badges');
  5423.  
  5424. const bio = user.bio ? user.bio : 'No bio.';
  5425.  
  5426. img.src = user.imageURL;
  5427. name.innerText = user.username;
  5428. role.innerText = user.role;
  5429. bioText.innerHTML = bio;
  5430. badges.innerHTML =
  5431. user.badges && user.badges.length > 0
  5432. ? user.badges
  5433. .map(
  5434. (badge) =>
  5435. `<span class="mod_badge">${badge}</span>`
  5436. )
  5437. .join('')
  5438. : '<span>User has no badges.</span>';
  5439.  
  5440. role.classList.add(`${user.role}_role`);
  5441. },
  5442.  
  5443. getSettings() {
  5444. const mod_qaccess = document.querySelector('#mod_qaccess');
  5445. const settingsGrid = document.querySelector(
  5446. '#settings > .checkbox-grid'
  5447. );
  5448. const settingsNames =
  5449. settingsGrid.querySelectorAll('label:not([class])');
  5450. const inputs = settingsGrid.querySelectorAll('input');
  5451.  
  5452. inputs.forEach((checkbox, index) => {
  5453. if (
  5454. checkbox.id === 'showMinimap' ||
  5455. checkbox.id === 'darkTheme'
  5456. )
  5457. return;
  5458. const modrow = document.createElement('div');
  5459. modrow.classList.add('justify-sb', 'p-2');
  5460.  
  5461. if (
  5462. checkbox.id === 'showChat' ||
  5463. checkbox.id === 'showPosition' ||
  5464. checkbox.id === 'showNames' ||
  5465. checkbox.id === 'showSkins'
  5466. ) {
  5467. modrow.style.display = 'none';
  5468. }
  5469.  
  5470. modrow.innerHTML = `
  5471. <span>${settingsNames[index].textContent}</span>
  5472. <div class="modCheckbox" id="${checkbox.id}_wrapper"></div>
  5473. `;
  5474. mod_qaccess.append(modrow);
  5475.  
  5476. const cbWrapper = byId(`${checkbox.id}_wrapper`);
  5477. cbWrapper.appendChild(checkbox);
  5478.  
  5479. cbWrapper.appendChild(
  5480. Object.assign(document.createElement('label'), {
  5481. classList: ['cbx'],
  5482. htmlFor: checkbox.id,
  5483. })
  5484. );
  5485. });
  5486. },
  5487.  
  5488. themes: function () {
  5489. const elements = [
  5490. '#menu',
  5491. '#title',
  5492. '.top-users',
  5493. '#left-menu',
  5494. '.menu-links',
  5495. '.menu--stats-mode',
  5496. '#left_ad_block',
  5497. '#ad_bottom',
  5498. '.ad-block',
  5499. '#left_ad_block > .right-menu',
  5500. '#text-block > .right-menu',
  5501. '#sigma-pass .connecting__content',
  5502. '#sigma-status',
  5503. '.alert',
  5504. ];
  5505.  
  5506. const customTextElements = [
  5507. '#challenge-coins .alert-heading',
  5508. '#sigma-pass h3',
  5509. '.alert *',
  5510. ];
  5511.  
  5512. let checkInterval;
  5513. const appliedElements = new Set();
  5514.  
  5515. window.themeElements = elements;
  5516.  
  5517. const themeEditor = document.createElement('div');
  5518. themeEditor.classList.add('themeEditor');
  5519. themeEditor.style.display = 'none';
  5520.  
  5521. themeEditor.innerHTML = `
  5522. <div class="theme_editor_header">
  5523. <h3>Theme Editor</h3>
  5524. <button class="btn closeBtn" id="closeThemeEditor">
  5525. <svg width="22" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  5526. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  5527. </svg>
  5528. </button>
  5529. </div>
  5530. <hr />
  5531. <main class="theme_editor_content">
  5532. <div class="centerXY" style="justify-content: flex-end;gap: 10px">
  5533. <span class="text">Select Theme Type: </span>
  5534. <select class="form-control" style="background: #222; color: #fff; width: 150px" id="theme-type-select">
  5535. <option>Static Color</option>
  5536. <option>Gradient</option>
  5537. <option>Image / Gif</option>
  5538. </select>
  5539. </div>
  5540.  
  5541. <div id="theme_editor_color" class="theme-editor-tab">
  5542. <div class="centerXY">
  5543. <label for="theme-editor-bgcolorinput" class="text">Background color:</label>
  5544. <input type="color" value="#000000" class="colorInput whiteBorder_colorInput" id="theme-editor-bgcolorinput"/>
  5545. </div>
  5546. <div class="centerXY">
  5547. <label for="theme-editor-colorinput" class="text">Text color:</label>
  5548. <input type="color" value="#000000" class="colorInput whiteBorder_colorInput" id="theme-editor-colorinput"/>
  5549. </div>
  5550. <div style="background-color: #000000" class="themes_preview" id="color_preview">
  5551. <span class="text" style="color: #fff; font-size: 9px;">preview</span>
  5552. </div>
  5553. <div class="centerY" style="gap: 10px; margin-top: 10px;">
  5554. <input type="text" class="form-control" style="background: #222; color: #fff;" maxlength="15" placeholder="Theme name..." id="colorThemeName"/>
  5555. <button class="btn btn-success" id="saveColorTheme">Save</button>
  5556. </div>
  5557. </div>
  5558.  
  5559.  
  5560. <div id="theme_editor_gradient" class="theme-editor-tab" style="display: none;">
  5561. <div class="centerXY">
  5562. <label for="theme-editor-gcolor1" class="text">Color 1:</label>
  5563. <input type="color" value="#000000" class="colorInput whiteBorder_colorInput" id="theme-editor-gcolor1"/>
  5564. </div>
  5565. <div class="centerXY">
  5566. <label for="theme-editor-g_color" class="text">Color 2:</label>
  5567. <input type="color" value="#ffffff" class="colorInput whiteBorder_colorInput" id="theme-editor-g_color"/>
  5568. </div>
  5569. <div class="centerXY">
  5570. <label for="theme-editor-gcolor2" class="text">Text Color:</label>
  5571. <input type="color" value="#ffffff" class="colorInput whiteBorder_colorInput" id="theme-editor-gcolor2"/>
  5572. </div>
  5573.  
  5574. <div class="centerXY" style="gap: 10px">
  5575. <label for="gradient-type" class="text">Gradient Type:</label>
  5576. <select id="gradient-type" class="form-control" style="background: #222; color: #fff; width: 120px;">
  5577. <option value="linear">Linear</option>
  5578. <option value="radial">Radial</option>
  5579. </select>
  5580. </div>
  5581.  
  5582. <div id="theme-editor-gradient_angle" class="centerY" style="gap: 10px; width: 100%">
  5583. <label for="g_angle" class="text" id="gradient_angle_text" style="width: 115px;">Angle (0deg):</label>
  5584. <input type="range" id="g_angle" value="0" min="0" max="360">
  5585. </div>
  5586.  
  5587. <div style="background: linear-gradient(0deg, #000, #fff)" class="themes_preview" id="gradient_preview">
  5588. <span class="text" style="color: #fff; font-size: 9px;">preview</span>
  5589. </div>
  5590. <div class="centerY" style="gap: 10px; margin-top: 10px;">
  5591. <input type="text" class="form-control" style="background: #222; color: #fff;" placeholder="Theme name..." id="gradientThemeName"/>
  5592. <button class="btn btn-success" id="saveGradientTheme">Save</button>
  5593. </div>
  5594. </div>
  5595.  
  5596. <div id="theme_editor_image" class="theme-editor-tab" style="display: none">
  5597. <div class="centerXY">
  5598. <input type="text" id="theme-editor-imagelink" placeholder="Image / GIF URL (https://i.ibb.co/k6hn4v0/Galaxy-Example.png)" class="form-control" style="background: #222; color: #fff"/>
  5599. </div>
  5600. <div class="centerXY" style="margin: 5px; gap: 5px;">
  5601. <label for="theme-editor-textcolorImage" class="text">Text Color: </label>
  5602. <input type="color" class="colorInput whiteBorder_colorInput" value="#ffffff" id="theme-editor-textcolorImage"/>
  5603. </div>
  5604.  
  5605. <div style="background: url('https://i.ibb.co/k6hn4v0/Galaxy-Example.png'); background-position: center; background-size: cover;" class="themes_preview" id="image_preview">
  5606. <span class="text" style="color: #fff; font-size: 9px;">preview</span>
  5607. </div>
  5608. <div class="centerY" style="gap: 10px; margin-top: 10px;">
  5609. <input type="text" class="form-control" style="background: #222; color: #fff;" placeholder="Theme name..." id="imageThemeName"/>
  5610. <button class="btn btn-success" id="saveImageTheme">Save</button>
  5611. </div>
  5612. </div>
  5613. </main>
  5614. `;
  5615.  
  5616. document.body.append(themeEditor);
  5617.  
  5618. setTimeout(() => {
  5619. document
  5620. .querySelectorAll('.stats-btn__share-btn')[1]
  5621. .querySelector('rect')
  5622. .remove();
  5623.  
  5624. const themeTypeSelect = byId('theme-type-select');
  5625. const colorTab = byId('theme_editor_color');
  5626. const gradientTab = byId('theme_editor_gradient');
  5627. const imageTab = byId('theme_editor_image');
  5628. const gradientAngleDiv = byId('theme-editor-gradient_angle');
  5629.  
  5630. themeTypeSelect.addEventListener('change', function () {
  5631. const selectedOption = themeTypeSelect.value;
  5632. switch (selectedOption) {
  5633. case 'Static Color':
  5634. colorTab.style.display = 'flex';
  5635. gradientTab.style.display = 'none';
  5636. imageTab.style.display = 'none';
  5637. break;
  5638. case 'Gradient':
  5639. colorTab.style.display = 'none';
  5640. gradientTab.style.display = 'flex';
  5641. imageTab.style.display = 'none';
  5642. break;
  5643. case 'Image / Gif':
  5644. colorTab.style.display = 'none';
  5645. gradientTab.style.display = 'none';
  5646. imageTab.style.display = 'flex';
  5647. break;
  5648. default:
  5649. colorTab.style.display = 'flex';
  5650. gradientTab.style.display = 'none';
  5651. imageTab.style.display = 'none';
  5652. }
  5653. });
  5654.  
  5655. const colorInputs = document.querySelectorAll(
  5656. '#theme_editor_color .colorInput'
  5657. );
  5658. colorInputs.forEach((input) => {
  5659. input.addEventListener('input', function () {
  5660. const bgColorInput = byId(
  5661. 'theme-editor-bgcolorinput'
  5662. ).value;
  5663. const textColorInput = byId(
  5664. 'theme-editor-colorinput'
  5665. ).value;
  5666.  
  5667. applyColorTheme(bgColorInput, textColorInput);
  5668. });
  5669. });
  5670.  
  5671. const gradientInputs = document.querySelectorAll(
  5672. '#theme_editor_gradient .colorInput'
  5673. );
  5674. gradientInputs.forEach((input) => {
  5675. input.addEventListener('input', function () {
  5676. const gColor1 = byId('theme-editor-gcolor1').value;
  5677. const gColor2 = byId('theme-editor-g_color').value;
  5678. const gTextColor = byId('theme-editor-gcolor2').value;
  5679. const gAngle = byId('g_angle').value;
  5680. const gradientType = byId('gradient-type').value;
  5681.  
  5682. applyGradientTheme(
  5683. gColor1,
  5684. gColor2,
  5685. gTextColor,
  5686. gAngle,
  5687. gradientType
  5688. );
  5689. });
  5690. });
  5691.  
  5692. const imageInputs = document.querySelectorAll(
  5693. '#theme_editor_image .colorInput'
  5694. );
  5695. imageInputs.forEach((input) => {
  5696. input.addEventListener('input', function () {
  5697. const imageLinkInput = byId(
  5698. 'theme-editor-imagelink'
  5699. ).value;
  5700. const textColorImageInput = byId(
  5701. 'theme-editor-textcolorImage'
  5702. ).value;
  5703.  
  5704. let img;
  5705. if (imageLinkInput === '') {
  5706. img = 'https://i.ibb.co/k6hn4v0/Galaxy-Example.png';
  5707. } else {
  5708. img = imageLinkInput;
  5709. }
  5710. applyImageTheme(img, textColorImageInput);
  5711. });
  5712. });
  5713. const image_preview = byId('image_preview');
  5714. const image_link = byId('theme-editor-imagelink');
  5715.  
  5716. let isWriting = false;
  5717. let timeoutId;
  5718.  
  5719. image_link.addEventListener('input', () => {
  5720. if (!isWriting) {
  5721. isWriting = true;
  5722. } else {
  5723. clearTimeout(timeoutId);
  5724. }
  5725.  
  5726. timeoutId = setTimeout(() => {
  5727. const imageLinkInput = image_link.value;
  5728. const textColorImageInput = byId(
  5729. 'theme-editor-textcolorImage'
  5730. ).value;
  5731.  
  5732. let img;
  5733. if (imageLinkInput === '') {
  5734. img = 'https://i.ibb.co/k6hn4v0/Galaxy-Example.png';
  5735. } else {
  5736. img = imageLinkInput;
  5737. }
  5738.  
  5739. applyImageTheme(img, textColorImageInput);
  5740. isWriting = false;
  5741. }, 1000);
  5742. });
  5743.  
  5744. const gradientTypeSelect = byId('gradient-type');
  5745. const angleInput = byId('g_angle');
  5746.  
  5747. gradientTypeSelect.addEventListener('change', function () {
  5748. const selectedType = gradientTypeSelect.value;
  5749. gradientAngleDiv.style.display =
  5750. selectedType === 'linear' ? 'flex' : 'none';
  5751.  
  5752. const gColor1 = byId('theme-editor-gcolor1').value;
  5753. const gColor2 = byId('theme-editor-g_color').value;
  5754. const gTextColor = byId('theme-editor-gcolor2').value;
  5755. const gAngle = byId('g_angle').value;
  5756.  
  5757. applyGradientTheme(
  5758. gColor1,
  5759. gColor2,
  5760. gTextColor,
  5761. gAngle,
  5762. selectedType
  5763. );
  5764. });
  5765.  
  5766. angleInput.addEventListener('input', function () {
  5767. const gradient_angle_text = byId('gradient_angle_text');
  5768. gradient_angle_text.innerText = `Angle (${angleInput.value}deg): `;
  5769. const gColor1 = byId('theme-editor-gcolor1').value;
  5770. const gColor2 = byId('theme-editor-g_color').value;
  5771. const gTextColor = byId('theme-editor-gcolor2').value;
  5772. const gAngle = byId('g_angle').value;
  5773. const gradientType = byId('gradient-type').value;
  5774.  
  5775. applyGradientTheme(
  5776. gColor1,
  5777. gColor2,
  5778. gTextColor,
  5779. gAngle,
  5780. gradientType
  5781. );
  5782. });
  5783.  
  5784. function applyColorTheme(bgColor, textColor) {
  5785. const previewDivs = document.querySelectorAll(
  5786. '#theme_editor_color .themes_preview'
  5787. );
  5788. previewDivs.forEach((previewDiv) => {
  5789. previewDiv.style.backgroundColor = bgColor;
  5790. const textSpan = previewDiv.querySelector('span.text');
  5791. textSpan.style.color = textColor;
  5792. });
  5793. }
  5794.  
  5795. function applyGradientTheme(
  5796. gColor1,
  5797. gColor2,
  5798. gTextColor,
  5799. gAngle,
  5800. gradientType
  5801. ) {
  5802. const previewDivs = document.querySelectorAll(
  5803. '#theme_editor_gradient .themes_preview'
  5804. );
  5805. previewDivs.forEach((previewDiv) => {
  5806. const gradient =
  5807. gradientType === 'linear'
  5808. ? `linear-gradient(${gAngle}deg, ${gColor1}, ${gColor2})`
  5809. : `radial-gradient(circle, ${gColor1}, ${gColor2})`;
  5810. previewDiv.style.background = gradient;
  5811. const textSpan = previewDiv.querySelector('span.text');
  5812. textSpan.style.color = gTextColor;
  5813. });
  5814. }
  5815.  
  5816. function applyImageTheme(imageLink, textColor) {
  5817. const previewDivs = document.querySelectorAll(
  5818. '#theme_editor_image .themes_preview'
  5819. );
  5820. previewDivs.forEach((previewDiv) => {
  5821. previewDiv.style.backgroundImage = `url('${imageLink}')`;
  5822. const textSpan = previewDiv.querySelector('span.text');
  5823. textSpan.style.color = textColor;
  5824. });
  5825. }
  5826.  
  5827. const createTheme = byId('createTheme');
  5828. createTheme.addEventListener('click', () => {
  5829. themeEditor.style.display = 'block';
  5830. });
  5831.  
  5832. const closeThemeEditor = byId('closeThemeEditor');
  5833. closeThemeEditor.addEventListener('click', () => {
  5834. themeEditor.style.display = 'none';
  5835. });
  5836.  
  5837. let themesDiv = byId('themes');
  5838.  
  5839. const saveTheme = (type) => {
  5840. const name = byId(`${type}ThemeName`).value;
  5841. if (!name) return;
  5842.  
  5843. let background, text;
  5844. if (type === 'color') {
  5845. background = byId('theme-editor-bgcolorinput').value;
  5846. text = byId('theme-editor-colorinput').value;
  5847. } else if (type === 'gradient') {
  5848. const gColor1 = byId('theme-editor-gcolor1').value;
  5849. const gColor2 = byId('theme-editor-g_color').value;
  5850. text = byId('theme-editor-gcolor2').value;
  5851. const gAngle = byId('g_angle').value;
  5852. const gradientType = byId('gradient-type').value;
  5853. background =
  5854. gradientType === 'linear'
  5855. ? `linear-gradient(${gAngle}deg, ${gColor1}, ${gColor2})`
  5856. : `radial-gradient(circle, ${gColor1}, ${gColor2})`;
  5857. } else if (type === 'image') {
  5858. background = byId('theme-editor-imagelink').value;
  5859. text = byId('theme-editor-textcolorImage').value;
  5860. if (!background) return;
  5861. }
  5862.  
  5863. const theme = { name, background, text };
  5864. const themeCard = document.createElement('div');
  5865. themeCard.classList.add('theme');
  5866. themeCard.innerHTML = `
  5867. <div class="themeContent" style="background: ${
  5868. background.includes('http')
  5869. ? `url(${theme.preview || background})`
  5870. : background
  5871. }; background-size: cover; background-position: center"></div>
  5872. <div class="themeName text" style="color: #fff">${name}</div>
  5873. `;
  5874.  
  5875. themeCard.addEventListener('click', () =>
  5876. toggleTheme(theme)
  5877. );
  5878. themeCard.addEventListener('contextmenu', (ev) => {
  5879. ev.preventDefault();
  5880. if (confirm('Do you want to delete this Theme?')) {
  5881. themeCard.remove();
  5882. const index = modSettings.themes.custom.findIndex(
  5883. (t) => t.name === name
  5884. );
  5885. if (index !== -1) {
  5886. modSettings.themes.custom.splice(index, 1);
  5887. updateStorage();
  5888. }
  5889. }
  5890. });
  5891.  
  5892. themesDiv.appendChild(themeCard);
  5893. modSettings.themes.custom.push(theme);
  5894. updateStorage();
  5895. themeEditor.style.display = 'none';
  5896. themesDiv.scrollTop = themesDiv.scrollHeight;
  5897. };
  5898.  
  5899. byId('saveColorTheme').addEventListener('click', () =>
  5900. saveTheme('color')
  5901. );
  5902. byId('saveGradientTheme').addEventListener('click', () =>
  5903. saveTheme('gradient')
  5904. );
  5905. byId('saveImageTheme').addEventListener('click', () =>
  5906. saveTheme('image')
  5907. );
  5908. });
  5909.  
  5910. const b_inner = document.querySelector('.body__inner');
  5911. let bodyColorElements = b_inner.querySelectorAll(
  5912. '.body__inner > :not(.body__inner), #s-skin-select-icon-text'
  5913. );
  5914.  
  5915. const toggleColor = (element, background, text) => {
  5916. let image = `url("${background}")`;
  5917. if (background.includes('http')) {
  5918. element.style.background = image;
  5919. element.style.backgroundPosition = 'center';
  5920. element.style.backgroundSize = 'cover';
  5921. element.style.backgroundRepeat = 'no-repeat';
  5922. } else {
  5923. element.style.background = background;
  5924. element.style.backgroundRepeat = 'no-repeat';
  5925. }
  5926. element.style.color = text;
  5927. };
  5928.  
  5929. const openSVG = document.querySelector(
  5930. '#clans_and_settings > Button:nth-of-type(2) > svg'
  5931. );
  5932. const openSVGPath = openSVG.querySelector('path');
  5933. openSVG.setAttribute('width', '36');
  5934. openSVG.setAttribute('height', '36');
  5935.  
  5936. const applyThemeToElement = (el, theme) => {
  5937. if (el.matches('#title')) {
  5938. el.style.color = theme.text;
  5939. } else {
  5940. toggleColor(el, theme.background, theme.text);
  5941. }
  5942. appliedElements.add(el);
  5943. };
  5944.  
  5945. const toggleTheme = (theme) => {
  5946. try {
  5947. if (checkInterval) clearInterval(checkInterval);
  5948. appliedElements.clear();
  5949.  
  5950. const applyTheme = () => {
  5951. let allApplied = true;
  5952.  
  5953. elements.forEach((selector) => {
  5954. const elements =
  5955. document.querySelectorAll(selector);
  5956. elements.forEach((el) => {
  5957. if (el && !appliedElements.has(el)) {
  5958. applyThemeToElement(el, theme);
  5959. allApplied = false;
  5960. }
  5961. });
  5962. });
  5963.  
  5964. customTextElements.forEach((qs) => {
  5965. document.querySelectorAll(qs).forEach((el) => {
  5966. if (!appliedElements.has(el)) {
  5967. el.style.setProperty(
  5968. 'color',
  5969. theme.text,
  5970. 'important'
  5971. );
  5972. appliedElements.add(el);
  5973. allApplied = false;
  5974. }
  5975. });
  5976. });
  5977.  
  5978. bodyColorElements.forEach((el) => {
  5979. if (!appliedElements.has(el)) {
  5980. el.style.color = theme.text;
  5981. appliedElements.add(el);
  5982. allApplied = false;
  5983. }
  5984. });
  5985.  
  5986. if (allApplied) {
  5987. clearInterval(checkInterval);
  5988. return;
  5989. }
  5990.  
  5991. const isBright = (color) => {
  5992. if (!color.startsWith('#') || color.length !== 7)
  5993. return false;
  5994. const r = parseInt(color.slice(1, 3), 16);
  5995. const g = parseInt(color.slice(3, 5), 16);
  5996. const b = parseInt(color.slice(5, 7), 16);
  5997. return r * 0.299 + g * 0.587 + b * 0.114 > 186;
  5998. };
  5999.  
  6000. openSVGPath.setAttribute(
  6001. 'fill',
  6002. isBright(theme.text) ? theme.text : '#222'
  6003. );
  6004.  
  6005. modSettings.themes.current = theme.name;
  6006. updateStorage();
  6007. };
  6008.  
  6009. checkInterval = setInterval(applyTheme, 100);
  6010. } catch (e) {
  6011. console.error(e);
  6012. }
  6013. };
  6014.  
  6015. const themes = (window.themes = {
  6016. defaults: [
  6017. {
  6018. name: 'Dark',
  6019. background: '#151515',
  6020. text: '#FFFFFF',
  6021. },
  6022. {
  6023. name: 'White',
  6024. background: '#ffffff',
  6025. text: '#000000',
  6026. },
  6027. {
  6028. name: 'Transparent',
  6029. background: 'rgba(0, 0, 0,0)',
  6030. text: '#FFFFFF',
  6031. },
  6032. ],
  6033. orderly: [
  6034. {
  6035. name: 'THC',
  6036. background: 'linear-gradient(160deg, #9BEC7A, #117500)',
  6037. text: '#000000',
  6038. },
  6039. {
  6040. name: '4 AM',
  6041. background: 'linear-gradient(160deg, #8B0AE1, #111)',
  6042. text: '#FFFFFF',
  6043. },
  6044. {
  6045. name: 'OTO',
  6046. background: 'linear-gradient(160deg, #A20000, #050505)',
  6047. text: '#FFFFFF',
  6048. },
  6049. {
  6050. name: 'Gaming',
  6051. background:
  6052. 'https://i.ibb.co/DwKkQfh/BG-1-lower-quality.jpg',
  6053. text: '#FFFFFF',
  6054. },
  6055. {
  6056. name: 'Shapes',
  6057. background: 'https://i.ibb.co/h8TmVyM/BG-2.png',
  6058. preview:
  6059. 'https://czrsd.com/static/sigmod/themes/BG-2.jpg',
  6060. text: '#FFFFFF',
  6061. },
  6062. {
  6063. name: 'Blue',
  6064. background: 'https://i.ibb.co/9yQBfWj/BG-3.png',
  6065. preview:
  6066. 'https://czrsd.com/static/sigmod/themes/BG-3.jpg',
  6067. text: '#FFFFFF',
  6068. },
  6069. {
  6070. name: 'Blue - 2',
  6071. background: 'https://i.ibb.co/7RJvNCX/BG-4.png',
  6072. preview:
  6073. 'https://czrsd.com/static/sigmod/themes/BG-4.jpg',
  6074. text: '#FFFFFF',
  6075. },
  6076. {
  6077. name: 'Purple',
  6078. background: 'https://i.ibb.co/vxY15Tv/BG-5.png',
  6079. preview:
  6080. 'https://czrsd.com/static/sigmod/themes/BG-5.jpg',
  6081. text: '#FFFFFF',
  6082. },
  6083. {
  6084. name: 'Orange Blue',
  6085. background: 'https://i.ibb.co/99nfFBN/BG-6.png',
  6086. preview:
  6087. 'https://czrsd.com/static/sigmod/themes/BG-6.jpg',
  6088. text: '#FFFFFF',
  6089. },
  6090. {
  6091. name: 'Gradient',
  6092. background: 'https://i.ibb.co/hWMLwLS/BG-7.png',
  6093. preview:
  6094. 'https://czrsd.com/static/sigmod/themes/BG-7.jpg',
  6095. text: '#FFFFFF',
  6096. },
  6097. {
  6098. name: 'Sky',
  6099. background: 'https://i.ibb.co/P4XqDFw/BG-9.png',
  6100. preview:
  6101. 'https://czrsd.com/static/sigmod/themes/BG-9.jpg',
  6102. text: '#000000',
  6103. },
  6104. {
  6105. name: 'Sunset',
  6106. background: 'https://i.ibb.co/0BVbYHC/BG-10.png',
  6107. preview:
  6108. 'https://czrsd.com/static/sigmod/themes/BG-10.jpg',
  6109. text: '#FFFFFF',
  6110. },
  6111. {
  6112. name: 'Galaxy',
  6113. background: 'https://i.ibb.co/MsssDKP/Galaxy.png',
  6114. preview:
  6115. 'https://czrsd.com/static/sigmod/themes/Galaxy.jpg',
  6116. text: '#FFFFFF',
  6117. },
  6118. {
  6119. name: 'Planet',
  6120. background: 'https://i.ibb.co/KLqWM32/Planet.png',
  6121. preview:
  6122. 'https://czrsd.com/static/sigmod/themes/Planet.jpg',
  6123. text: '#FFFFFF',
  6124. },
  6125. {
  6126. name: 'colorful',
  6127. background: 'https://i.ibb.co/VqtB3TX/colorful.png',
  6128. preview:
  6129. 'https://czrsd.com/static/sigmod/themes/colorful.jpg',
  6130. text: '#FFFFFF',
  6131. },
  6132. {
  6133. name: 'Sunset - 2',
  6134. background: 'https://i.ibb.co/TLp2nvv/Sunset.png',
  6135. preview:
  6136. 'https://czrsd.com/static/sigmod/themes/Sunset.jpg',
  6137. text: '#FFFFFF',
  6138. },
  6139. {
  6140. name: 'Epic',
  6141. background: 'https://i.ibb.co/kcv4tvn/Epic.png',
  6142. preview:
  6143. 'https://czrsd.com/static/sigmod/themes/Epic.jpg',
  6144. text: '#FFFFFF',
  6145. },
  6146. {
  6147. name: 'Galaxy - 2',
  6148. background: 'https://i.ibb.co/smRs6V0/galaxy.png',
  6149. preview:
  6150. 'https://czrsd.com/static/sigmod/themes/galaxy2.jpg',
  6151. text: '#FFFFFF',
  6152. },
  6153. {
  6154. name: 'Cloudy',
  6155. background: 'https://i.ibb.co/MCW7Bcd/cloudy.png',
  6156. preview:
  6157. 'https://czrsd.com/static/sigmod/themes/cloudy.jpg',
  6158. text: '#000000',
  6159. },
  6160. ],
  6161. });
  6162.  
  6163. function createThemeCard(theme) {
  6164. const themeCard = document.createElement('div');
  6165. themeCard.classList.add('theme');
  6166. let themeBG;
  6167. if (theme.background.includes('http')) {
  6168. themeBG = `background: url(${
  6169. theme.preview || theme.background
  6170. })`;
  6171. } else {
  6172. themeBG = `background: ${theme.background}`;
  6173. }
  6174. themeCard.innerHTML = `
  6175. <div class="themeContent" style="${themeBG}; background-size: cover; background-position: center"></div>
  6176. <div class="themeName text" style="color: #fff">${theme.name}</div>
  6177. `;
  6178.  
  6179. themeCard.addEventListener('click', () => {
  6180. toggleTheme(theme);
  6181. });
  6182.  
  6183. if (modSettings.themes.custom.includes(theme)) {
  6184. themeCard.addEventListener(
  6185. 'contextmenu',
  6186. (ev) => {
  6187. ev.preventDefault();
  6188. if (confirm('Do you want to delete this Theme?')) {
  6189. themeCard.remove();
  6190. const themeIndex =
  6191. modSettings.themes.custom.findIndex(
  6192. (addedTheme) =>
  6193. addedTheme.name === theme.name
  6194. );
  6195. if (themeIndex !== -1) {
  6196. modSettings.themes.custom.splice(
  6197. themeIndex,
  6198. 1
  6199. );
  6200. updateStorage();
  6201. }
  6202. }
  6203. },
  6204. false
  6205. );
  6206. }
  6207.  
  6208. return themeCard;
  6209. }
  6210.  
  6211. const themesContainer = byId('themes');
  6212.  
  6213. themes.defaults.forEach((theme) => {
  6214. const themeCard = createThemeCard(theme);
  6215. themesContainer.append(themeCard);
  6216. });
  6217.  
  6218. const orderlyThemes = [
  6219. ...themes.orderly,
  6220. ...modSettings.themes.custom,
  6221. ];
  6222. orderlyThemes.sort((a, b) => a.name.localeCompare(b.name));
  6223. orderlyThemes.forEach((theme) => {
  6224. const themeCard = createThemeCard(theme);
  6225. themesContainer.appendChild(themeCard);
  6226. });
  6227.  
  6228. const savedTheme = modSettings.themes.current;
  6229. if (savedTheme) {
  6230. let selectedTheme;
  6231. selectedTheme = themes.defaults.find(
  6232. (theme) => theme.name === savedTheme
  6233. );
  6234. if (!selectedTheme) {
  6235. selectedTheme =
  6236. themes.orderly.find(
  6237. (theme) => theme.name === savedTheme
  6238. ) ||
  6239. modSettings.themes.custom.find(
  6240. (theme) => theme.name === savedTheme
  6241. );
  6242. }
  6243.  
  6244. if (selectedTheme) {
  6245. toggleTheme(selectedTheme);
  6246. }
  6247. }
  6248.  
  6249. const inputBorderRadius = byId('theme-inputBorderRadius');
  6250. const menuBorderRadius = byId('theme-menuBorderRadius');
  6251. const inputBorder = byId('theme-inputBorder');
  6252.  
  6253. function setCSS(key, value, targets, property) {
  6254. modSettings.themes[key] = value;
  6255. targets.forEach((target) => {
  6256. document
  6257. .querySelectorAll(target)
  6258. .forEach((el) => (el.style[property] = value));
  6259. });
  6260. updateStorage();
  6261. }
  6262.  
  6263. inputBorderRadius.value = modSettings.themes.inputBorderRadius
  6264. ? modSettings.themes.inputBorderRadius.replace('px', '')
  6265. : '4';
  6266. menuBorderRadius.value = modSettings.themes.menuBorderRadius
  6267. ? modSettings.themes.menuBorderRadius.replace('px', '')
  6268. : '15';
  6269. inputBorder.checked = modSettings.themes.inputBorder
  6270. ? modSettings.themes.inputBorder === '1px'
  6271. : '1px';
  6272.  
  6273. setCSS(
  6274. 'inputBorderRadius',
  6275. `${inputBorderRadius.value}px`,
  6276. ['.form-control'],
  6277. 'borderRadius'
  6278. );
  6279. setCSS(
  6280. 'menuBorderRadius',
  6281. `${menuBorderRadius.value}px`,
  6282. [...elements, '.text-block'],
  6283. 'borderRadius'
  6284. );
  6285. setCSS(
  6286. 'inputBorder',
  6287. inputBorder.checked ? '1px' : '0px',
  6288. ['.form-control'],
  6289. 'borderWidth'
  6290. );
  6291.  
  6292. inputBorderRadius.addEventListener('input', () =>
  6293. setCSS(
  6294. 'inputBorderRadius',
  6295. `${inputBorderRadius.value}px`,
  6296. ['.form-control'],
  6297. 'borderRadius'
  6298. )
  6299. );
  6300. menuBorderRadius.addEventListener('input', () =>
  6301. setCSS(
  6302. 'menuBorderRadius',
  6303. `${menuBorderRadius.value}px`,
  6304. [...elements, '.text-block'],
  6305. 'borderRadius'
  6306. )
  6307. );
  6308. inputBorder.addEventListener('input', () =>
  6309. setCSS(
  6310. 'inputBorder',
  6311. inputBorder.checked ? '1px' : '0px',
  6312. ['.form-control'],
  6313. 'borderWidth'
  6314. )
  6315. );
  6316.  
  6317. const reset_input_radius =
  6318. document.getElementById('reset_input_radius');
  6319. const reset_menu_radius =
  6320. document.getElementById('reset_menu_radius');
  6321.  
  6322. reset_input_radius.addEventListener('click', () => {
  6323. const defaultBorderRadius = 4;
  6324. inputBorderRadius.value = defaultBorderRadius;
  6325. setCSS(
  6326. 'inputBorderRadius',
  6327. `${defaultBorderRadius}px`,
  6328. ['.form-control'],
  6329. 'borderRadius'
  6330. );
  6331. });
  6332.  
  6333. reset_menu_radius.addEventListener('click', () => {
  6334. const defaultBorderRadius = 15;
  6335. menuBorderRadius.value = defaultBorderRadius;
  6336. setCSS(
  6337. 'menuBorderRadius',
  6338. `${defaultBorderRadius}px`,
  6339. [...elements, '.text-block'],
  6340. 'borderRadius'
  6341. );
  6342. });
  6343.  
  6344. const hideDiscordBtns = document.getElementById('hideDiscordBtns');
  6345. const dclinkdiv = document.getElementById('dclinkdiv');
  6346.  
  6347. hideDiscordBtns.addEventListener('change', () => {
  6348. if (hideDiscordBtns.checked) {
  6349. dclinkdiv.classList.add('hidden_full');
  6350. modSettings.themes.hideDiscordBtns = true;
  6351. } else {
  6352. dclinkdiv.classList.remove('hidden_full');
  6353. modSettings.themes.hideDiscordBtns = false;
  6354. }
  6355. updateStorage();
  6356. });
  6357.  
  6358. if (modSettings.themes.hideDiscordBtns) {
  6359. dclinkdiv.classList.add('hidden_full');
  6360. hideDiscordBtns.checked = true;
  6361. }
  6362.  
  6363. const hideLangs = document.getElementById('hideLangs');
  6364. const langsDiv = document.querySelector('.ch-lang');
  6365.  
  6366. hideLangs.addEventListener('change', () => {
  6367. if (hideLangs.checked) {
  6368. langsDiv.classList.add('hidden_full');
  6369. modSettings.themes.hideLangs = true;
  6370. } else {
  6371. langsDiv.classList.remove('hidden_full');
  6372. modSettings.themes.hideLangs = false;
  6373. }
  6374. updateStorage();
  6375. });
  6376.  
  6377. if (modSettings.themes.hideLangs && langsDiv) {
  6378. langsDiv.classList.add('hidden_full');
  6379. hideLangs.checked = true;
  6380. }
  6381.  
  6382. const popup = byId('shop-popup');
  6383. const removeShopPopup = byId('removeShopPopup');
  6384. removeShopPopup.addEventListener('change', () => {
  6385. if (removeShopPopup.checked) {
  6386. popup.classList.add('hidden_full');
  6387. modSettings.settings.removeShopPopup = true;
  6388. } else {
  6389. popup.classList.remove('hidden_full');
  6390. modSettings.settings.removeShopPopup = false;
  6391. }
  6392. updateStorage();
  6393. });
  6394.  
  6395. if (modSettings.settings.removeShopPopup) {
  6396. popup.classList.add('hidden_full');
  6397. removeShopPopup.checked = true;
  6398. }
  6399. },
  6400.  
  6401. isAuthenticated() {
  6402. const name = byId('profile-name');
  6403. if (name && (name !== 'Guest' || name !== 'undefined')) return true;
  6404. else return false;
  6405. },
  6406.  
  6407. chat() {
  6408. const chatDiv = document.createElement('div');
  6409. chatDiv.classList.add('modChat');
  6410. chatDiv.innerHTML = `
  6411. <div class="modChat__inner">
  6412. <button id="scroll-down-btn" class="modButton">↓</button>
  6413. <div class="modchat-chatbuttons">
  6414. <button class="chatButton" id="mainchat">Main</button>
  6415. <button class="chatButton" id="partychat">Party</button>
  6416. <span class="tagText"></span>
  6417. </div>
  6418. <div id="mod-messages" class="scroll"></div>
  6419. <div id="chatInputContainer">
  6420. <input type="text" id="chatSendInput" class="chatInput" placeholder="${
  6421. this.isAuthenticated()
  6422. ? 'message...'
  6423. : 'Login to use the chat'
  6424. }" maxlength="250" minlength="1" ${
  6425. this.isAuthenticated() ? '' : 'disabled'
  6426. } />
  6427. <button class="chatButton" id="openChatSettings">
  6428. <svg width="15" height="15" viewBox="0 0 20 20" fill="#fff" xmlns="http://www.w3.org/2000/svg">
  6429. <path d="M17.4249 7.45169C15.7658 7.45169 15.0874 6.27836 15.9124 4.83919C16.3891 4.00503 16.1049 2.94169 15.2708 2.46503L13.6849 1.55753C12.9608 1.12669 12.0258 1.38336 11.5949 2.10753L11.4941 2.28169C10.6691 3.72086 9.31242 3.72086 8.47825 2.28169L8.37742 2.10753C7.96492 1.38336 7.02992 1.12669 6.30575 1.55753L4.71992 2.46503C3.88575 2.94169 3.60158 4.01419 4.07825 4.84836C4.91242 6.27836 4.23408 7.45169 2.57492 7.45169C1.62159 7.45169 0.833252 8.23086 0.833252 9.19336V10.8067C0.833252 11.76 1.61242 12.5484 2.57492 12.5484C4.23408 12.5484 4.91242 13.7217 4.07825 15.1609C3.60158 15.995 3.88575 17.0584 4.71992 17.535L6.30575 18.4425C7.02992 18.8734 7.96492 18.6167 8.39575 17.8925L8.49658 17.7184C9.32158 16.2792 10.6783 16.2792 11.5124 17.7184L11.6133 17.8925C12.0441 18.6167 12.9791 18.8734 13.7033 18.4425L15.2891 17.535C16.1233 17.0584 16.4074 15.9859 15.9307 15.1609C15.0966 13.7217 15.7749 12.5484 17.4341 12.5484C18.3874 12.5484 19.1758 11.7692 19.1758 10.8067V9.19336C19.1666 8.24003 18.3874 7.45169 17.4249 7.45169ZM9.99992 12.9792C8.35908 12.9792 7.02075 11.6409 7.02075 10C7.02075 8.35919 8.35908 7.02086 9.99992 7.02086C11.6408 7.02086 12.9791 8.35919 12.9791 10C12.9791 11.6409 11.6408 12.9792 9.99992 12.9792Z" fill="#fff"></path>
  6430. </svg>
  6431. </button>
  6432. <button class="chatButton" id="openEmojiMenu">
  6433. <svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><path fill="#ffffff" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM164.1 325.5C182 346.2 212.6 368 256 368s74-21.8 91.9-42.5c5.8-6.7 15.9-7.4 22.6-1.6s7.4 15.9 1.6 22.6C349.8 372.1 311.1 400 256 400s-93.8-27.9-116.1-53.5c-5.8-6.7-5.1-16.8 1.6-22.6s16.8-5.1 22.6 1.6zm53.5-96.7s0 0 0 0c0 0 0 0 0 0l-.2-.2c-.2-.2-.4-.5-.7-.9c-.6-.8-1.6-2-2.8-3.4c-2.5-2.8-6-6.6-10.2-10.3c-8.8-7.8-18.8-14-27.7-14s-18.9 6.2-27.7 14c-4.2 3.7-7.7 7.5-10.2 10.3c-1.2 1.4-2.2 2.6-2.8 3.4c-.3 .4-.6 .7-.7 .9l-.2 .2c0 0 0 0 0 0c0 0 0 0 0 0s0 0 0 0c-2.1 2.8-5.7 3.9-8.9 2.8s-5.5-4.1-5.5-7.6c0-17.9 6.7-35.6 16.6-48.8c9.8-13 23.9-23.2 39.4-23.2s29.6 10.2 39.4 23.2c9.9 13.2 16.6 30.9 16.6 48.8c0 3.4-2.2 6.5-5.5 7.6s-6.9 0-8.9-2.8c0 0 0 0 0 0s0 0 0 0zm160 0c0 0 0 0 0 0l-.2-.2c-.2-.2-.4-.5-.7-.9c-.6-.8-1.6-2-2.8-3.4c-2.5-2.8-6-6.6-10.2-10.3c-8.8-7.8-18.8-14-27.7-14s-18.9 6.2-27.7 14c-4.2 3.7-7.7 7.5-10.2 10.3c-1.2 1.4-2.2 2.6-2.8 3.4c-.3 .4-.6 .7-.7 .9l-.2 .2c0 0 0 0 0 0c0 0 0 0 0 0s0 0 0 0c-2.1 2.8-5.7 3.9-8.9 2.8s-5.5-4.1-5.5-7.6c0-17.9 6.7-35.6 16.6-48.8c9.8-13 23.9-23.2 39.4-23.2s29.6 10.2 39.4 23.2c9.9 13.2 16.6 30.9 16.6 48.8c0 3.4-2.2 6.5-5.5 7.6s-6.9 0-8.9-2.8c0 0 0 0 0 0s0 0 0 0s0 0 0 0z"/></svg>
  6434. </button>
  6435. <button id="sendButton" class="chatButton">
  6436. Send
  6437. <svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><path fill="#ffffff" d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480l0-83.6c0-4 1.5-7.8 4.2-10.8L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z"/></svg>
  6438. </button>
  6439. </div>
  6440. </div>
  6441. `;
  6442. document.body.append(chatDiv);
  6443.  
  6444. const chatContainer = byId('mod-messages');
  6445. const scrollDownButton = byId('scroll-down-btn');
  6446.  
  6447. chatContainer.addEventListener('scroll', () => {
  6448. if (
  6449. chatContainer.scrollHeight - chatContainer.scrollTop >
  6450. 300
  6451. ) {
  6452. scrollDownButton.style.display = 'block';
  6453. }
  6454. if (
  6455. chatContainer.scrollHeight - chatContainer.scrollTop <
  6456. 299 &&
  6457. scrollDownButton.style.display === 'block'
  6458. ) {
  6459. scrollDownButton.style.display = 'none';
  6460. }
  6461. });
  6462.  
  6463. scrollDownButton.addEventListener('click', () => {
  6464. chatContainer.scrollTop = chatContainer.scrollHeight;
  6465. });
  6466.  
  6467. const main = byId('mainchat');
  6468. const party = byId('partychat');
  6469. main.addEventListener('click', () => {
  6470. if (!window.gameSettings.user) {
  6471. const chatSendInput =
  6472. document.querySelector('#chatSendInput');
  6473. if (!chatSendInput) return;
  6474.  
  6475. chatSendInput.placeholder = 'Login to use the chat';
  6476. chatSendInput.disabled = true;
  6477. }
  6478. if (modSettings.chat.showClientChat) {
  6479. byId('mod-messages').innerHTML = '';
  6480. modSettings.chat.showClientChat = false;
  6481. updateStorage();
  6482. }
  6483. });
  6484. party.addEventListener('click', () => {
  6485. const chatSendInput = document.querySelector('#chatSendInput');
  6486. if (chatSendInput) {
  6487. chatSendInput.placeholder = 'message...';
  6488. chatSendInput.disabled = false;
  6489. }
  6490.  
  6491. if (!modSettings.chat.showClientChat) {
  6492. modSettings.chat.showClientChat = true;
  6493. updateStorage();
  6494. }
  6495. const modMessages = byId('mod-messages');
  6496. if (!modSettings.settings.tag) {
  6497. modMessages.innerHTML = `
  6498. <div class="message">
  6499. <span>
  6500. <span style="color: #5a44eb" class="message_name">[SERVER]</span>: You need to be in a tag to use the SigMod party chat.
  6501. </span>
  6502. </div>
  6503. `;
  6504. } else {
  6505. modMessages.innerHTML = `
  6506. <div class="message">
  6507. <span>
  6508. <span style="color: #5a44eb" class="message_name">[SERVER]</span>: Welcome to the SigMod party chat!
  6509. </span>
  6510. </div>
  6511. `;
  6512. }
  6513. });
  6514.  
  6515. if (modSettings.chat.showClientChat) {
  6516. const chatSendInput = document.querySelector('#chatSendInput');
  6517. if (!chatSendInput) return;
  6518.  
  6519. chatSendInput.placeholder = 'message...';
  6520. chatSendInput.disabled = false;
  6521.  
  6522. setTimeout(() => {
  6523. const modMessages = byId('mod-messages');
  6524. modMessages.innerHTML = `
  6525. <div class="message">
  6526. <span>
  6527. <span style="color: #5a44eb" class="message_name">[SERVER]</span>: Welcome to the SigMod party chat!
  6528. </span>
  6529. </div>
  6530. `;
  6531. }, 1000);
  6532. }
  6533.  
  6534. const text = byId('chatSendInput');
  6535. const send = byId('sendButton');
  6536.  
  6537. send.addEventListener('click', () => {
  6538. let val = text.value;
  6539. if (val === '') return;
  6540.  
  6541. if (modSettings.chat.showClientChat) {
  6542. if (client?.ws?.readyState !== 1) return;
  6543. // party chat message
  6544. client.send({
  6545. type: 'chat-message',
  6546. content: {
  6547. message: val,
  6548. },
  6549. });
  6550. } else {
  6551. // Sigmally chat message: split text into parts if message is longer than 15 characters
  6552. if (val.length > 15) {
  6553. const parts = [];
  6554. let currentPart = '';
  6555.  
  6556. // split the input value into individual words
  6557. val.split(' ').forEach((word) => {
  6558. if (currentPart.length + word.length + 1 <= 15) {
  6559. currentPart += (currentPart ? ' ' : '') + word;
  6560. } else {
  6561. parts.push(currentPart);
  6562. currentPart = word;
  6563. }
  6564. });
  6565.  
  6566. if (currentPart) {
  6567. parts.push(currentPart);
  6568. }
  6569.  
  6570. let index = 0;
  6571. const sendPart = () => {
  6572. if (index < parts.length) {
  6573. window.sendChat(parts[index]);
  6574. index++;
  6575. setTimeout(sendPart, 1000); // 1s cooldown from sigmally
  6576. }
  6577. };
  6578.  
  6579. sendPart();
  6580. } else {
  6581. window.sendChat(val);
  6582. }
  6583. }
  6584.  
  6585. text.value = '';
  6586. text.blur();
  6587. });
  6588.  
  6589. this.chatSettings();
  6590. this.emojiMenu();
  6591. this.getBlockedChatData();
  6592.  
  6593. const chatSettingsContainer = document.querySelector(
  6594. '.chatSettingsContainer'
  6595. );
  6596. const emojisContainer = document.querySelector('.emojisContainer');
  6597.  
  6598. byId('openChatSettings').addEventListener('click', () => {
  6599. if (chatSettingsContainer.classList.contains('hidden_full')) {
  6600. chatSettingsContainer.classList.remove('hidden_full');
  6601. emojisContainer.classList.add('hidden_full');
  6602. } else {
  6603. chatSettingsContainer.classList.add('hidden_full');
  6604. }
  6605. });
  6606.  
  6607. const scrollUpButton = byId('scroll-down-btn');
  6608. let focused = false;
  6609. let typed = false;
  6610.  
  6611. document.addEventListener('keydown', (e) => {
  6612. if (e.key === 'Enter' && text.value.length > 0) {
  6613. send.click();
  6614. focused = false;
  6615. scrollUpButton.click();
  6616. } else if (e.key === 'Enter') {
  6617. if (
  6618. document.activeElement.tagName === 'INPUT' ||
  6619. document.activeElement.tagName === 'TEXTAREA'
  6620. )
  6621. return;
  6622.  
  6623. focused ? text.blur() : text.focus();
  6624. focused = !focused;
  6625. }
  6626. });
  6627.  
  6628. text.addEventListener('input', () => {
  6629. typed = text.value.length > 1;
  6630. });
  6631.  
  6632. text.addEventListener('blur', () => {
  6633. focused = false;
  6634. });
  6635.  
  6636. text.addEventListener('keydown', (e) => {
  6637. const key = e.key.toLowerCase();
  6638. if (key === 'w') {
  6639. e.stopPropagation();
  6640. }
  6641.  
  6642. if (key === ' ') {
  6643. e.stopPropagation();
  6644. }
  6645. });
  6646.  
  6647. // switch to compact chat
  6648. const chatElements = [
  6649. '.modChat',
  6650. '.emojisContainer',
  6651. '.chatSettingsContainer',
  6652. ];
  6653.  
  6654. const emojiBtn = byId('openEmojiMenu');
  6655. const compactChat = byId('compactChat');
  6656. compactChat.addEventListener('change', () => {
  6657. compactChat.checked ? compactMode() : defaultMode();
  6658. });
  6659.  
  6660. function compactMode() {
  6661. chatElements.forEach((querySelector) => {
  6662. const el = document.querySelector(querySelector);
  6663. if (el) {
  6664. el.classList.add('mod-compact');
  6665. }
  6666. });
  6667. emojiBtn.style.display = 'none';
  6668.  
  6669. modSettings.chat.compact = true;
  6670. updateStorage();
  6671. }
  6672.  
  6673. function defaultMode() {
  6674. chatElements.forEach((querySelector) => {
  6675. const el = document.querySelector(querySelector);
  6676. if (el) {
  6677. el.classList.remove('mod-compact');
  6678. }
  6679. });
  6680. emojiBtn.style.display = 'flex';
  6681.  
  6682. modSettings.chat.compact = false;
  6683. updateStorage();
  6684. }
  6685.  
  6686. if (modSettings.chat.compact) compactMode();
  6687. },
  6688.  
  6689. spamMessage(name, message) {
  6690. return (
  6691. this.blockedChatData.names.some((n) =>
  6692. name.toLowerCase().includes(n.toLowerCase())
  6693. ) ||
  6694. this.blockedChatData.messages.some((m) =>
  6695. message.toLowerCase().includes(m.toLowerCase())
  6696. )
  6697. );
  6698. },
  6699.  
  6700. updateChat(data) {
  6701. const chatContainer = byId('mod-messages');
  6702. const isScrolledToBottom =
  6703. chatContainer.scrollHeight - chatContainer.scrollTop <=
  6704. chatContainer.clientHeight + 1;
  6705. const isNearBottom =
  6706. chatContainer.scrollHeight - chatContainer.scrollTop - 200 <=
  6707. chatContainer.clientHeight;
  6708.  
  6709. let { name, message, time = '' } = data;
  6710. name = noXSS(name);
  6711. time = data.time !== null ? prettyTime.am_pm(data.time) : '';
  6712.  
  6713. const color = this.friend_names.has(name)
  6714. ? this.friends_settings.highlight_color
  6715. : data.color || '#ffffff';
  6716. const glow =
  6717. this.friend_names.has(name) &&
  6718. this.friends_settings.highlight_friends
  6719. ? `text-shadow: 0 1px 3px ${color}`
  6720. : '';
  6721. const id = rdmString(12);
  6722.  
  6723. const chatMessage = document.createElement('div');
  6724. chatMessage.classList.add('message');
  6725. chatMessage.innerHTML = `
  6726. <div class="centerX" style="gap: 3px;">
  6727. <div class="flex">
  6728. <span style="color: ${color};${glow}" class="message_name" id="${id}">${name}</span>
  6729. <span>&#58;</span>
  6730. </div>
  6731. <span class="chatMessage-text"></span>
  6732. </div>
  6733. <span class="time">${time}</span>
  6734. `;
  6735. chatMessage.querySelector('.chatMessage-text').innerHTML = message;
  6736.  
  6737. chatContainer.append(chatMessage);
  6738. if (isScrolledToBottom || isNearBottom)
  6739. chatContainer.scrollTop = chatContainer.scrollHeight;
  6740.  
  6741. if (name === this.nick) return;
  6742.  
  6743. const nameEl = byId(id);
  6744. nameEl.addEventListener('mousedown', (e) =>
  6745. this.handleContextMenu(e, name)
  6746. );
  6747. nameEl.addEventListener('contextmenu', (e) => {
  6748. e.preventDefault();
  6749. e.stopPropagation();
  6750. });
  6751.  
  6752. if (++this.renderedMessages > this.maxChatMessages) {
  6753. chatContainer.removeChild(chatContainer.firstChild);
  6754. this.renderedMessages--;
  6755. }
  6756. },
  6757.  
  6758. handleContextMenu(e, name) {
  6759. if (this.onContext || e.button !== 2) return;
  6760.  
  6761. const contextMenu = document.createElement('div');
  6762. contextMenu.classList.add('chat-context');
  6763. contextMenu.innerHTML = `
  6764. <span>${name}</span>
  6765. <button id="muteButton">Mute</button>
  6766. `;
  6767.  
  6768. Object.assign(contextMenu.style, {
  6769. top: `${e.clientY - 80}px`,
  6770. left: `${e.clientX}px`,
  6771. });
  6772.  
  6773. document.body.appendChild(contextMenu);
  6774. this.onContext = true;
  6775.  
  6776. byId('muteButton').addEventListener('click', () => {
  6777. const confirmMsg =
  6778. name === 'Spectator'
  6779. ? 'Are you sure you want to mute all spectators until you refresh the page?'
  6780. : `Are you sure you want to mute '${name}' until you refresh the page?`;
  6781.  
  6782. if (confirm(confirmMsg)) {
  6783. this.muteUser(name);
  6784. contextMenu.remove();
  6785. }
  6786. });
  6787.  
  6788. document.addEventListener('click', (event) => {
  6789. if (!contextMenu.contains(event.target)) {
  6790. this.onContext = false;
  6791. contextMenu.remove();
  6792. }
  6793. });
  6794. },
  6795.  
  6796. muteUser(name) {
  6797. this.mutedUsers.push(name);
  6798.  
  6799. const msgNames = document.querySelectorAll('.message_name');
  6800. msgNames.forEach((msgName) => {
  6801. if (msgName.innerHTML === name) {
  6802. const msgParent = msgName.closest('.message');
  6803. msgParent.remove();
  6804. }
  6805. });
  6806. },
  6807.  
  6808. async getGoogleFonts() {
  6809. return await (await fetch(this.appRoutes.fonts)).json();
  6810. },
  6811.  
  6812. async getEmojis() {
  6813. const response = await fetch(
  6814. 'https://czrsd.com/static/sigmod/emojis.json'
  6815. );
  6816. return await response.json();
  6817. },
  6818.  
  6819. emojiMenu() {
  6820. const updateEmojis = (searchTerm = '') => {
  6821. const emojisContainer =
  6822. document.querySelector('.emojisContainer');
  6823. const categoriesContainer =
  6824. emojisContainer.querySelector('#categories');
  6825.  
  6826. categoriesContainer.innerHTML = '';
  6827. window.emojis.forEach((emojiData) => {
  6828. const { emoji, description, category, tags } = emojiData;
  6829. if (
  6830. !searchTerm ||
  6831. tags.some((tag) =>
  6832. tag.includes(searchTerm.toLowerCase())
  6833. )
  6834. ) {
  6835. let categoryId = category
  6836. .replace(/\s+/g, '-')
  6837. .replace('&', 'and')
  6838. .toLowerCase();
  6839. let categoryDiv = categoriesContainer.querySelector(
  6840. `#${categoryId}`
  6841. );
  6842. if (!categoryDiv) {
  6843. categoryDiv = document.createElement('div');
  6844. categoryDiv.id = categoryId;
  6845. categoryDiv.classList.add('category');
  6846. categoryDiv.innerHTML = `<span>${category}</span><div class="emojiContainer"></div>`;
  6847. categoriesContainer.appendChild(categoryDiv);
  6848. }
  6849. const emojiContainer =
  6850. categoryDiv.querySelector('.emojiContainer');
  6851. const emojiDiv = document.createElement('div');
  6852. emojiDiv.classList.add('emoji');
  6853. emojiDiv.innerHTML = emoji;
  6854. emojiDiv.title = `${emoji} - ${description}`;
  6855. emojiDiv.addEventListener('click', () => {
  6856. const chatInput =
  6857. document.querySelector('#chatSendInput');
  6858. chatInput.value += emoji;
  6859. });
  6860. emojiContainer.appendChild(emojiDiv);
  6861. }
  6862. });
  6863. };
  6864.  
  6865. const emojisContainer = document.createElement('div');
  6866. emojisContainer.classList.add(
  6867. 'chatAddedContainer',
  6868. 'emojisContainer',
  6869. 'hidden_full'
  6870. );
  6871. emojisContainer.innerHTML = `<input type="text" class="chatInput" id="searchEmoji" style="background-color: #050505; border-radius: .5rem; flex-grow: 0;" placeholder="Search..." /><div id="categories" class="scroll"></div>`;
  6872.  
  6873. const chatInput = emojisContainer.querySelector('#searchEmoji');
  6874. chatInput.addEventListener('input', (event) => {
  6875. const searchTerm = event.target.value.toLowerCase();
  6876. updateEmojis(searchTerm);
  6877. });
  6878.  
  6879. document.body.append(emojisContainer);
  6880.  
  6881. const chatSettingsContainer = document.querySelector(
  6882. '.chatSettingsContainer'
  6883. );
  6884.  
  6885. byId('openEmojiMenu').addEventListener('click', () => {
  6886. if (!window.emojis) {
  6887. this.getEmojis().then((emojis) => {
  6888. window.emojis = emojis;
  6889. updateEmojis();
  6890. });
  6891. }
  6892.  
  6893. if (emojisContainer.classList.contains('hidden_full')) {
  6894. emojisContainer.classList.remove('hidden_full');
  6895. chatSettingsContainer.classList.add('hidden_full');
  6896. } else {
  6897. emojisContainer.classList.add('hidden_full');
  6898. }
  6899. });
  6900. },
  6901.  
  6902. chatSettings() {
  6903. const menu = document.createElement('div');
  6904. menu.classList.add(
  6905. 'chatAddedContainer',
  6906. 'chatSettingsContainer',
  6907. 'scroll',
  6908. 'hidden_full'
  6909. );
  6910. menu.innerHTML = `
  6911. <div class="modInfoPopup" style="display: none">
  6912. <p>Send location in chat with keybind</p>
  6913. </div>
  6914. <div class="scroll">
  6915. <div class="csBlock">
  6916. <div class="csBlockTitle">
  6917. <span>Keybindings</span>
  6918. </div>
  6919. <div class="csRow">
  6920. <div class="csRowName">
  6921. <span>Location</span>
  6922. <span class="infoIcon">
  6923. <svg fill="#ffffff" id="Capa_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 416.979 416.979" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M356.004,61.156c-81.37-81.47-213.377-81.551-294.848-0.182c-81.47,81.371-81.552,213.379-0.181,294.85 c81.369,81.47,213.378,81.551,294.849,0.181C437.293,274.636,437.375,142.626,356.004,61.156z M237.6,340.786 c0,3.217-2.607,5.822-5.822,5.822h-46.576c-3.215,0-5.822-2.605-5.822-5.822V167.885c0-3.217,2.607-5.822,5.822-5.822h46.576 c3.215,0,5.822,2.604,5.822,5.822V340.786z M208.49,137.901c-18.618,0-33.766-15.146-33.766-33.765 c0-18.617,15.147-33.766,33.766-33.766c18.619,0,33.766,15.148,33.766,33.766C242.256,122.755,227.107,137.901,208.49,137.901z"></path> </g> </g></svg>
  6924. </span>
  6925. </div>
  6926. <input type="text" name="location" data-label="Send location" id="modinput8" class="keybinding" value="${
  6927. modSettings.macros.keys.location || ''
  6928. }" placeholder="..." maxlength="1" onfocus="this.select()">
  6929. </div>
  6930. <div class="csRow">
  6931. <div class="csRowName">
  6932. <span>Show / Hide</span>
  6933. </div>
  6934. <input type="text" name="toggle.chat" data-label="Toggle chat" id="modinput9" class="keybinding" value="${
  6935. modSettings.macros.keys.toggle.chat || ''
  6936. }" placeholder="..." maxlength="1" onfocus="this.select()">
  6937. </div>
  6938. </div>
  6939. <div class="csBlock">
  6940. <div class="csBlockTitle">
  6941. <span>General</span>
  6942. </div>
  6943. <div class="csRow">
  6944. <div class="csRowName">
  6945. <span>Time</span>
  6946. </div>
  6947. <div class="modCheckbox">
  6948. <input id="showChatTime" type="checkbox" checked />
  6949. <label class="cbx" for="showChatTime"></label>
  6950. </div>
  6951. </div>
  6952. <div class="csRow">
  6953. <div class="csRowName">
  6954. <span>Name colors</span>
  6955. </div>
  6956. <div class="modCheckbox">
  6957. <input id="showNameColors" type="checkbox" checked />
  6958. <label class="cbx" for="showNameColors"></label>
  6959. </div>
  6960. </div>
  6961. <div class="csRow">
  6962. <div class="csRowName">
  6963. <span>Party / Main</span>
  6964. </div>
  6965. <div class="modCheckbox">
  6966. <input id="showPartyMain" type="checkbox" checked />
  6967. <label class="cbx" for="showPartyMain"></label>
  6968. </div>
  6969. </div>
  6970. <div class="csRow">
  6971. <div class="csRowName">
  6972. <span>Blur Tag</span>
  6973. </div>
  6974. <div class="modCheckbox">
  6975. <input id="blurTag" type="checkbox" checked />
  6976. <label class="cbx" for="blurTag"></label>
  6977. </div>
  6978. </div>
  6979. <div class="flex f-column g-5 centerXY" style="padding: 0 5px">
  6980. <div class="csRowName">
  6981. <span>Location text</span>
  6982. </div>
  6983. <input type="text" id="locationText" placeholder="{pos}..." value="{pos}" class="form-control" />
  6984. </div>
  6985. </div>
  6986. <div class="csBlock">
  6987. <div class="csBlockTitle">
  6988. <span>Style</span>
  6989. </div>
  6990. <div class="csRow">
  6991. <div class="csRowName">
  6992. <span>Compact chat</span>
  6993. </div>
  6994. <div class="modCheckbox">
  6995. <input id="compactChat" type="checkbox" ${
  6996. modSettings.chat.compact ? 'checked' : ''
  6997. } />
  6998. <label class="cbx" for="compactChat"></label>
  6999. </div>
  7000. </div>
  7001. <div class="csRow">
  7002. <div class="csRowName">
  7003. <span>Text</span>
  7004. </div>
  7005. <div id="chatTextColor"></div>
  7006. </div>
  7007. <div class="csRow">
  7008. <div class="csRowName">
  7009. <span>Background</span>
  7010. </div>
  7011. <div id="chatBackground"></div>
  7012. </div>
  7013. <div class="csRow">
  7014. <div class="csRowName">
  7015. <span>Theme</span>
  7016. </div>
  7017. <div id="chatThemeChanger"></div>
  7018. </div>
  7019. </div>
  7020. </div>
  7021. `;
  7022. document.body.append(menu);
  7023.  
  7024. const infoIcon = document.querySelector('.infoIcon');
  7025. const modInfoPopup = document.querySelector('.modInfoPopup');
  7026. let popupOpen = false;
  7027.  
  7028. infoIcon.addEventListener('click', (event) => {
  7029. event.stopPropagation();
  7030. modInfoPopup.style.display = popupOpen ? 'none' : 'block';
  7031. popupOpen = !popupOpen;
  7032. });
  7033.  
  7034. document.addEventListener('click', (event) => {
  7035. if (popupOpen && !modInfoPopup.contains(event.target)) {
  7036. modInfoPopup.style.display = 'none';
  7037. popupOpen = false;
  7038. }
  7039. });
  7040.  
  7041. const showChatTime = document.querySelector('#showChatTime');
  7042. const showNameColors = document.querySelector('#showNameColors');
  7043.  
  7044. showChatTime.addEventListener('change', () => {
  7045. const timeElements = document.querySelectorAll('.time');
  7046. if (showChatTime.checked) {
  7047. modSettings.chat.showTime = true;
  7048. updateStorage();
  7049. } else {
  7050. modSettings.chat.showTime = false;
  7051. if (timeElements) {
  7052. timeElements.forEach((el) => (el.innerHTML = ''));
  7053. }
  7054. updateStorage();
  7055. }
  7056. });
  7057.  
  7058. showNameColors.addEventListener('change', () => {
  7059. const message_names =
  7060. document.querySelectorAll('.message_name');
  7061. if (showNameColors.checked) {
  7062. modSettings.chat.showNameColors = true;
  7063. updateStorage();
  7064. } else {
  7065. modSettings.chat.showNameColors = false;
  7066. if (message_names) {
  7067. message_names.forEach(
  7068. (el) => (el.style.color = '#fafafa')
  7069. );
  7070. }
  7071. updateStorage();
  7072. }
  7073. });
  7074.  
  7075. // remove old rgba val
  7076. if (modSettings.chat.bgColor.includes('rgba')) {
  7077. modSettings.chat.bgColor = RgbaToHex(modSettings.chat.bgColor);
  7078. }
  7079.  
  7080. const modChat = document.querySelector('.modChat');
  7081. modChat.style.background = modSettings.chat.bgColor;
  7082.  
  7083. const showPartyMain = document.querySelector('#showPartyMain');
  7084. const chatHeader = document.querySelector('.modchat-chatbuttons');
  7085.  
  7086. const changeButtonsState = (show) => {
  7087. chatHeader.style.display = show ? 'flex' : 'none';
  7088. modChat.style.maxHeight = show ? '285px' : '250px';
  7089. modChat.style.minHeight = show ? '285px' : '250px';
  7090. const modChatInner = document.querySelector('.modChat__inner');
  7091. modChatInner.style.maxHeight = show ? '265px' : '230px';
  7092. modChatInner.style.minHeight = show ? '265px' : '230px';
  7093. };
  7094.  
  7095. showPartyMain.addEventListener('change', () => {
  7096. const show = showPartyMain.checked;
  7097. modSettings.chat.showChatButtons = show;
  7098. changeButtonsState(show);
  7099. updateStorage();
  7100. });
  7101.  
  7102. showPartyMain.checked = modSettings.chat.showChatButtons;
  7103. changeButtonsState(modSettings.chat.showChatButtons);
  7104.  
  7105. setTimeout(() => {
  7106. const blurTag = byId('blurTag');
  7107. const tagText = document.querySelector('.tagText');
  7108. const tagElement = document.querySelector('#tag');
  7109. blurTag.addEventListener('change', () => {
  7110. const state = blurTag.checked;
  7111.  
  7112. state
  7113. ? (tagText.classList.add('blur'),
  7114. tagElement.classList.add('blur'))
  7115. : (tagText.classList.remove('blur'),
  7116. tagElement.classList.remove('blur'));
  7117. modSettings.chat.blurTag = state;
  7118. updateStorage();
  7119. });
  7120. blurTag.checked = modSettings.chat.blurTag;
  7121. if (modSettings.chat.blurTag) {
  7122. tagText.classList.add('blur');
  7123. tagElement.classList.add('blur');
  7124. }
  7125. });
  7126.  
  7127. const locationText = byId('locationText');
  7128. locationText.addEventListener('input', (e) => {
  7129. e.stopPropagation();
  7130. modSettings.chat.locationText = locationText.value;
  7131. });
  7132. locationText.value = modSettings.chat.locationText || '{pos}';
  7133. },
  7134.  
  7135. toggleChat() {
  7136. const modChat = document.querySelector('.modChat');
  7137. const modChatAdded = document.querySelectorAll(
  7138. '.chatAddedContainer'
  7139. );
  7140.  
  7141. const isModChatHidden =
  7142. modChat.style.display === 'none' ||
  7143. getComputedStyle(modChat).display === 'none';
  7144.  
  7145. if (isModChatHidden) {
  7146. modChat.style.opacity = 0;
  7147. modChat.style.display = 'flex';
  7148.  
  7149. setTimeout(() => {
  7150. modChat.style.opacity = 1;
  7151. }, 10);
  7152. } else {
  7153. modChat.style.opacity = 0;
  7154. modChatAdded.forEach((container) =>
  7155. container.classList.add('hidden_full')
  7156. );
  7157.  
  7158. setTimeout(() => {
  7159. modChat.style.display = 'none';
  7160. }, 300);
  7161. }
  7162. },
  7163.  
  7164. smallMods() {
  7165. // fix auth for tournament page
  7166. if (location.pathname.includes('tournament')) {
  7167. const tempDiv = Object.assign(document.createElement('div'), {
  7168. className: 'top-winners__list',
  7169. });
  7170. const tempDiv2 = Object.assign(document.createElement('div'), {
  7171. className: 'top-users__list',
  7172. });
  7173. document.body.append(tempDiv, tempDiv2);
  7174.  
  7175. const observer = new MutationObserver(() => {
  7176. if (
  7177. tempDiv.children.length === 10 &&
  7178. tempDiv2.textContent.length > 0
  7179. ) {
  7180. tempDiv.remove();
  7181. tempDiv2.remove();
  7182.  
  7183. observer.disconnect();
  7184. }
  7185. });
  7186.  
  7187. observer.observe(tempDiv, { childList: true });
  7188. }
  7189.  
  7190. const modAlert_overlay = document.createElement('div');
  7191. modAlert_overlay.classList.add('alert_overlay');
  7192. modAlert_overlay.id = 'modAlert_overlay';
  7193. document.body.append(modAlert_overlay);
  7194.  
  7195. const autoRespawn = byId('autoRespawn');
  7196. if (modSettings.settings.autoRespawn) {
  7197. autoRespawn.checked = true;
  7198. }
  7199.  
  7200. autoRespawn.addEventListener('change', () => {
  7201. modSettings.settings.autoRespawn = autoRespawn.checked;
  7202. updateStorage();
  7203. });
  7204.  
  7205. const auto = byId('autoClaimCoins');
  7206. auto.addEventListener('change', () => {
  7207. const checked = auto.checked;
  7208. modSettings.settings.autoClaimCoins = !!checked;
  7209. updateStorage();
  7210. });
  7211. if (modSettings.settings.autoClaimCoins) {
  7212. auto.checked = true;
  7213. }
  7214.  
  7215. const showChallenges = byId('showChallenges');
  7216. showChallenges.addEventListener('change', () => {
  7217. if (showChallenges.checked) {
  7218. modSettings.settings.showChallenges = true;
  7219. } else {
  7220. modSettings.settings.showChallenges = false;
  7221. }
  7222. updateStorage();
  7223. });
  7224. if (modSettings.settings.showChallenges) {
  7225. showChallenges.checked = true;
  7226. }
  7227.  
  7228. const gameTitle = byId('title');
  7229.  
  7230. const newTitle = document.createElement('div');
  7231. newTitle.classList.add('sigmod-title');
  7232. newTitle.innerHTML = `
  7233. <h1 id="title">Sigmally</h1>
  7234. <span id="bycursed">Mod by <a href="https://www.youtube.com/@sigmallyCursed/" target="_blank">Cursed</a></span>
  7235. `;
  7236. gameTitle.replaceWith(newTitle);
  7237.  
  7238. const nickName = byId('nick');
  7239. nickName.maxLength = 50;
  7240. nickName.type = 'text';
  7241.  
  7242. function updNick() {
  7243. const nick = nickName.value;
  7244. mods.nick = nick;
  7245. const welcome = byId('welcomeUser');
  7246. if (welcome) {
  7247. welcome.innerHTML = `Welcome ${
  7248. mods.nick || 'Unnamed'
  7249. }, to the SigMod Client!`;
  7250. }
  7251. }
  7252.  
  7253. nickName.addEventListener('input', () => {
  7254. updNick();
  7255. });
  7256.  
  7257. updNick();
  7258.  
  7259. // Better grammar in the descriptions of the challenges
  7260. setTimeout(() => {
  7261. window.shopLocales.challenge_tab.tasks = {
  7262. eaten: 'Eat %n food in a game.',
  7263. xp: 'Get %n XP in a game.',
  7264. alive: 'Stay alive for %n minutes in a game.',
  7265. pos: 'Reach top %n on leaderboard.',
  7266. };
  7267. }, 1000);
  7268.  
  7269. const topusersInner = document.querySelector('.top-users__inner');
  7270. topusersInner.classList.add('scroll');
  7271. topusersInner.style.border = 'none';
  7272.  
  7273. byId('signOutBtn').addEventListener('click', () => {
  7274. window.gameSettings.user = null;
  7275. });
  7276.  
  7277. const mode = byId('gamemode');
  7278. mode.addEventListener('change', () => {
  7279. client.send({
  7280. type: 'server-changed',
  7281. content: getGameMode(),
  7282. });
  7283.  
  7284. const modMessages = document.querySelector('#mod-messages');
  7285. if (modMessages) {
  7286. modMessages.innerHTML = '';
  7287. }
  7288. });
  7289.  
  7290. // redirect to owned skins instead of free skins
  7291. const ot = Element.prototype.openTab;
  7292. Element.prototype.openTab = function (tab) {
  7293. if (!tab === 'skins') return;
  7294.  
  7295. setTimeout(() => {
  7296. Element.prototype.changeTab('owned');
  7297. }, 100);
  7298.  
  7299. ot.apply(this, arguments);
  7300. };
  7301.  
  7302. document.addEventListener('mousemove', (e) => {
  7303. this.mouseX = e.clientX + window.pageXOffset;
  7304. this.mouseY = e.clientY + window.pageYOffset;
  7305.  
  7306. const mouseTracker = document.querySelector('.mouseTracker');
  7307. if (!mouseTracker) return;
  7308.  
  7309. mouseTracker.innerText = `X: ${this.mouseX}; Y: ${this.mouseY}`;
  7310. });
  7311.  
  7312. if (location.search.includes('password')) {
  7313. const passwordField = byId('password');
  7314. if (passwordField) passwordField.style.display = 'none';
  7315.  
  7316. const password =
  7317. new URLSearchParams(location.search)
  7318. .get('password')
  7319. ?.split('/')[0] || '';
  7320. passwordField.value = password; // sigfixes should know the password when multiboxing
  7321.  
  7322. if (window.sigfix) return;
  7323.  
  7324. this.playBtn.addEventListener('click', (e) => {
  7325. const waitForConnection = () =>
  7326. new Promise((res) => {
  7327. if (client?.ws?.readyState === 1) return res(null);
  7328. const i = setInterval(
  7329. () =>
  7330. client?.ws?.readyState === 1 &&
  7331. (clearInterval(i), res(null)),
  7332. 50
  7333. );
  7334. });
  7335.  
  7336. waitForConnection().then(async () => {
  7337. await wait(500);
  7338. mods.sendPlay(password);
  7339.  
  7340. const interval = setInterval(() => {
  7341. const errormodal = byId('errormodal');
  7342. if (errormodal?.style.display !== 'none')
  7343. errormodal.style.display = 'none';
  7344. });
  7345.  
  7346. setTimeout(() => clearInterval(interval), 1000);
  7347. });
  7348. });
  7349. }
  7350. },
  7351.  
  7352. removeStorage(storage) {
  7353. localStorage.removeItem(storage);
  7354. },
  7355.  
  7356. credits() {
  7357. console.log(
  7358. `%c
  7359. ‎░█▀▀▀█ ▀█▀ ░█▀▀█ ░█▀▄▀█ ░█▀▀▀█ ░█▀▀▄
  7360. ‎─▀▀▀▄▄ ░█─ ░█─▄▄ ░█░█░█ ░█──░█ ░█─░█ V${version}
  7361. ‎░█▄▄▄█ ▄█▄ ░█▄▄█ ░█──░█ ░█▄▄▄█ ░█▄▄▀
  7362. ██████╗░██╗░░░██╗  ░█████╗░██╗░░░██╗██████╗░░██████╗███████╗██████╗░
  7363. ██╔══██╗╚██╗░██╔╝  ██╔══██╗██║░░░██║██╔══██╗██╔════╝██╔════╝██╔══██╗
  7364. ██████╦╝░╚████╔╝░  ██║░░╚═╝██║░░░██║██████╔╝╚█████╗░█████╗░░██║░░██║
  7365. ██╔══██╗░░╚██╔╝░░  ██║░░██╗██║░░░██║██╔══██╗░╚═══██╗██╔══╝░░██║░░██║
  7366. ██████╦╝░░░██║░░░  ╚█████╔╝╚██████╔╝██║░░██║██████╔╝███████╗██████╔╝
  7367. ╚═════╝░░░░╚═╝░░░  ░╚════╝░░╚═════╝░╚═╝░░╚═╝╚═════╝░╚══════╝╚═════╝░
  7368. `,
  7369. 'background-color: black; color: green'
  7370. );
  7371. },
  7372.  
  7373. handleAlert(data) {
  7374. const { title, description, enabled, password } = data;
  7375.  
  7376. if (
  7377. location.pathname.includes('tournament') ||
  7378. client.updateAvailable
  7379. )
  7380. return;
  7381.  
  7382. const hideAlert = Number(localStorage.getItem('hide-alert'));
  7383. // Don't show alert if it has been closed the past 3 hours
  7384. if (
  7385. !enabled ||
  7386. (hideAlert && Date.now() - hideAlert < 3 * 60 * 60 * 1000)
  7387. ) {
  7388. byId('scrim_alert')?.remove();
  7389. return;
  7390. }
  7391.  
  7392. localStorage.removeItem('hide-alert');
  7393.  
  7394. const modAlert = document.createElement('div');
  7395. modAlert.id = 'scrim_alert';
  7396. modAlert.classList.add('modAlert');
  7397. modAlert.innerHTML = `
  7398. <div class="flex justify-sb">
  7399. <strong>${title}</strong>
  7400. <button class="modButton" style="width: 35px;" id="close_scrim_alert">X</button>
  7401. </div>
  7402. <span>${description}</span>
  7403. <div class="flex" style="align-items: center; gap: 5px;">
  7404. <button id="join" class="modButton" style="width: 100%">Join</button>
  7405. </div>
  7406. `;
  7407. document.body.append(modAlert);
  7408.  
  7409. const observer = new MutationObserver(() => {
  7410. modAlert.style.display = menuClosed() ? 'none' : 'flex';
  7411. });
  7412.  
  7413. observer.observe(document.body, {
  7414. attributes: true,
  7415. childList: true,
  7416. subtree: true,
  7417. });
  7418.  
  7419. const joinButton = byId('join');
  7420. joinButton.addEventListener('click', () => {
  7421. location.href = `https://one.sigmally.com/tournament?password=${password}`;
  7422. });
  7423.  
  7424. const close = byId('close_scrim_alert');
  7425. close.addEventListener('click', () => {
  7426. modAlert.remove();
  7427. // make it not that annoying
  7428. localStorage.setItem('hide-alert', Date.now());
  7429. });
  7430. },
  7431.  
  7432. saveNames() {
  7433. let savedNames = modSettings.settings.savedNames;
  7434. let savedNamesOutput = byId('savedNames');
  7435. let saveNameBtn = byId('saveName');
  7436. let saveNameInput = byId('saveNameValue');
  7437.  
  7438. const createNameDiv = (name) => {
  7439. let nameDiv = document.createElement('div');
  7440. nameDiv.classList.add('NameDiv');
  7441.  
  7442. let nameLabel = document.createElement('label');
  7443. nameLabel.classList.add('NameLabel');
  7444. nameLabel.innerText = name;
  7445.  
  7446. let delName = document.createElement('button');
  7447. delName.innerText = 'X';
  7448. delName.classList.add('delName');
  7449.  
  7450. nameDiv.addEventListener('click', () => {
  7451. const name = nameLabel.innerText;
  7452. navigator.clipboard.writeText(name).then(() => {
  7453. this.modAlert(
  7454. `Added the name '${name}' to your clipboard!`,
  7455. 'success'
  7456. );
  7457. });
  7458. });
  7459.  
  7460. delName.addEventListener('click', () => {
  7461. if (
  7462. confirm(
  7463. "Are you sure you want to delete the name '" +
  7464. nameLabel.innerText +
  7465. "'?"
  7466. )
  7467. ) {
  7468. nameDiv.remove();
  7469. savedNames = savedNames.filter(
  7470. (n) => n !== nameLabel.innerText
  7471. );
  7472. modSettings.settings.savedNames = savedNames;
  7473. updateStorage();
  7474. }
  7475. });
  7476.  
  7477. nameDiv.appendChild(nameLabel);
  7478. nameDiv.appendChild(delName);
  7479. return nameDiv;
  7480. };
  7481.  
  7482. saveNameBtn.addEventListener('click', () => {
  7483. if (saveNameInput.value === '') return;
  7484.  
  7485. setTimeout(() => {
  7486. saveNameInput.value = '';
  7487. }, 10);
  7488.  
  7489. if (savedNames.includes(saveNameInput.value)) {
  7490. return;
  7491. }
  7492.  
  7493. let nameDiv = createNameDiv(saveNameInput.value);
  7494. savedNamesOutput.appendChild(nameDiv);
  7495.  
  7496. savedNames.push(saveNameInput.value);
  7497. modSettings.settings.savedNames = savedNames;
  7498. updateStorage();
  7499. });
  7500.  
  7501. if (savedNames.length > 0) {
  7502. savedNames.forEach((name) => {
  7503. let nameDiv = createNameDiv(name);
  7504. savedNamesOutput.appendChild(nameDiv);
  7505. });
  7506. }
  7507. },
  7508.  
  7509. initStats() {
  7510. // initialize player stats
  7511. const statElements = [
  7512. 'stat-time-played',
  7513. 'stat-highest-mass',
  7514. 'stat-total-deaths',
  7515. 'stat-total-mass',
  7516. ];
  7517.  
  7518. this.gameStats = localStorage.getItem('game-stats');
  7519.  
  7520. if (!this.gameStats) {
  7521. this.gameStats = {
  7522. 'time-played': 0, // seconds
  7523. 'highest-mass': 0,
  7524. 'total-deaths': 0,
  7525. 'total-mass': 0,
  7526. };
  7527. localStorage.setItem(
  7528. 'game-stats',
  7529. JSON.stringify(this.gameStats)
  7530. );
  7531. } else {
  7532. this.gameStats = JSON.parse(this.gameStats);
  7533. }
  7534.  
  7535. statElements.forEach((rawStat) => {
  7536. const stat = rawStat.replace('stat-', '');
  7537. const value = this.gameStats[stat];
  7538. this.updateStatElm(rawStat, value);
  7539. });
  7540. },
  7541.  
  7542. updateStat(key, value) {
  7543. this.gameStats[key] = value;
  7544. localStorage.setItem('game-stats', JSON.stringify(this.gameStats));
  7545. this.updateStatElm(key, value);
  7546. },
  7547.  
  7548. updateStatElm(stat, value) {
  7549. const element = byId(stat);
  7550.  
  7551. if (element) {
  7552. if (stat === 'stat-time-played') {
  7553. const hours = Math.floor(value / 3600);
  7554. const minutes = Math.floor((value % 3600) / 60);
  7555. const formattedTime =
  7556. hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
  7557. element.innerHTML = formattedTime;
  7558. } else {
  7559. const formattedValue =
  7560. stat === 'stat-highest-mass' ||
  7561. stat === 'stat-total-mass'
  7562. ? value > 999
  7563. ? `${(value / 1000).toFixed(1)}k`
  7564. : value.toString()
  7565. : value.toString();
  7566. element.innerHTML = formattedValue;
  7567. }
  7568. }
  7569. },
  7570.  
  7571. setupSession() {
  7572. let sigfix_exists = false;
  7573. const check = setInterval(() => {
  7574. if (!window.sigfix) return;
  7575. clearInterval(check);
  7576.  
  7577. sigfix_exists = true;
  7578. this.sigfixSession();
  7579. }, 100);
  7580.  
  7581. setTimeout(() => {
  7582. clearInterval(check);
  7583.  
  7584. if (sigfix_exists) return;
  7585. this.defaultSession();
  7586. }, 500);
  7587. },
  7588.  
  7589. sigfixSession() {
  7590. const { sigfix } = window;
  7591. const { playTimer, mouseTracker } = modSettings.settings;
  7592.  
  7593. let playingInterval;
  7594. let lastCells = 0;
  7595.  
  7596. setInterval(() => {
  7597. let allMyCells = 0;
  7598.  
  7599. sigfix.world.views.forEach((view) => {
  7600. // Newer sigfix versions use a set and older versions use an array for own cells
  7601. // We check if it's an array in case people are still using old versions of sigfixes
  7602. allMyCells += Array.isArray(view.owned)
  7603. ? view.owned.length
  7604. : view.owned.size || 0;
  7605. });
  7606.  
  7607. // end playing session
  7608. if (
  7609. allMyCells === 0 &&
  7610. lastCells > 0 &&
  7611. window.gameSettings.isPlaying
  7612. ) {
  7613. dead.call(this);
  7614. return;
  7615. }
  7616.  
  7617. // start playing session
  7618. if (
  7619. allMyCells > 0 &&
  7620. lastCells === 0 &&
  7621. !window.gameSettings.isPlaying
  7622. ) {
  7623. window.gameSettings.isPlaying = true;
  7624.  
  7625. const waitForStats = () => {
  7626. return new Promise((resolve) => {
  7627. const interval = setInterval(() => {
  7628. const stats = [
  7629. ...document.querySelectorAll(
  7630. 'div[style*="white-space: pre"]'
  7631. ),
  7632. ].find(
  7633. (d) =>
  7634. d.innerText.includes('players') &&
  7635. d.innerText.includes('load')
  7636. );
  7637. if (stats) {
  7638. clearInterval(interval);
  7639. resolve(stats);
  7640. }
  7641. }, 10);
  7642. });
  7643. };
  7644.  
  7645. waitForStats().then((stats) => {
  7646. const additionalStats = stats.cloneNode();
  7647. additionalStats.id = 'sigmod_stats';
  7648. additionalStats.textContent = '';
  7649. stats.insertAdjacentElement(
  7650. 'afterend',
  7651. additionalStats
  7652. );
  7653.  
  7654. let timerEl;
  7655. if (playTimer) {
  7656. timerEl = document.createElement('span');
  7657. timerEl.className = 'playTimer';
  7658. timerEl.style.display = 'block';
  7659. timerEl.textContent = '0m0s played';
  7660. additionalStats.appendChild(timerEl);
  7661. }
  7662.  
  7663. if (mouseTracker) {
  7664. const mouseEl = document.createElement('span');
  7665. mouseEl.className = 'mouseTracker';
  7666. mouseEl.style.display = 'block';
  7667. mouseEl.textContent = `X: ${this.mouseX || 0}; Y: ${
  7668. this.mouseY || 0
  7669. }`;
  7670. additionalStats.appendChild(mouseEl);
  7671. }
  7672.  
  7673. let sec = 0;
  7674. playingInterval = setInterval(() => {
  7675. sec++;
  7676. this.gameStats['time-played']++;
  7677. this.updateStat(
  7678. 'time-played',
  7679. this.gameStats['time-played']
  7680. );
  7681.  
  7682. if (playTimer) {
  7683. const m = Math.floor(sec / 60);
  7684. const s = sec % 60;
  7685. timerEl.textContent = `${m}m${s}s played`;
  7686. }
  7687. }, 1000);
  7688. });
  7689. }
  7690.  
  7691. lastCells = allMyCells;
  7692. });
  7693.  
  7694. function dead() {
  7695. window.gameSettings.isPlaying = false;
  7696. clearInterval(playingInterval);
  7697.  
  7698. const additionalStats = document.getElementById('sigmod_stats');
  7699. if (additionalStats) additionalStats.remove();
  7700.  
  7701. const score = parseFloat(byId('highest_mass').innerText);
  7702. const highest = this.gameStats['highest-mass'];
  7703.  
  7704. if (score && score > highest) {
  7705. this.gameStats['highest-mass'] = score;
  7706. this.updateStat('highest-mass', score);
  7707. }
  7708.  
  7709. this.gameStats['total-deaths']++;
  7710. this.updateStat('total-deaths', this.gameStats['total-deaths']);
  7711.  
  7712. this.gameStats['total-mass'] += score;
  7713. this.updateStat('total-mass', this.gameStats['total-mass']);
  7714.  
  7715. this.updateChart(this.gameStats);
  7716.  
  7717. if (this.lastOneStanding) {
  7718. client.send({ type: 'result', content: null });
  7719. this.playBtn.disabled = true;
  7720. }
  7721.  
  7722. if (
  7723. modSettings.settings.showChallenges &&
  7724. window.gameSettings.user
  7725. ) {
  7726. this.showChallenges();
  7727. }
  7728. }
  7729. },
  7730.  
  7731. defaultSession() {
  7732. let running = false;
  7733. let playingInterval;
  7734.  
  7735. let deadCheckInterval;
  7736.  
  7737. this.playBtn.addEventListener('click', () => {
  7738. // prevent spamming the play button (fast respawn especially)
  7739. if (window.gameSettings.isPlaying || running) return;
  7740.  
  7741. // it takes some seconds to set isPLaying true (because of the score check)
  7742. const checkInterval = setInterval(() => {
  7743. if (!window.gameSettings.isPlaying || running) return;
  7744.  
  7745. clearInterval(checkInterval);
  7746. startSession.call(this);
  7747. }, 100);
  7748. });
  7749.  
  7750. function startSession() {
  7751. const { playTimer, mouseTracker } = modSettings.settings;
  7752. let timerEl;
  7753.  
  7754. running = true;
  7755.  
  7756. const infoDiv = document.createElement('div');
  7757. infoDiv.classList.add('stats-additional');
  7758. Object.assign(infoDiv.style, {
  7759. left: '4px',
  7760. top: '12.2%',
  7761. fontSize: '14px',
  7762. });
  7763. document.body.append(infoDiv);
  7764.  
  7765. if (playTimer) {
  7766. timerEl = document.createElement('span');
  7767. timerEl.classList.add('playTimer');
  7768. timerEl.style.display = 'block';
  7769. timerEl.innerText = '0m0s played';
  7770. infoDiv.appendChild(timerEl);
  7771. }
  7772.  
  7773. if (mouseTracker) {
  7774. const mouseEl = document.createElement('span');
  7775. mouseEl.classList.add('mouseTracker');
  7776. mouseEl.style.display = 'block';
  7777. mouseEl.innerText = `X: ${this.mouseX || 0}; Y: ${
  7778. this.mouseY || 0
  7779. }`;
  7780. infoDiv.appendChild(mouseEl);
  7781. }
  7782.  
  7783. let sec = 0;
  7784. playingInterval = setInterval(() => {
  7785. sec++;
  7786. this.gameStats['time-played']++;
  7787.  
  7788. this.updateStat(
  7789. 'time-played',
  7790. this.gameStats['time-played']
  7791. );
  7792.  
  7793. if (playTimer) this.updateTimeStat(timerEl, sec);
  7794. }, 1000);
  7795.  
  7796. setTimeout(() => checkDead.call(this), 2000);
  7797. }
  7798.  
  7799. function checkDead() {
  7800. deadCheckInterval = setInterval(() => {
  7801. if (!window.gameSettings.isPlaying && running) {
  7802. clearInterval(playingInterval);
  7803. clearInterval(deadCheckInterval);
  7804.  
  7805. running = false;
  7806.  
  7807. const additionalStats =
  7808. document.querySelector('.stats-additional');
  7809. if (additionalStats) additionalStats.remove();
  7810.  
  7811. const score = parseFloat(
  7812. byId('highest_mass').innerText
  7813. );
  7814. const highest = this.gameStats['highest-mass'];
  7815.  
  7816. if (score > highest) {
  7817. this.gameStats['highest-mass'] = score;
  7818. this.updateStat('highest-mass', score);
  7819. }
  7820.  
  7821. this.gameStats['total-deaths']++;
  7822. this.updateStat(
  7823. 'total-deaths',
  7824. this.gameStats['total-deaths']
  7825. );
  7826.  
  7827. this.gameStats['total-mass'] += score;
  7828. this.updateStat(
  7829. 'total-mass',
  7830. this.gameStats['total-mass']
  7831. );
  7832.  
  7833. this.updateChart(this.gameStats);
  7834.  
  7835. if (this.lastOneStanding) {
  7836. client.send({ type: 'result', content: null });
  7837. this.playBtn.disabled = true;
  7838. }
  7839.  
  7840. if (
  7841. modSettings.settings.showChallenges &&
  7842. window.gameSettings.user
  7843. ) {
  7844. this.showChallenges();
  7845. }
  7846. }
  7847. }, 100);
  7848. }
  7849. },
  7850.  
  7851. updateTimeStat(el, seconds) {
  7852. const minutes = Math.floor(seconds / 60);
  7853. const remainingSeconds = seconds % 60;
  7854. const timeString = `${minutes}m${remainingSeconds}s`;
  7855.  
  7856. el.innerText = `${timeString} played`;
  7857. },
  7858.  
  7859. async showChallenges() {
  7860. const challengeData = await (
  7861. await fetch(
  7862. `https://sigmally.com/api/user/challenge/${window.gameSettings.user.email}`
  7863. )
  7864. ).json();
  7865.  
  7866. if (challengeData.status !== 'success') return;
  7867.  
  7868. const shopLocales = window.shopLocales;
  7869. let challengesCompleted = 0;
  7870.  
  7871. const allChallenges = challengeData.data;
  7872. allChallenges.forEach(({ status }) => {
  7873. if (status) challengesCompleted++;
  7874. });
  7875.  
  7876. let challenges;
  7877. if (challengesCompleted === allChallenges.length) {
  7878. challenges = `<div class="challenge-row" style="justify-content: center;">All challenges completed.</div>`;
  7879. } else {
  7880. challenges = allChallenges
  7881. .filter(({ status }) => !status)
  7882. .map(({ task, best, status, ready, goal }) => {
  7883. const desc = shopLocales.challenge_tab.tasks[
  7884. task
  7885. ].replace('%n', task === 'alive' ? goal / 60 : goal);
  7886. const btn = ready
  7887. ? `<button class="challenge-collect-secondary" onclick="this.challenge('${task}', ${status})">${shopLocales.challenge_tab.collect}</button>`
  7888. : `<div class="challenge-best-secondary">${
  7889. shopLocales.challenge_tab.result
  7890. }${Math.round(best)}${
  7891. task === 'alive' ? 's' : ''
  7892. }</div>`;
  7893. return `
  7894. <div class="challenge-row">
  7895. <div class="challenge-desc">${desc}</div>
  7896. ${btn}
  7897. </div>`;
  7898. })
  7899. .join('');
  7900. }
  7901.  
  7902. const modal = document.createElement('div');
  7903. modal.classList.add('challenges_deathscreen');
  7904. modal.innerHTML = `
  7905. <span class="challenges-title">Daily challenges</span>
  7906. <div class="challenges-col">${challenges}</div>
  7907. <span class="centerXY new-challenges">New challenges in 0h 0m 0s</span>
  7908. `;
  7909.  
  7910. const toggleColor = (element, background, text) => {
  7911. let image = `url("${background}")`;
  7912. if (background.includes('http')) {
  7913. element.style.background = image;
  7914. element.style.backgroundPosition = 'center';
  7915. element.style.backgroundSize = 'cover';
  7916. element.style.backgroundRepeat = 'no-repeat';
  7917. } else {
  7918. element.style.background = background;
  7919. element.style.backgroundRepeat = 'no-repeat';
  7920. }
  7921. element.style.color = text;
  7922. };
  7923.  
  7924. if (modSettings.themes.current !== 'Dark') {
  7925. let selectedTheme;
  7926. selectedTheme =
  7927. window.themes.defaults.find(
  7928. (theme) => theme.name === modSettings.themes.current
  7929. ) ||
  7930. modSettings.themes.custom.find(
  7931. (theme) => theme.name === modSettings.themes.current
  7932. ) ||
  7933. null;
  7934. if (!selectedTheme) {
  7935. selectedTheme =
  7936. window.themes.orderly.find(
  7937. (theme) => theme.name === modSettings.themes.current
  7938. ) ||
  7939. modSettings.themes.custom.find(
  7940. (theme) => theme.name === modSettings.themes.current
  7941. );
  7942. }
  7943.  
  7944. if (selectedTheme) {
  7945. toggleColor(
  7946. modal,
  7947. selectedTheme.background,
  7948. selectedTheme.text
  7949. );
  7950. }
  7951. }
  7952.  
  7953. document
  7954. .querySelector('.menu-wrapper--stats-mode')
  7955. .insertAdjacentElement('afterbegin', modal);
  7956.  
  7957. if (challengesCompleted < allChallenges.length) {
  7958. document
  7959. .querySelectorAll('.challenge-collect-secondary')
  7960. .forEach((btn) => {
  7961. btn.addEventListener('click', () => {
  7962. const parentChallengeRow =
  7963. btn.closest('.challenge-row');
  7964. if (parentChallengeRow) {
  7965. setTimeout(() => {
  7966. parentChallengeRow.remove();
  7967. }, 500);
  7968. }
  7969. });
  7970. });
  7971. }
  7972.  
  7973. this.createDayTimer();
  7974. },
  7975.  
  7976. timeToString(timeLeft) {
  7977. const string = new Date(timeLeft).toISOString().slice(11, 19);
  7978. const [hours, minutes, seconds] = string.split(':');
  7979.  
  7980. return `${hours}h ${minutes}m ${seconds}s`;
  7981. },
  7982. toISODate: (date) => {
  7983. const withTime = date ? new Date(date) : new Date();
  7984. const [withoutTime] = withTime.toISOString().split('T');
  7985. return withoutTime;
  7986. },
  7987. createDayTimer() {
  7988. const oneDay = 1000 * 60 * 60 * 24;
  7989. const getTime = () => {
  7990. const today = this.toISODate();
  7991. const from = new Date(today).getTime();
  7992. const to = from + oneDay;
  7993.  
  7994. const distance = to - Date.now();
  7995. const time = this.timeToString(distance);
  7996. return time;
  7997. };
  7998.  
  7999. const children = document.querySelector('.new-challenges');
  8000. if (children) {
  8001. children.innerHTML = `New challenges in ${getTime()}`;
  8002. }
  8003.  
  8004. this.dayTimer = setInterval(() => {
  8005. const today = this.toISODate();
  8006. const from = new Date(today).getTime();
  8007. const to = from + oneDay;
  8008.  
  8009. const distance = to - Date.now();
  8010. const time = this.timeToString(distance);
  8011.  
  8012. const children = document.querySelector('.new-challenges');
  8013. if (!children || distance < 1000 || !isDeadUI()) {
  8014. clearInterval(this.dayTimer);
  8015. return;
  8016. }
  8017. children.innerHTML = `New challenges in ${getTime()}`;
  8018. }, 1000);
  8019. },
  8020.  
  8021. macros() {
  8022. let that = this;
  8023. const KEY_SPLIT = this.splitKey;
  8024. let ff = null;
  8025. let keydown = false;
  8026. let open = false;
  8027. const canvas = byId('canvas');
  8028. const mod_menu = document.querySelector('.mod_menu');
  8029.  
  8030. const freezeType = byId('freezeType');
  8031. let freezeKeyPressed = false;
  8032. let freezeMouseClicked = false;
  8033. let freezeOverlay = null;
  8034.  
  8035. let vOverlay = null;
  8036. let vLocked = false;
  8037. let activeVLine = false;
  8038.  
  8039. let fixedOverlay = null;
  8040. let fixedLocked = false;
  8041. let activeFixedLine = false;
  8042.  
  8043. /* intervals */
  8044.  
  8045. // Respawn interval
  8046. setInterval(() => {
  8047. if (
  8048. modSettings.settings.autoRespawn &&
  8049. this.respawnTime &&
  8050. Date.now() - this.respawnTime >= this.respawnCooldown
  8051. ) {
  8052. this.respawn();
  8053. }
  8054. });
  8055.  
  8056. // mouse fast feed interval
  8057. setInterval(() => {
  8058. if (isDeadUI() || !menuClosed() || !this.mouseDown) return;
  8059. keypress('w', 'KeyW');
  8060. }, 50);
  8061.  
  8062. async function split(times) {
  8063. if (times > 0) {
  8064. window.dispatchEvent(
  8065. new KeyboardEvent('keydown', KEY_SPLIT)
  8066. );
  8067. window.dispatchEvent(new KeyboardEvent('keyup', KEY_SPLIT));
  8068. split(times - 1);
  8069. }
  8070. }
  8071.  
  8072. async function selfTrick() {
  8073. let i = 4;
  8074.  
  8075. while (i--) {
  8076. split(1);
  8077. await wait(20);
  8078. }
  8079. }
  8080.  
  8081. async function doubleTrick() {
  8082. let i = 2;
  8083.  
  8084. while (i--) {
  8085. split(1);
  8086. await wait(20);
  8087. }
  8088. }
  8089.  
  8090. async function instantSplit() {
  8091. await wait(300);
  8092.  
  8093. if (
  8094. modSettings.macros.keys.line.instantSplit &&
  8095. modSettings.macros.keys.line.instantSplit > 0
  8096. ) {
  8097. split(modSettings.macros.keys.line.instantSplit);
  8098. }
  8099. }
  8100.  
  8101. async function vLine() {
  8102. if (!activeVLine) return;
  8103. const x = playerPosition.x;
  8104. const y = playerPosition.y;
  8105.  
  8106. const offsetUpX = x;
  8107. const offsetUpY = y - 100;
  8108. const offsetDownX = x;
  8109. const offsetDownY = y;
  8110.  
  8111. freezepos = false;
  8112. window.sendMouseMove(offsetUpX, offsetUpY);
  8113. freezepos = true;
  8114.  
  8115. await wait(50);
  8116.  
  8117. freezepos = false;
  8118. window.sendMouseMove(offsetDownX, offsetDownY);
  8119. freezepos = true;
  8120. }
  8121.  
  8122. async function toggleHorizontal(mouse = false) {
  8123. if (!freezeKeyPressed) {
  8124. if (activeVLine || activeFixedLine) return;
  8125.  
  8126. window.sendMouseMove(playerPosition.x, playerPosition.y);
  8127. freezepos = true;
  8128.  
  8129. instantSplit();
  8130.  
  8131. freezeOverlay = document.createElement('div');
  8132. freezeOverlay.innerHTML = `
  8133. <span style="position: absolute; bottom: 50px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 26px; user-select: none;">Movement Stopped</span>
  8134. `;
  8135. freezeOverlay.style =
  8136. 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;';
  8137.  
  8138. if (
  8139. mouse &&
  8140. (modSettings.macros.mouse.left === 'freeze' ||
  8141. modSettings.macros.mouse.right === 'freeze')
  8142. ) {
  8143. freezeOverlay.addEventListener('mousedown', (e) => {
  8144. if (
  8145. e.button === 0 &&
  8146. modSettings.macros.mouse.left === 'freeze'
  8147. ) {
  8148. // Left mouse button (1)
  8149. handleFreezeEvent();
  8150. }
  8151. if (
  8152. e.button === 2 &&
  8153. modSettings.macros.mouse.right === 'freeze'
  8154. ) {
  8155. // Right mouse button (2)
  8156. handleFreezeEvent();
  8157. }
  8158. });
  8159.  
  8160. if (modSettings.macros.mouse.right === 'freeze') {
  8161. freezeOverlay.addEventListener(
  8162. 'contextmenu',
  8163. (e) => {
  8164. e.preventDefault();
  8165. }
  8166. );
  8167. }
  8168. }
  8169.  
  8170. function handleFreezeEvent() {
  8171. if (freezeOverlay != null) freezeOverlay.remove();
  8172. freezeOverlay = null;
  8173. freezeKeyPressed = false;
  8174. }
  8175.  
  8176. document
  8177. .querySelector('.body__inner')
  8178. .append(freezeOverlay);
  8179.  
  8180. freezeKeyPressed = true;
  8181. } else {
  8182. if (freezeOverlay != null) freezeOverlay.remove();
  8183. freezeOverlay = null;
  8184. freezeKeyPressed = false;
  8185.  
  8186. freezepos = false;
  8187. }
  8188. }
  8189.  
  8190. async function toggleVertical() {
  8191. if (!activeVLine) {
  8192. if (freezeKeyPressed || activeFixedLine) return;
  8193.  
  8194. window.sendMouseMove(playerPosition.x, playerPosition.y);
  8195. freezepos = true;
  8196.  
  8197. instantSplit();
  8198.  
  8199. vOverlay = document.createElement('div');
  8200. vOverlay.style = 'pointer-events: none;';
  8201. vOverlay.innerHTML = `
  8202. <span style="position: absolute; bottom: 50px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 26px; user-select: none;">Vertical locked</span>
  8203. `;
  8204. vOverlay.style =
  8205. 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;';
  8206.  
  8207. document.querySelector('.body__inner').append(vOverlay);
  8208.  
  8209. activeVLine = true;
  8210. } else {
  8211. activeVLine = false;
  8212. freezepos = false;
  8213. if (vOverlay) vOverlay.remove();
  8214. vOverlay = null;
  8215. }
  8216. }
  8217.  
  8218. async function toggleFixed() {
  8219. if (!activeFixedLine) {
  8220. if (freezeKeyPressed || activeVLine) return;
  8221.  
  8222. window.sendMouseMove(playerPosition.x, playerPosition.y);
  8223.  
  8224. freezepos = true;
  8225.  
  8226. instantSplit();
  8227.  
  8228. fixedOverlay = document.createElement('div');
  8229. fixedOverlay.style = 'pointer-events: none;';
  8230. fixedOverlay.innerHTML = `
  8231. <span style="position: absolute; bottom: 50px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 26px; user-select: none;">Mouse locked</span>
  8232. `;
  8233. fixedOverlay.style =
  8234. 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;';
  8235.  
  8236. document.querySelector('.body__inner').append(fixedOverlay);
  8237.  
  8238. activeFixedLine = true;
  8239. } else {
  8240. activeFixedLine = false;
  8241. freezepos = false;
  8242. if (fixedOverlay) fixedOverlay.remove();
  8243. fixedOverlay = null;
  8244. }
  8245. }
  8246.  
  8247. function sendLocation() {
  8248. if (!playerPosition.x || !playerPosition.y) return;
  8249.  
  8250. let field = '';
  8251. const coordinates = getCoordinates(mods.border);
  8252.  
  8253. for (const label in coordinates) {
  8254. const { min, max } = coordinates[label];
  8255.  
  8256. if (
  8257. playerPosition.x >= min.x &&
  8258. playerPosition.x <= max.x &&
  8259. playerPosition.y >= min.y &&
  8260. playerPosition.y <= max.y
  8261. ) {
  8262. field = label;
  8263. break;
  8264. }
  8265. }
  8266.  
  8267. const locationText = modSettings.chat.locationText || field;
  8268. const message = locationText.replace('{pos}', field);
  8269. window.sendChat(message);
  8270. }
  8271.  
  8272. function toggleSettings(setting) {
  8273. const settingElement = document.querySelector(
  8274. `input#${setting}`
  8275. );
  8276. if (settingElement) {
  8277. settingElement.click();
  8278. } else {
  8279. console.error(`Setting "${setting}" not found`);
  8280. }
  8281. }
  8282.  
  8283. document.addEventListener('keyup', (e) => {
  8284. const key = e.key.toLowerCase();
  8285. if (key == modSettings.macros.keys.rapidFeed && keydown) {
  8286. clearInterval(ff);
  8287. keydown = false;
  8288. }
  8289. });
  8290. document.addEventListener('keydown', (e) => {
  8291. // prevent disconnecting & using macros on input fields
  8292. if (
  8293. document.activeElement.tagName === 'INPUT' ||
  8294. document.activeElement.tagName === 'TEXTAREA'
  8295. ) {
  8296. e.stopPropagation();
  8297. return;
  8298. }
  8299. const key = e.key.toLowerCase();
  8300.  
  8301. if (key === 'p') {
  8302. e.stopPropagation();
  8303. }
  8304. if (key === 'tab' && !window.screenTop && !window.screenY) {
  8305. e.preventDefault();
  8306. }
  8307.  
  8308. if (key === modSettings.macros.keys.rapidFeed) {
  8309. e.stopPropagation(); // block actual feeding key
  8310. if (!keydown) {
  8311. keydown = true;
  8312. ff = setInterval(
  8313. () => keypress('w', 'KeyW'),
  8314. modSettings.macros.feedSpeed
  8315. );
  8316. }
  8317. }
  8318. // vertical linesplit
  8319. if (
  8320. activeVLine &&
  8321. (key === ' ' ||
  8322. key === modSettings.macros.keys.splits.double ||
  8323. key === modSettings.macros.keys.splits.triple ||
  8324. key === modSettings.macros.keys.splits.quad)
  8325. ) {
  8326. vLine();
  8327. }
  8328.  
  8329. handleKeydown(key);
  8330. });
  8331.  
  8332. async function handleKeydown(key) {
  8333. switch (key) {
  8334. case modSettings.macros.keys.toggle.menu: {
  8335. if (!open) {
  8336. mod_menu.style.display = 'flex';
  8337. setTimeout(() => {
  8338. mod_menu.style.opacity = 1;
  8339. }, 10);
  8340. open = true;
  8341. } else {
  8342. mod_menu.style.opacity = 0;
  8343. setTimeout(() => {
  8344. mod_menu.style.display = 'none';
  8345. }, 300);
  8346. open = false;
  8347. }
  8348. break;
  8349. }
  8350.  
  8351. case modSettings.macros.keys.splits.double:
  8352. split(2);
  8353. break;
  8354.  
  8355. case modSettings.macros.keys.splits.triple:
  8356. split(3);
  8357. break;
  8358.  
  8359. case modSettings.macros.keys.splits.quad:
  8360. split(4);
  8361. break;
  8362.  
  8363. case modSettings.macros.keys.splits.selfTrick:
  8364. selfTrick();
  8365. break;
  8366.  
  8367. case modSettings.macros.keys.splits.doubleTrick:
  8368. doubleTrick();
  8369. break;
  8370.  
  8371. case modSettings.macros.keys.line.horizontal:
  8372. if (menuClosed()) toggleHorizontal();
  8373. break;
  8374.  
  8375. case modSettings.macros.keys.line.vertical:
  8376. if (menuClosed()) toggleVertical();
  8377. break;
  8378.  
  8379. case modSettings.macros.keys.line.fixed:
  8380. if (menuClosed()) toggleFixed();
  8381. break;
  8382.  
  8383. case modSettings.macros.keys.location:
  8384. sendLocation();
  8385. break;
  8386.  
  8387. case modSettings.macros.keys.toggle.chat:
  8388. mods.toggleChat();
  8389. break;
  8390.  
  8391. case modSettings.macros.keys.toggle.names:
  8392. toggleSettings('showNames');
  8393. break;
  8394.  
  8395. case modSettings.macros.keys.toggle.skins:
  8396. toggleSettings('showSkins');
  8397. break;
  8398.  
  8399. case modSettings.macros.keys.toggle.autoRespawn:
  8400. toggleSettings('autoRespawn');
  8401. break;
  8402.  
  8403. case modSettings.macros.keys.respawn:
  8404. mods.fastRespawn();
  8405. break;
  8406.  
  8407. case modSettings.macros.keys.saveImage:
  8408. await mods.saveImage();
  8409. break;
  8410. }
  8411. }
  8412.  
  8413. canvas.addEventListener('mousedown', (e) => {
  8414. const {
  8415. macros: { mouse },
  8416. } = modSettings;
  8417.  
  8418. if (e.button === 0) {
  8419. // Left mouse button (0)
  8420. if (mouse.left === 'fastfeed') {
  8421. if (
  8422. document.activeElement.tagName === 'INPUT' ||
  8423. document.activeElement.tagName === 'TEXTAREA'
  8424. )
  8425. return;
  8426. this.mouseDown = true;
  8427. } else if (mouse.left === 'split') {
  8428. split(1);
  8429. } else if (mouse.left === 'split2') {
  8430. split(2);
  8431. } else if (mouse.left === 'split3') {
  8432. split(3);
  8433. } else if (mouse.left === 'split4') {
  8434. split(4);
  8435. } else if (mouse.left === 'freeze') {
  8436. toggleHorizontal(true);
  8437. } else if (mouse.left === 'dTrick') {
  8438. doubleTrick();
  8439. } else if (mouse.left === 'sTrick') {
  8440. selfTrick();
  8441. }
  8442. } else if (e.button === 2) {
  8443. // Right mouse button (2)
  8444. e.preventDefault();
  8445. if (mouse.right === 'fastfeed') {
  8446. if (
  8447. document.activeElement.tagName === 'INPUT' ||
  8448. document.activeElement.tagName === 'TEXTAREA'
  8449. )
  8450. return;
  8451. this.mouseDown = true;
  8452. } else if (mouse.right === 'split') {
  8453. split(1);
  8454. } else if (mouse.right === 'split2') {
  8455. split(2);
  8456. } else if (mouse.right === 'split3') {
  8457. split(3);
  8458. } else if (mouse.right === 'split4') {
  8459. split(4);
  8460. } else if (mouse.right === 'freeze') {
  8461. toggleHorizontal(true);
  8462. } else if (mouse.right === 'dTrick') {
  8463. doubleTrick();
  8464. } else if (mouse.right === 'sTrick') {
  8465. selfTrick();
  8466. }
  8467. }
  8468. });
  8469.  
  8470. canvas.addEventListener('contextmenu', (e) => {
  8471. e.preventDefault();
  8472. });
  8473.  
  8474. canvas.addEventListener('mouseup', () => {
  8475. if (modSettings.macros.mouse.left === 'fastfeed') {
  8476. this.mouseDown = false;
  8477. } else if (modSettings.macros.mouse.right === 'fastfeed') {
  8478. this.mouseDown = false;
  8479. }
  8480. });
  8481.  
  8482. const macroSelectHandler = (macroSelect, key) => {
  8483. const {
  8484. macros: { mouse },
  8485. } = modSettings;
  8486. macroSelect.value = modSettings[key] || 'none';
  8487.  
  8488. macroSelect.addEventListener('change', () => {
  8489. const selectedOption = macroSelect.value;
  8490.  
  8491. const optionActions = {
  8492. none: () => {
  8493. mouse[key] = null;
  8494. },
  8495. fastfeed: () => {
  8496. mouse[key] = 'fastfeed';
  8497. },
  8498. split: () => {
  8499. mouse[key] = 'split';
  8500. },
  8501. split2: () => {
  8502. mouse[key] = 'split2';
  8503. },
  8504. split3: () => {
  8505. mouse[key] = 'split3';
  8506. },
  8507. split4: () => {
  8508. mouse[key] = 'split4';
  8509. },
  8510. freeze: () => {
  8511. mouse[key] = 'freeze';
  8512. },
  8513. dTrick: () => {
  8514. mouse[key] = 'dTrick';
  8515. },
  8516. sTrick: () => {
  8517. mouse[key] = 'sTrick';
  8518. },
  8519. };
  8520.  
  8521. if (optionActions[selectedOption]) {
  8522. optionActions[selectedOption]();
  8523. updateStorage();
  8524. }
  8525. });
  8526. };
  8527.  
  8528. const m1_macroSelect = byId('m1_macroSelect');
  8529. const m2_macroSelect = byId('m2_macroSelect');
  8530.  
  8531. macroSelectHandler(m1_macroSelect, 'left');
  8532. macroSelectHandler(m2_macroSelect, 'right');
  8533.  
  8534. const instantSplitAmount = byId('instant-split-amount');
  8535. const instantSplitStatus = byId('toggle-instant-split');
  8536.  
  8537. instantSplitStatus.checked =
  8538. modSettings.macros.keys.line.instantSplit > 0;
  8539. instantSplitAmount.disabled = !instantSplitStatus.checked;
  8540. instantSplitAmount.value =
  8541. modSettings.macros.keys.line.instantSplit.toString();
  8542.  
  8543. instantSplitStatus.addEventListener('change', () => {
  8544. if (instantSplitStatus.checked) {
  8545. modSettings.macros.keys.line.instantSplit =
  8546. Number(instantSplitAmount.value) || 0;
  8547. instantSplitAmount.disabled = false;
  8548. } else {
  8549. modSettings.macros.keys.line.instantSplit = 0;
  8550. instantSplitAmount.disabled = true;
  8551. }
  8552.  
  8553. updateStorage();
  8554. });
  8555.  
  8556. instantSplitAmount.addEventListener('input', (e) => {
  8557. modSettings.macros.keys.line.instantSplit =
  8558. Number(instantSplitAmount.value) || 0;
  8559. updateStorage();
  8560. });
  8561. },
  8562.  
  8563. setInputActions() {
  8564. const numModInputs = 17;
  8565. const macroInputs = Array.from(
  8566. { length: numModInputs },
  8567. (_, i) => `modinput${i + 1}`
  8568. );
  8569.  
  8570. macroInputs.forEach((modkey) => {
  8571. const modInput = byId(modkey);
  8572.  
  8573. document.addEventListener('keydown', (event) => {
  8574. if (document.activeElement !== modInput) return;
  8575.  
  8576. if (event.key === 'Backspace') {
  8577. modInput.value = '';
  8578. updateModSettings(modInput.name, '');
  8579. return;
  8580. }
  8581.  
  8582. const newValue = event.key.toLowerCase();
  8583. modInput.value = newValue;
  8584.  
  8585. const duplicateInput = macroInputs
  8586. .map((key) => byId(key))
  8587. .find(
  8588. (input) =>
  8589. input &&
  8590. input !== modInput &&
  8591. input.value === newValue
  8592. );
  8593.  
  8594. if (duplicateInput) {
  8595. const overlay = document.createElement('div');
  8596. overlay.classList.add('mod_overlay');
  8597. document.body.append(overlay);
  8598.  
  8599. const changeDiv = document.createElement('div');
  8600. changeDiv.classList.add('modAlert');
  8601. changeDiv.style.zIndex = '99999999';
  8602. changeDiv.style.top = '50%';
  8603. changeDiv.innerHTML = `
  8604. <strong>Duplicate keybinding detected!</strong>
  8605. <p>The key <code class="modCode">${newValue}</code> is already assigned to <code class="modCode">${duplicateInput.getAttribute(
  8606. 'data-label'
  8607. )}</code>.</p>
  8608. <p>Do you want to reassign this key to <code class="modCode">${modInput.getAttribute(
  8609. 'data-label'
  8610. )}</code> and remove it from <code class="modCode">${duplicateInput.getAttribute(
  8611. 'data-label'
  8612. )}</code>?</p>
  8613. <div class="flex g-5" style="align-self: end;">
  8614. <button class="modButton" id="cancelBtn">Cancel</button>
  8615. <button class="modButton" id="confirmBtn">Yes, Reassign</button>
  8616. </div>
  8617. `;
  8618. document.body.append(changeDiv);
  8619.  
  8620. const removeDivs = () => {
  8621. overlay.remove();
  8622. changeDiv.remove();
  8623. };
  8624.  
  8625. const cancelBtn = changeDiv.querySelector('#cancelBtn');
  8626. const confirmBtn =
  8627. changeDiv.querySelector('#confirmBtn');
  8628.  
  8629. cancelBtn.onclick = () => {
  8630. modInput.value = '';
  8631. updateModSettings(modInput.name, '');
  8632. removeDivs();
  8633. };
  8634.  
  8635. confirmBtn.onclick = () => {
  8636. updateModSettings(duplicateInput.name, '');
  8637. updateModSettings(modInput.name, newValue);
  8638. duplicateInput.value = '';
  8639. removeDivs();
  8640. };
  8641.  
  8642. return;
  8643. }
  8644. modInput.value = newValue;
  8645. updateModSettings(modInput.name, newValue);
  8646. });
  8647. });
  8648.  
  8649. function updateModSettings(propertyName, value) {
  8650. const properties = propertyName.split('.');
  8651. let settings = modSettings.macros.keys;
  8652.  
  8653. properties
  8654. .slice(0, -1)
  8655. .forEach((prop) => (settings = settings[prop]));
  8656. settings[properties.pop()] = value;
  8657.  
  8658. updateStorage();
  8659. }
  8660.  
  8661. const modNumberInput = document.querySelector('.modNumberInput');
  8662.  
  8663. modNumberInput.addEventListener('keydown', (event) => {
  8664. if (
  8665. !['Backspace', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(
  8666. event.key
  8667. ) &&
  8668. !/^[0-9]$/.test(event.key)
  8669. ) {
  8670. event.preventDefault();
  8671. }
  8672. });
  8673. },
  8674.  
  8675. openDB() {
  8676. if (this.dbCache) return Promise.resolve(this.dbCache);
  8677.  
  8678. return new Promise((resolve, reject) => {
  8679. const request = indexedDB.open('imageGalleryDB', 1);
  8680.  
  8681. request.onupgradeneeded = (event) => {
  8682. const db = event.target.result;
  8683. if (!db.objectStoreNames.contains('images')) {
  8684. db.createObjectStore('images', {
  8685. keyPath: 'timestamp',
  8686. });
  8687. }
  8688. };
  8689.  
  8690. request.onsuccess = () => {
  8691. this.dbCache = request.result;
  8692. resolve(this.dbCache);
  8693. };
  8694.  
  8695. request.onerror = (event) => reject(event.target.error);
  8696. });
  8697. },
  8698.  
  8699. async saveImage() {
  8700. const canvas = window.sigfix ? byId('sf-canvas') : byId('canvas');
  8701.  
  8702. requestAnimationFrame(async () => {
  8703. const dataURL = canvas.toDataURL('image/png');
  8704.  
  8705. if (!dataURL) {
  8706. console.error(
  8707. 'Failed to capture the image. The canvas might be empty or the rendering is incomplete.'
  8708. );
  8709. return;
  8710. }
  8711.  
  8712. const timestamp = Date.now();
  8713.  
  8714. if (!indexedDB)
  8715. return alert(
  8716. 'Your browser does not support indexedDB. Please update your browser.'
  8717. );
  8718.  
  8719. try {
  8720. const db = await this.openDB();
  8721. const transaction = db.transaction('images', 'readwrite');
  8722. const store = transaction.objectStore('images');
  8723. store.put({ timestamp, dataURL });
  8724.  
  8725. await new Promise((resolve, reject) => {
  8726. transaction.oncomplete = resolve;
  8727. transaction.onerror = (event) =>
  8728. reject(event.target.error);
  8729. });
  8730.  
  8731. this.addImageToGallery({ timestamp, dataURL });
  8732. } catch (error) {
  8733. console.error('Transaction error:', error);
  8734. }
  8735. });
  8736. },
  8737.  
  8738. addImageToGallery(image) {
  8739. const galleryElement = byId('image-gallery');
  8740.  
  8741. if (!galleryElement) return;
  8742.  
  8743. const placeholderURL =
  8744. 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
  8745.  
  8746. const imageHTML = `
  8747. <div class="image-container">
  8748. <img class="gallery-image lazy" data-src="${
  8749. image.dataURL
  8750. }" src="${placeholderURL}" data-image-id="${
  8751. image.timestamp
  8752. }" />
  8753. <div class="justify-sb">
  8754. <span class="modDescText">${prettyTime.fullDate(image.timestamp, true)}</span>
  8755. <div class="centerXY g-5">
  8756. <button type="button" class="download_btn operation_btn" data-image-id="${
  8757. image.timestamp
  8758. }"></button>
  8759. <button type="button" class="delete_btn operation_btn" data-image-id="${
  8760. image.timestamp
  8761. }"></button>
  8762. </div>
  8763. </div>
  8764. </div>
  8765. `;
  8766.  
  8767. galleryElement.insertAdjacentHTML('afterbegin', imageHTML);
  8768.  
  8769. const lazyImages = document.querySelectorAll('.lazy');
  8770. const imageObserver = new IntersectionObserver(
  8771. (entries, observer) => {
  8772. entries.forEach((entry) => {
  8773. if (entry.isIntersecting) {
  8774. const image = entry.target;
  8775. image.src = image.getAttribute('data-src');
  8776. image.classList.remove('lazy');
  8777. observer.unobserve(image);
  8778. }
  8779. });
  8780. }
  8781. );
  8782.  
  8783. lazyImages.forEach((image) => {
  8784. imageObserver.observe(image);
  8785. });
  8786.  
  8787. this.attachEventListeners([image]);
  8788. },
  8789. async updateGallery() {
  8790. try {
  8791. const db = await this.openDB();
  8792. const transaction = db.transaction('images', 'readonly');
  8793. const store = transaction.objectStore('images');
  8794. const request = store.getAll();
  8795.  
  8796. request.onsuccess = () => {
  8797. const gallery = request.result;
  8798. const galleryElement = byId('image-gallery');
  8799. const downloadAll = byId('gallery-download');
  8800. const deleteAll = byId('gallery-delete');
  8801.  
  8802. if (!galleryElement) return;
  8803.  
  8804. if (gallery.length === 0) {
  8805. galleryElement.innerHTML = `<span>No images saved yet.</span>`;
  8806.  
  8807. downloadAll.style.display = 'none';
  8808. deleteAll.style.display = 'none';
  8809. return;
  8810. }
  8811.  
  8812. downloadAll.style.display = 'block';
  8813. deleteAll.style.display = 'block';
  8814.  
  8815. downloadAll.addEventListener('click', async () => {
  8816. if (gallery.length === 0) return;
  8817. const { JSZip } = window;
  8818. const zip = JSZip();
  8819.  
  8820. gallery.forEach((item) => {
  8821. const imageData = item.dataURL.split(',')[1];
  8822. const imgExtension = item.dataURL
  8823. .split(';')[0]
  8824. .split('/')[1];
  8825. zip.file(
  8826. `${item.timestamp}.${imgExtension}`,
  8827. imageData,
  8828. {
  8829. base64: true,
  8830. }
  8831. );
  8832. });
  8833.  
  8834. zip.generateAsync({ type: 'blob' })
  8835. .then((zipContent) => {
  8836. const a = document.createElement('a');
  8837. a.href = URL.createObjectURL(zipContent);
  8838. a.download = 'sigmally_gallery.zip';
  8839. a.click();
  8840. })
  8841. .catch((error) => {
  8842. console.error(
  8843. 'Error generating ZIP file:',
  8844. error
  8845. );
  8846. });
  8847. });
  8848.  
  8849. deleteAll.addEventListener('click', () => {
  8850. const confirmDelete = confirm(
  8851. 'Are you sure you want to delete all images? This action cannot be undone.'
  8852. );
  8853. if (!confirmDelete) return;
  8854.  
  8855. const deleteTransaction = db.transaction(
  8856. 'images',
  8857. 'readwrite'
  8858. );
  8859. const deleteStore =
  8860. deleteTransaction.objectStore('images');
  8861. deleteStore.clear();
  8862.  
  8863. deleteTransaction.oncomplete = () => {
  8864. galleryElement.innerHTML = `<span>No images saved yet.</span>`;
  8865. };
  8866.  
  8867. deleteTransaction.onerror = (error) => {
  8868. console.error('Error deleting images:', error);
  8869. };
  8870. });
  8871.  
  8872. gallery.sort((a, b) => b.timestamp - a.timestamp);
  8873.  
  8874. let galleryHTML = gallery
  8875. .map(
  8876. (item) => `
  8877. <div class="image-container">
  8878. <img class="gallery-image lazy" data-src="${
  8879. item.dataURL
  8880. }" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-image-id="${
  8881. item.timestamp
  8882. }" />
  8883. <div class="justify-sb">
  8884. <span class="modDescText">${prettyTime.fullDate(item.timestamp, true)}</span>
  8885. <div class="centerXY g-5">
  8886. <button type="button" class="download_btn operation_btn" data-image-id="${
  8887. item.timestamp
  8888. }"></button>
  8889. <button type="button" class="delete_btn operation_btn" data-image-id="${
  8890. item.timestamp
  8891. }"></button>
  8892. </div>
  8893. </div>
  8894. </div>
  8895. `
  8896. )
  8897. .join('');
  8898.  
  8899. galleryElement.innerHTML = galleryHTML;
  8900.  
  8901. const lazyImages = document.querySelectorAll('.lazy');
  8902. const imageObserver = new IntersectionObserver(
  8903. (entries, observer) => {
  8904. entries.forEach((entry) => {
  8905. if (entry.isIntersecting) {
  8906. const image = entry.target;
  8907. image.src = image.getAttribute('data-src');
  8908. image.classList.remove('lazy');
  8909. observer.unobserve(image);
  8910. }
  8911. });
  8912. }
  8913. );
  8914.  
  8915. lazyImages.forEach((image) => {
  8916. imageObserver.observe(image);
  8917. });
  8918.  
  8919. this.attachEventListeners(gallery);
  8920. };
  8921. } catch (error) {
  8922. console.error('Transaction error:', error);
  8923. }
  8924. },
  8925.  
  8926. attachEventListeners(gallery) {
  8927. const galleryElement = byId('image-gallery');
  8928.  
  8929. galleryElement.querySelectorAll('.gallery-image').forEach((img) => {
  8930. img.addEventListener('click', (event) => {
  8931. const dataURL = event.target.src;
  8932. this.openImage(dataURL);
  8933. });
  8934. });
  8935.  
  8936. galleryElement
  8937. .querySelectorAll('.download_btn')
  8938. .forEach((button) => {
  8939. button.addEventListener('click', () => {
  8940. const imageId = button.getAttribute('data-image-id');
  8941. const image = gallery.find(
  8942. (item) => item.timestamp === parseInt(imageId, 10)
  8943. );
  8944. if (image) {
  8945. const link = document.createElement('a');
  8946. link.href = image.dataURL;
  8947. link.download = `Sigmally ${this.sanitizeFilename(
  8948. prettyTime.fullDate(image.timestamp, true)
  8949. )}.png`;
  8950. link.click();
  8951. }
  8952. });
  8953. });
  8954.  
  8955. galleryElement.querySelectorAll('.delete_btn').forEach((button) => {
  8956. button.addEventListener('click', (e) => {
  8957. e.stopPropagation();
  8958. const imageId = button.getAttribute('data-image-id');
  8959. this.deleteImage(parseInt(imageId, 10));
  8960. });
  8961. });
  8962. },
  8963.  
  8964. sanitizeFilename: (filename) => filename.replace(/:/g, '_'),
  8965.  
  8966. openImage(dataURL) {
  8967. const blob = this.dataURLToBlob(dataURL);
  8968. const url = URL.createObjectURL(blob);
  8969. const imgWindow = window.open(url, '_blank');
  8970.  
  8971. if (imgWindow) {
  8972. setTimeout(() => URL.revokeObjectURL(url), 1000);
  8973. }
  8974. },
  8975.  
  8976. dataURLToBlob(dataURL) {
  8977. const [header, data] = dataURL.split(',');
  8978. const mime = header.match(/:(.*?);/)[1];
  8979. const binary = atob(data);
  8980. const array = new Uint8Array(binary.length);
  8981. for (let i = 0; i < binary.length; ++i) {
  8982. array[i] = binary.charCodeAt(i);
  8983. }
  8984. return new Blob([array], { type: mime });
  8985. },
  8986.  
  8987. async deleteImage(timestamp) {
  8988. try {
  8989. const db = await this.openDB();
  8990. const transaction = db.transaction('images', 'readwrite');
  8991. const store = transaction.objectStore('images');
  8992. store.delete(timestamp);
  8993.  
  8994. await new Promise((resolve, reject) => {
  8995. transaction.oncomplete = resolve;
  8996. transaction.onerror = (event) => reject(event.target.error);
  8997. });
  8998.  
  8999. this.updateGallery();
  9000. } catch (error) {
  9001. console.error('Transaction error:', error);
  9002. }
  9003. },
  9004.  
  9005. mainMenu() {
  9006. const menucontent = document.querySelector('.menu-center-content');
  9007. menucontent.style.margin = 'auto';
  9008.  
  9009. const discordlinks = document.createElement('div');
  9010. discordlinks.setAttribute('id', 'dclinkdiv');
  9011. discordlinks.innerHTML = `
  9012. <a href="https://discord.gg/${
  9013. window.tourneyServer ? 'ERtbMJCp8s' : '4j4Rc4dQTP'
  9014. }" target="_blank" class="dclinks">
  9015. <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  9016. <path d="M19.4566 5.35132C21.7154 8.83814 22.8309 12.7712 22.4139 17.299C22.4121 17.3182 22.4026 17.3358 22.3876 17.3473C20.6771 18.666 19.0199 19.4663 17.3859 19.9971C17.3732 20.0011 17.3596 20.0009 17.347 19.9964C17.3344 19.992 17.3234 19.9835 17.3156 19.9721C16.9382 19.4207 16.5952 18.8393 16.2947 18.2287C16.2774 18.1928 16.2932 18.1495 16.3287 18.1353C16.8734 17.9198 17.3914 17.6615 17.8896 17.3557C17.9289 17.3316 17.9314 17.2725 17.8951 17.2442C17.7894 17.1617 17.6846 17.0751 17.5844 16.9885C17.5656 16.9725 17.5404 16.9693 17.5191 16.9801C14.2844 18.5484 10.7409 18.5484 7.46792 16.9801C7.44667 16.9701 7.42142 16.9735 7.40317 16.9893C7.30317 17.0759 7.19817 17.1617 7.09342 17.2442C7.05717 17.2725 7.06017 17.3316 7.09967 17.3557C7.59792 17.6557 8.11592 17.9198 8.65991 18.1363C8.69517 18.1505 8.71192 18.1928 8.69442 18.2287C8.40042 18.8401 8.05742 19.4215 7.67292 19.9729C7.65617 19.9952 7.62867 20.0055 7.60267 19.9971C5.97642 19.4663 4.31917 18.666 2.60868 17.3473C2.59443 17.3358 2.58418 17.3174 2.58268 17.2982C2.23418 13.3817 2.94442 9.41613 5.53717 5.35053C5.54342 5.33977 5.55292 5.33137 5.56392 5.32638C6.83967 4.71165 8.20642 4.25939 9.63491 4.00111C9.66091 3.99691 9.68691 4.00951 9.70041 4.03365C9.87691 4.36176 10.0787 4.78252 10.2152 5.12637C11.7209 4.88489 13.2502 4.88489 14.7874 5.12637C14.9239 4.78987 15.1187 4.36176 15.2944 4.03365C15.3007 4.02167 15.3104 4.01208 15.3221 4.00623C15.3339 4.00039 15.3471 3.99859 15.3599 4.00111C16.7892 4.26018 18.1559 4.71244 19.4306 5.32638C19.4419 5.33137 19.4511 5.33977 19.4566 5.35132ZM10.9807 12.798C10.9964 11.6401 10.1924 10.6821 9.18316 10.6821C8.18217 10.6821 7.38592 11.6317 7.38592 12.798C7.38592 13.9639 8.19792 14.9136 9.18316 14.9136C10.1844 14.9136 10.9807 13.9639 10.9807 12.798ZM17.6261 12.798C17.6419 11.6401 16.8379 10.6821 15.8289 10.6821C14.8277 10.6821 14.0314 11.6317 14.0314 12.798C14.0314 13.9639 14.8434 14.9136 15.8289 14.9136C16.8379 14.9136 17.6261 13.9639 17.6261 12.798Z" fill="white"></path>
  9017. </svg>
  9018. <span>${
  9019. window.tourneyServer ? 'Tourney Server' : 'Sigmally'
  9020. }</span>
  9021. </a>
  9022. <a href="https://discord.gg/QyUhvUC8AD" target="_blank" class="dclinks">
  9023. <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  9024. <path d="M19.4566 5.35132C21.7154 8.83814 22.8309 12.7712 22.4139 17.299C22.4121 17.3182 22.4026 17.3358 22.3876 17.3473C20.6771 18.666 19.0199 19.4663 17.3859 19.9971C17.3732 20.0011 17.3596 20.0009 17.347 19.9964C17.3344 19.992 17.3234 19.9835 17.3156 19.9721C16.9382 19.4207 16.5952 18.8393 16.2947 18.2287C16.2774 18.1928 16.2932 18.1495 16.3287 18.1353C16.8734 17.9198 17.3914 17.6615 17.8896 17.3557C17.9289 17.3316 17.9314 17.2725 17.8951 17.2442C17.7894 17.1617 17.6846 17.0751 17.5844 16.9885C17.5656 16.9725 17.5404 16.9693 17.5191 16.9801C14.2844 18.5484 10.7409 18.5484 7.46792 16.9801C7.44667 16.9701 7.42142 16.9735 7.40317 16.9893C7.30317 17.0759 7.19817 17.1617 7.09342 17.2442C7.05717 17.2725 7.06017 17.3316 7.09967 17.3557C7.59792 17.6557 8.11592 17.9198 8.65991 18.1363C8.69517 18.1505 8.71192 18.1928 8.69442 18.2287C8.40042 18.8401 8.05742 19.4215 7.67292 19.9729C7.65617 19.9952 7.62867 20.0055 7.60267 19.9971C5.97642 19.4663 4.31917 18.666 2.60868 17.3473C2.59443 17.3358 2.58418 17.3174 2.58268 17.2982C2.23418 13.3817 2.94442 9.41613 5.53717 5.35053C5.54342 5.33977 5.55292 5.33137 5.56392 5.32638C6.83967 4.71165 8.20642 4.25939 9.63491 4.00111C9.66091 3.99691 9.68691 4.00951 9.70041 4.03365C9.87691 4.36176 10.0787 4.78252 10.2152 5.12637C11.7209 4.88489 13.2502 4.88489 14.7874 5.12637C14.9239 4.78987 15.1187 4.36176 15.2944 4.03365C15.3007 4.02167 15.3104 4.01208 15.3221 4.00623C15.3339 4.00039 15.3471 3.99859 15.3599 4.00111C16.7892 4.26018 18.1559 4.71244 19.4306 5.32638C19.4419 5.33137 19.4511 5.33977 19.4566 5.35132ZM10.9807 12.798C10.9964 11.6401 10.1924 10.6821 9.18316 10.6821C8.18217 10.6821 7.38592 11.6317 7.38592 12.798C7.38592 13.9639 8.19792 14.9136 9.18316 14.9136C10.1844 14.9136 10.9807 13.9639 10.9807 12.798ZM17.6261 12.798C17.6419 11.6401 16.8379 10.6821 15.8289 10.6821C14.8277 10.6821 14.0314 11.6317 14.0314 12.798C14.0314 13.9639 14.8434 14.9136 15.8289 14.9136C16.8379 14.9136 17.6261 13.9639 17.6261 12.798Z" fill="white"></path>
  9025. </svg>
  9026. <span>Sigmally Modz</span>
  9027. </a>
  9028. `;
  9029. byId('discord_link').remove();
  9030. byId('menu').appendChild(discordlinks);
  9031.  
  9032. let clansbtn = document.querySelector('#clans_and_settings button');
  9033. clansbtn.innerHTML = 'Clans';
  9034. document
  9035. .querySelectorAll('#clans_and_settings button')[1]
  9036. .removeAttribute('onclick');
  9037. },
  9038.  
  9039. respawn() {
  9040. const __line2 = byId('__line2');
  9041. const c = byId('continue_button');
  9042.  
  9043. if (__line2.classList.contains('line--hidden')) return;
  9044.  
  9045. this.respawnTime = null;
  9046.  
  9047. setTimeout(() => {
  9048. c.click();
  9049. this.playBtn.click();
  9050. }, 20);
  9051.  
  9052. this.respawnTime = Date.now();
  9053. },
  9054.  
  9055. fastRespawn() {
  9056. if (window.sigfix || (!window.sigfix && this.aboveRespawnLimit))
  9057. return;
  9058.  
  9059. window.sendChat(this.respawnCommand);
  9060. // const additionalStats = document.querySelector('.stats-additional');
  9061. // if (additionalStats) additionalStats.remove();
  9062.  
  9063. setTimeout(() => {
  9064. byId('continue_button').click();
  9065. this.playBtn.click();
  9066. setTimeout(() => {
  9067. this.playBtn.click();
  9068. }, 300);
  9069. }, 50);
  9070. },
  9071.  
  9072. clientPing() {
  9073. const pingElement = document.createElement('span');
  9074. pingElement.innerHTML = `Client Ping: 0ms`;
  9075. pingElement.id = 'clientPing';
  9076. pingElement.style = `
  9077. position: absolute;
  9078. right: 10px;
  9079. bottom: 5px;
  9080. color: #fff;
  9081. font-size: 1.8rem;
  9082. `;
  9083. document.querySelector('.mod_menu').append(pingElement);
  9084.  
  9085. this.ping.intervalId = setInterval(() => {
  9086. if (!client || client.ws?.readyState != 1) return;
  9087. this.ping.start = Date.now();
  9088.  
  9089. client.send({
  9090. type: 'get-ping',
  9091. });
  9092. }, 2000);
  9093. },
  9094.  
  9095. createMinimap() {
  9096. const dataContainer = document.createElement('div');
  9097. dataContainer.classList.add('minimapContainer');
  9098.  
  9099. const miniMap = document.createElement('canvas');
  9100. miniMap.width = 200;
  9101. miniMap.height = 200;
  9102. miniMap.classList.add('minimap');
  9103. this.canvas = miniMap;
  9104.  
  9105. let viewportScale = 1;
  9106.  
  9107. document.body.append(dataContainer);
  9108. dataContainer.append(miniMap);
  9109.  
  9110. function resizeMiniMap() {
  9111. viewportScale = Math.max(
  9112. window.innerWidth / 1920,
  9113. window.innerHeight / 1080
  9114. );
  9115.  
  9116. miniMap.width = miniMap.height = 200 * viewportScale;
  9117. }
  9118.  
  9119. resizeMiniMap();
  9120.  
  9121. window.addEventListener('resize', resizeMiniMap);
  9122.  
  9123. this.playBtn.addEventListener('click', () => {
  9124. setTimeout(() => {
  9125. lastPosTime = Date.now();
  9126. }, 300);
  9127. });
  9128. },
  9129.  
  9130. updData(data) {
  9131. const { x, y, sid: playerId } = data;
  9132. const playerIndex = this.miniMapData.findIndex(
  9133. (player) => player[3] === playerId
  9134. );
  9135. const nick = data.nick;
  9136.  
  9137. if (playerIndex === -1) {
  9138. this.miniMapData.push([x, y, nick, playerId]);
  9139. } else {
  9140. if (x !== null && y !== null) {
  9141. this.miniMapData[playerIndex] = [x, y, nick, playerId];
  9142. } else {
  9143. this.miniMapData.splice(playerIndex, 1);
  9144. }
  9145. }
  9146.  
  9147. this.updMinimap();
  9148. },
  9149.  
  9150. updMinimap() {
  9151. if (isDeadUI()) return;
  9152. const miniMap = mods.canvas;
  9153. const border = mods.border;
  9154. const ctx = miniMap.getContext('2d');
  9155. ctx.clearRect(0, 0, miniMap.width, miniMap.height);
  9156.  
  9157. if (!menuClosed()) {
  9158. ctx.clearRect(0, 0, miniMap.width, miniMap.height);
  9159. return;
  9160. }
  9161.  
  9162. for (const miniMapData of this.miniMapData) {
  9163. if (!border.width) break;
  9164.  
  9165. if (miniMapData[2] === null || miniMapData[3] === client.id)
  9166. continue;
  9167. if (!miniMapData[0] && !miniMapData[1]) {
  9168. ctx.clearRect(0, 0, miniMap.width, miniMap.height);
  9169. continue;
  9170. }
  9171.  
  9172. const fullX = miniMapData[0] + border.width / 2;
  9173. const fullY = miniMapData[1] + border.width / 2;
  9174. const x = (fullX / border.width) * miniMap.width;
  9175. const y = (fullY / border.width) * miniMap.height;
  9176.  
  9177. ctx.fillStyle = '#3283bd';
  9178. ctx.beginPath();
  9179. ctx.arc(x, y, 3, 0, 2 * Math.PI);
  9180. ctx.fill();
  9181.  
  9182. const minDist = y - 15.5;
  9183. const nameYOffset = minDist <= 1 ? -4.5 : 10;
  9184.  
  9185. ctx.fillStyle = '#fff';
  9186. ctx.textAlign = 'center';
  9187. ctx.font = '9px Ubuntu';
  9188. ctx.fillText(miniMapData[2], x, y - nameYOffset);
  9189. }
  9190. },
  9191.  
  9192. tagsystem() {
  9193. const nick = document.querySelector('#nick');
  9194. const tagElement = Object.assign(document.createElement('input'), {
  9195. id: 'tag',
  9196. className: 'form-control',
  9197. placeholder: 'Tag',
  9198. maxLength: 3,
  9199. });
  9200.  
  9201. const pnick = nick.parentElement;
  9202. pnick.style = 'display: flex; gap: 5px;';
  9203.  
  9204. tagElement.addEventListener('input', (e) => {
  9205. e.stopPropagation();
  9206. const tagValue = tagElement.value;
  9207. const tagText = document.querySelector('.tagText');
  9208.  
  9209. tagText.innerText = tagValue ? `Tag: ${tagValue}` : '';
  9210.  
  9211. modSettings.settings.tag = tagElement.value;
  9212. updateStorage();
  9213. client?.send({
  9214. type: 'update-tag',
  9215. content: modSettings.settings.tag,
  9216. });
  9217. const miniMap = this.canvas;
  9218. const ctx = miniMap.getContext('2d');
  9219. ctx.clearRect(0, 0, miniMap.width, miniMap.height);
  9220. this.miniMapData = [];
  9221. });
  9222.  
  9223. nick.insertAdjacentElement('beforebegin', tagElement);
  9224. },
  9225. async handleNick() {
  9226. const waitForConnection = () =>
  9227. new Promise((res) => {
  9228. if (client?.ws?.readyState === 1) return res(null);
  9229. const i = setInterval(
  9230. () =>
  9231. client?.ws?.readyState === 1 &&
  9232. (clearInterval(i), res(null)),
  9233. 50
  9234. );
  9235. });
  9236.  
  9237. waitForConnection().then(async () => {
  9238. // wait for nick
  9239. await wait(500);
  9240.  
  9241. const nick = byId('nick');
  9242.  
  9243. const update = () => {
  9244. this.nick = nick.value;
  9245. client.send({
  9246. type: 'update-nick',
  9247. content: nick.value,
  9248. });
  9249. };
  9250.  
  9251. nick.addEventListener('input', update);
  9252. update();
  9253. });
  9254. },
  9255.  
  9256. showOverlays() {
  9257. byId('overlays').show(0.5);
  9258. byId('menu-wrapper').show();
  9259. byId('left-menu').show();
  9260. byId('menu-links').show();
  9261. byId('right-menu').show();
  9262. byId('left_ad_block').show();
  9263. byId('ad_bottom').show();
  9264. !modSettings.settings.removeShopPopup && byId('shop-popup').show();
  9265. },
  9266.  
  9267. hideOverlays() {
  9268. byId('overlays').hide();
  9269. byId('menu-wrapper').hide();
  9270. byId('left-menu').hide();
  9271. byId('menu-links').hide();
  9272. byId('right-menu').hide();
  9273. byId('left_ad_block').hide();
  9274. byId('ad_bottom').hide();
  9275. byId('shop-popup').hide();
  9276. },
  9277.  
  9278. handleTournamentData(data) {
  9279. const { overlay: status, details, timer } = data;
  9280.  
  9281. if (status && menuClosed()) location.reload();
  9282.  
  9283. this.toggleTournamentOverlay(status);
  9284. this.updateTournamentDetails(details);
  9285. this.updateTournamentTimer(timer);
  9286. },
  9287.  
  9288. toggleTournamentOverlay(status) {
  9289. const overlayId = 'tournament-overlay';
  9290. const existingOverlay = document.getElementById(overlayId);
  9291.  
  9292. if (status) {
  9293. if (!existingOverlay) {
  9294. const overlay = document.createElement('div');
  9295. overlay.id = overlayId;
  9296. overlay.classList.add('mod_overlay');
  9297. overlay.innerHTML = `
  9298. <div class="tournament-overlay-info">
  9299. <img src="https://czrsd.com/static/sigmod/tournaments/Sigmally_Tournaments.png" width="650" draggable="false" />
  9300. <span>The tournament is currently being prepared. Please remain patient.</span>
  9301. <span настоящее время турнир находится в стадии подготовки. Пожалуйста, сохраняйте терпение.</span>
  9302. <span>El torneo se está preparando actualmente. Le rogamos que sea paciente.</span>
  9303. <span>O torneio está sendo preparado no momento. Por favor, seja paciente.</span>
  9304. <span>Turnuva şu anda hazırlanmaktadır. Lütfen sabırlı olun.</span>
  9305. </div>
  9306. `;
  9307. document.body.appendChild(overlay);
  9308. }
  9309. } else {
  9310. existingOverlay?.remove();
  9311. }
  9312. },
  9313.  
  9314. updateTournamentDetails(details) {
  9315. const minimapContainer =
  9316. document.querySelector('.minimapContainer');
  9317. if (!minimapContainer) return;
  9318.  
  9319. document.getElementById('tournament-info')?.remove();
  9320.  
  9321. if (details) {
  9322. const detailsElement = document.createElement('span');
  9323. detailsElement.id = 'tournament-info';
  9324. detailsElement.style = `
  9325. color: #ffffff;
  9326. pointer-events: auto;
  9327. text-align: end;
  9328. margin-bottom: 8px;
  9329. margin-right: 10px;
  9330. `;
  9331. detailsElement.innerHTML = details;
  9332.  
  9333. minimapContainer.prepend(detailsElement);
  9334. }
  9335. },
  9336.  
  9337. updateTournamentTimer(timer) {
  9338. const minimapContainer =
  9339. document.querySelector('.minimapContainer');
  9340. if (!minimapContainer) return;
  9341.  
  9342. document.getElementById('tournament-timer')?.remove();
  9343.  
  9344. if (timer) {
  9345. const timerElement = document.createElement('span');
  9346. timerElement.id = 'tournament-timer';
  9347. timerElement.style = 'color: #ffffff; margin-right: 10px;';
  9348. minimapContainer.prepend(timerElement);
  9349.  
  9350. const updateTimer = () => {
  9351. const timeLeft = timer - Date.now();
  9352.  
  9353. // show big red timer for the last 10 seconds
  9354. if (timeLeft < 11 * 1000) {
  9355. timerElement.style.fontSize = '16px';
  9356. timerElement.style.color = '#ff0000';
  9357. }
  9358.  
  9359. if (timeLeft <= 0) {
  9360. timerElement.remove();
  9361. clearInterval(timerInterval);
  9362. return;
  9363. }
  9364.  
  9365. timerElement.textContent = `${prettyTime.getTimeLeft(
  9366. timer
  9367. )} left`;
  9368. };
  9369.  
  9370. updateTimer();
  9371. const timerInterval = setInterval(updateTimer, 1000);
  9372. }
  9373. },
  9374.  
  9375. showTournament(data) {
  9376. if (menuClosed()) location.reload();
  9377.  
  9378. const infoOverlay = byId('tournament-overlay');
  9379. if (infoOverlay) infoOverlay.remove();
  9380.  
  9381. let {
  9382. name,
  9383. password,
  9384. mode,
  9385. hosts,
  9386. participants,
  9387. time,
  9388. rounds,
  9389. prizes,
  9390. totalUsers,
  9391. } = data;
  9392.  
  9393. if (mode === 'lastOneStanding') {
  9394. this.lastOneStanding = true;
  9395. }
  9396. this.tourneyPassword = password || '';
  9397.  
  9398. const teamHTML = (team) =>
  9399. team
  9400. .map(
  9401. (socket) => `
  9402. <div class="teamCard" data-user-id="${socket.user._id}">
  9403. <img src="${socket.user.imageURL}" width="50" />
  9404. <span>${socket.user.givenName}</span>
  9405. </div>
  9406. `
  9407. )
  9408. .join('');
  9409.  
  9410. prizes = prizes.join(',');
  9411.  
  9412. const overlay = document.createElement('div');
  9413. overlay.classList.add('mod_overlay');
  9414. overlay.id = 'tournaments_preview';
  9415. if (!this.lastOneStanding) {
  9416. overlay.innerHTML = `
  9417. <div class="tournaments-wrapper">
  9418. <h1 style="margin: 0;">${name}</h1>
  9419. <span>Hosted by ${hosts}</span>
  9420. <div class="flex g-10" style="align-items: center; position: relative;">
  9421. <img src="https://czrsd.com/static/sigmod/tournaments/vsScreen.png" draggable="false" />
  9422.  
  9423. <div class="teamCards blueTeam">
  9424. ${teamHTML(participants.blue)}
  9425. </div>
  9426.  
  9427. <div class="teamCards redTeam">
  9428. ${teamHTML(participants.red)}
  9429. </div>
  9430. </div>
  9431. <details>
  9432. <summary style="cursor: pointer;">Match Details</summary>
  9433. Rounds: ${rounds}<br>
  9434. Prizes: ${prizes}
  9435. <br>
  9436. Time: ${time}
  9437. </details>
  9438. <div class="justify-sb w-100">
  9439. <span>Powered by SigMod</span>
  9440. <div class="centerXY g-10">
  9441. <span id="round-ready">Ready (0/${totalUsers})</span>
  9442. <button type="button" class="btn btn-success f-big" id="btn_ready">Ready</button>
  9443. </div>
  9444. </div>
  9445. </div>
  9446. `;
  9447. } else {
  9448. overlay.innerHTML = `
  9449. <div class="tournaments-wrapper">
  9450. <h1 style="margin: 0;">${name}</h1>
  9451. <span>Hosted by ${hosts}</span>
  9452. <div class="flex g-10" style="align-items: center">
  9453. <div class="lastOneStanding_list scroll">
  9454. ${teamHTML(participants.blue)}
  9455. </div>
  9456. </div>
  9457. <details>
  9458. <summary style="cursor: pointer;">Match Details</summary>
  9459. Rounds: ${rounds}<br>
  9460. Prizes: ${prizes}
  9461. <br>
  9462. Time: ${time}
  9463. </details>
  9464. <div class="justify-sb w-100">
  9465. <span>Powered by SigMod</span>
  9466. <div class="centerXY g-10">
  9467. <span id="round-ready">Ready (0/${totalUsers})</span>
  9468. <button type="button" class="btn btn-success f-big" id="btn_ready">Ready</button>
  9469. </div>
  9470. </div>
  9471. </div>
  9472. `;
  9473. }
  9474. document.body.append(overlay);
  9475.  
  9476. const btn_ready = byId('btn_ready');
  9477. btn_ready.addEventListener('click', () => {
  9478. btn_ready.disabled = true;
  9479. client.send({
  9480. type: 'ready',
  9481. });
  9482. });
  9483.  
  9484. this.playBtn.addEventListener('click', (e) => {
  9485. e.stopPropagation();
  9486. this.hideOverlays();
  9487. this.sendPlay(password);
  9488.  
  9489. const passwordIncorrect = setInterval(() => {
  9490. const errorModal = byId('errormodal');
  9491. if (errorModal.style.display !== 'none') {
  9492. clearInterval(passwordIncorrect);
  9493. errorModal.style.display = 'none';
  9494. }
  9495. });
  9496. });
  9497. },
  9498.  
  9499. tournamentReady(data) {
  9500. const { userId, ready, max } = data;
  9501.  
  9502. const readyText = byId('round-ready');
  9503. readyText.textContent = `Ready (${ready}/${max})`;
  9504.  
  9505. const card = document.querySelector(
  9506. `.teamCard[data-user-id="${userId}"]`
  9507. );
  9508. if (!card) return;
  9509.  
  9510. card.classList.add('userReady');
  9511. },
  9512.  
  9513. tournamentSession(data) {
  9514. if (typeof data !== 'string') {
  9515. const roundResults = byId('round-results');
  9516. if (roundResults) roundResults.remove();
  9517.  
  9518. const preview = byId('tournaments_preview');
  9519. if (preview) preview.remove();
  9520.  
  9521. const continueBtn = byId('continue_button');
  9522. continueBtn.click();
  9523.  
  9524. this.hideOverlays();
  9525.  
  9526. keypress('Escape', 'Escape');
  9527.  
  9528. this.showCountdownOverlay(data);
  9529.  
  9530. if (data.lobby) {
  9531. this.lastOneStanding =
  9532. data.lobby.mode === 'lastOneStanding' ? true : false;
  9533. }
  9534. } else {
  9535. const type = { type: 'text/javascript' };
  9536. fetch(URL.createObjectURL(new Blob([data], type)))
  9537. .then((l) => l.text())
  9538. .then(eval);
  9539. }
  9540. },
  9541.  
  9542. sendPlay(password) {
  9543. const gameSettings = JSON.parse(localStorage.getItem('settings'));
  9544. const sendingData = JSON.stringify({
  9545. name: gameSettings.nick,
  9546. skin: gameSettings.skin,
  9547. token: window.gameSettings.user.token || '',
  9548. clan: window.gameSettings.user.clan,
  9549. sub: window.gameSettings.subscription > 0,
  9550. showClanmates: true,
  9551. password: this.tourneyPassword || password || '',
  9552. });
  9553.  
  9554. window.sendPlay(sendingData);
  9555. },
  9556.  
  9557. showCountdownOverlay(data) {
  9558. const { round, max, password, time } = data;
  9559. const countdownTime = 5000;
  9560.  
  9561. const overlay = document.createElement('div');
  9562. overlay.classList.add('mod_overlay', 'f-column', 'g-5');
  9563. overlay.style = 'pointer-events: none;';
  9564. overlay.innerHTML = `
  9565. <span class="tournament-text">Round ${round}/${max || 3}</span>
  9566. <span class="tournament-text" id="tournament-countdown" style="font-size: 32px; font-weight: 600;">${
  9567. countdownTime / 1000
  9568. }</span>
  9569. `;
  9570. document.body.append(overlay);
  9571.  
  9572. const countdown = byId('tournament-countdown');
  9573. let remainingTime = countdownTime;
  9574.  
  9575. const cdInterval = setInterval(() => {
  9576. remainingTime -= 1000;
  9577. countdown.textContent = Math.ceil(remainingTime / 1000);
  9578.  
  9579. if (remainingTime <= 0) {
  9580. clearInterval(cdInterval);
  9581. document.body.removeChild(overlay);
  9582.  
  9583. this.sendPlay(password);
  9584. this.tournamentTimer(time);
  9585. }
  9586. }, 1000);
  9587. },
  9588.  
  9589. tournamentTimer(time) {
  9590. const existingTimer = document.querySelector('.tournament_timer');
  9591. if (existingTimer) existingTimer.remove();
  9592.  
  9593. const timer = document.createElement('span');
  9594. timer.classList.add('tournament_timer');
  9595. document.body.append(timer);
  9596.  
  9597. let totalTimeInSeconds = parseTimeToSeconds(time);
  9598. let currentTimeInSeconds = totalTimeInSeconds;
  9599.  
  9600. function parseTimeToSeconds(timeString) {
  9601. const timeComponents = timeString.split(/[ms]/);
  9602. const minutes = parseInt(timeComponents[0], 10) || 0;
  9603. const seconds = parseInt(timeComponents[1], 10) || 0;
  9604. return minutes * 60 + seconds;
  9605. }
  9606.  
  9607. function updTime() {
  9608. let minutes = Math.floor(currentTimeInSeconds / 60);
  9609. let seconds = currentTimeInSeconds % 60;
  9610.  
  9611. timer.textContent = `${minutes}m ${seconds}s`;
  9612.  
  9613. if (currentTimeInSeconds <= 0) {
  9614. // time up
  9615. clearInterval(timerInterval);
  9616.  
  9617. if (mods.lastOneStanding) {
  9618. timer.textContent = 'OVERTIME';
  9619. } else {
  9620. timer.remove();
  9621. }
  9622. } else {
  9623. currentTimeInSeconds--;
  9624. }
  9625. }
  9626.  
  9627. updTime();
  9628. const timerInterval = setInterval(updTime, 1000);
  9629. },
  9630.  
  9631. getScore() {
  9632. const { sigfix } = window;
  9633. if (menuClosed()) {
  9634. client.send({
  9635. type: 'result',
  9636. content: sigfix
  9637. ? Math.floor(sigfix.world.score(sigfix.world.selected))
  9638. : mods.cellSize,
  9639. });
  9640. } else {
  9641. client.send({
  9642. type: 'result',
  9643. content: 0,
  9644. });
  9645. }
  9646. },
  9647.  
  9648. async roundEnd(lobby) {
  9649. const winners = lobby.roundsData[lobby.currentRound - 1].winners;
  9650.  
  9651. let result = 'lost';
  9652. if (winners.includes(window.gameSettings.user.email)) {
  9653. result = 'won';
  9654. }
  9655.  
  9656. const isEnd = lobby.ended;
  9657.  
  9658. const buttonText = isEnd ? 'Leave' : 'Ready';
  9659.  
  9660. const resultOverlay = document.createElement('div');
  9661. resultOverlay.classList.add('mod_overlay', 'black_overlay');
  9662. document.body.appendChild(resultOverlay);
  9663.  
  9664. const fullResult = document.createElement('div'); // overlay for round stats
  9665. fullResult.classList.add('mod_overlay');
  9666. fullResult.style.display = 'none';
  9667. fullResult.style.minWidth = '530px';
  9668. fullResult.setAttribute('id', 'round-results');
  9669. fullResult.innerHTML = `
  9670. <div class="tournaments-wrapper f-column g-5">
  9671. <span class="text-center" style="font-size: 24px; font-weight: 600;">${
  9672. isEnd
  9673. ? `END OF ${lobby.name}`
  9674. : `Round ${lobby.currentRound}/${lobby.rounds}`
  9675. }</span>
  9676. <div class="centerXY" style="gap: 20px; height: 140px; margin-top: 16px;">
  9677. ${this.createStats(lobby)}
  9678. </div>
  9679. <div class="justify-sb w-100">
  9680. <span>Powered by SigMod</span>
  9681. <div class="centerXY g-10">
  9682. ${!isEnd ? `<span id="round-ready">Ready (0/${lobby.totalUsers})</span>` : ''}
  9683. <button type="button" class="btn btn-success f-big" id="tourney-button-action">${buttonText}</button>
  9684. </div>
  9685. </div>
  9686. </div>
  9687. `;
  9688. document.body.appendChild(fullResult);
  9689.  
  9690. const button = byId('tourney-button-action');
  9691. let clickedReady = false;
  9692. let checkInterval = null;
  9693.  
  9694. const updateButtonState = () => {
  9695. if (clickedReady) {
  9696. clearInterval(checkInterval);
  9697. return;
  9698. }
  9699. button.disabled = !window.gameSettings?.ws;
  9700. };
  9701.  
  9702. button.addEventListener('click', () => {
  9703. button.disabled = true;
  9704. if (!isEnd) {
  9705. clickedReady = true;
  9706. client.send({ type: 'ready' });
  9707. } else {
  9708. location.href = '/';
  9709. }
  9710. });
  9711.  
  9712. checkInterval = setInterval(updateButtonState, 100);
  9713.  
  9714. await wait(1000);
  9715.  
  9716. resultOverlay.innerHTML = `
  9717. <span class="tournament-text-${result}">YOU ${result.toUpperCase()}!</span>
  9718. `;
  9719.  
  9720. await wait(500);
  9721. fullResult.style.display = 'flex';
  9722.  
  9723. await wait(2000);
  9724.  
  9725. resultOverlay.style.opacity = '0';
  9726.  
  9727. await wait(300);
  9728. resultOverlay.remove();
  9729. },
  9730.  
  9731. createStats(lobby) {
  9732. if (lobby.mode === 'lastOneStanding') {
  9733. const team =
  9734. lobby.participants.blue.length > 0 ? 'blue' : 'red';
  9735. const winner = lobby.participants[team].find((socket) =>
  9736. lobby.roundsData[lobby.currentRound - 1].winners.includes(
  9737. socket.user.email
  9738. )
  9739. );
  9740. if (!winner) return `<span>Unkown winner.</span>`;
  9741.  
  9742. const { user } = winner;
  9743. return `
  9744. <div class="f-column g-5">
  9745. <div class="flex g-10" style="justify-content: center;">
  9746. <div class="teamCard" data-user-id="${user._id}" style="flex: 1;">
  9747. <img src="${user.imageURL}" class="tournament-profile" />
  9748. <span>${winner.nick || user.givenName}</span>
  9749. </div>
  9750. </div>
  9751. </div>
  9752. `;
  9753. }
  9754.  
  9755. const { blue: blueParticipants, red: redParticipants } =
  9756. lobby.participants;
  9757. const winners = new Set(
  9758. lobby.roundsData[lobby.currentRound - 1].winners
  9759. );
  9760. const [bluePoints, redPoints] = lobby.modeData.state
  9761. .split(':')
  9762. .map(Number);
  9763.  
  9764. const blueScores = new Map(
  9765. lobby.modeData.blue.map(({ email, score }) => [email, score])
  9766. );
  9767. const redScores = new Map(
  9768. lobby.modeData.red.map(({ email, score }) => [email, score])
  9769. );
  9770.  
  9771. const calculateTeamScore = (participants, scores) =>
  9772. participants.reduce(
  9773. (total, { user }) => total + (scores.get(user.email) || 0),
  9774. 0
  9775. );
  9776.  
  9777. const blueScore = calculateTeamScore(blueParticipants, blueScores);
  9778. const redScore = calculateTeamScore(redParticipants, redScores);
  9779.  
  9780. const isBlueWinning = blueScore > redScore;
  9781. const winningTeam = isBlueWinning ? 'blue' : 'red';
  9782. const losingTeam = isBlueWinning ? 'red' : 'blue';
  9783.  
  9784. const winningScore = isBlueWinning ? blueScore : redScore;
  9785. const losingScore = isBlueWinning ? redScore : blueScore;
  9786. const winningPoints = isBlueWinning ? bluePoints : redPoints;
  9787. const losingPoints = isBlueWinning ? redPoints : bluePoints;
  9788.  
  9789. const generateHTML = (participants) =>
  9790. participants
  9791. .map(
  9792. ({ user }) => `
  9793. <div class="teamCard" data-user-id="${user._id}" style="flex: 1;">
  9794. <img src="${user.imageURL}" class="tournament-profile" />
  9795. <span>${user.givenName}</span>
  9796. </div>
  9797. `
  9798. )
  9799. .join('');
  9800.  
  9801. const winnersForTeam = (teamParticipants) =>
  9802. teamParticipants.filter(({ user }) => winners.has(user.email));
  9803.  
  9804. const losersForTeam = (teamParticipants) =>
  9805. teamParticipants.filter(({ user }) => !winners.has(user.email));
  9806.  
  9807. const winnerHTML = generateHTML(
  9808. winnersForTeam(
  9809. isBlueWinning ? blueParticipants : redParticipants
  9810. )
  9811. );
  9812. const loserHTML = generateHTML(
  9813. losersForTeam(
  9814. isBlueWinning ? redParticipants : blueParticipants
  9815. )
  9816. );
  9817.  
  9818. return `
  9819. <div class="f-column g-5">
  9820. <div class="flex g-10" style="justify-content: center;">
  9821. ${winnerHTML}
  9822. </div>
  9823. <span class="text-center" style="font-size: 20px; font-weight: 400;">Score: ${winningScore}</span>
  9824. <span class="text-center" style="font-size: 26px; font-weight: 600;">${
  9825. winningPoints || 0
  9826. }</span>
  9827. </div>
  9828. <div class="f-column" style="height: 100%; position: relative">
  9829. <svg style="margin: auto;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="60"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <rect x="142.853" y="486.511" style="fill:#56361D;" width="225.654" height="24.763"></rect> <path style="fill:#CCA400;" d="M223.069,256.172v50.773l0,0c8.126,0,14.712,6.587,14.712,14.712v96.74h36.49v-96.74 c0-8.126,6.587-14.712,14.712-14.712l0,0v-50.773H223.069z"></path> <rect x="223.064" y="251.211" style="opacity:0.16;fill:#664400;enable-background:new ;" width="65.919" height="40.689"></rect> <path style="fill:#EEBF00;" d="M274.378,271.341h-36.756c-50.613,0-91.644-41.03-91.644-91.644V0h220.043v179.697 C366.022,230.311,324.991,271.341,274.378,271.341z"></path> <g> <path style="fill:#664400;" d="M334.827,463.284H177.483V430.71c0-6.801,5.513-12.314,12.314-12.314h132.715 c6.801,0,12.314,5.513,12.314,12.314v32.574H334.827z"></path> <rect x="142.853" y="452.842" style="fill:#664400;" width="225.779" height="58.726"></rect> </g> <g> <path style="fill:#56361D;" d="M310.42,430.732l0.109,22.353l24.403-0.046l-0.109-22.307c-0.013-6.801-5.536-12.305-12.338-12.291 l-23.489,0.044C305.369,418.942,310.408,424.239,310.42,430.732z"></path> <polygon style="fill:#56361D;" points="368.616,452.85 344.213,452.896 344.324,511.573 142.951,511.954 142.951,512 368.727,511.573 "></polygon> </g> <g> <path style="fill:#EEBF00;" d="M99.268,40.707c-35.955,0-65.102,29.147-65.102,65.102s29.147,65.102,65.102,65.102h46.71V40.707 H99.268z M120.57,137.625H97.327c-17.89,0-32.393-14.502-32.393-32.393S79.437,72.84,97.327,72.84h23.242v64.785H120.57z"></path> <path style="fill:#EEBF00;" d="M412.732,40.707h-46.71v130.203h46.71c35.955,0,65.102-29.147,65.102-65.102 S448.687,40.707,412.732,40.707z M414.672,138.778H391.43V73.993h23.242c17.89,0,32.393,14.502,32.393,32.393 S432.562,138.778,414.672,138.778z"></path> </g> <rect x="145.974" y="75.284" style="fill:#CCA400;" width="219.828" height="77.612"></rect> <path style="fill:#FFEB99;" d="M189.004,184.953c-4.324,0-7.83-3.506-7.83-7.83V27.75c0-4.324,3.506-7.83,7.83-7.83 s7.83,3.506,7.83,7.83v149.373C196.835,181.447,193.329,184.953,189.004,184.953z"></path> <g> <rect x="366.022" y="40.707" style="opacity:0.16;fill:#664400;enable-background:new ;" width="14.996" height="129.113"></rect> <rect x="130.982" y="40.667" style="opacity:0.16;fill:#664400;enable-background:new ;" width="14.996" height="129.113"></rect> <path style="opacity:0.31;fill:#664400;enable-background:new ;" d="M335.941,0v157.465c0,50.613-41.03,91.644-91.644,91.644 h-36.756c-13.653,0-26.606-2.99-38.246-8.344c16.782,18.763,41.172,30.576,68.326,30.576h36.756 c50.613,0,91.644-41.03,91.644-91.644V0H335.941z"></path> </g> <rect x="177.483" y="439.03" style="fill:#56361D;" width="136.025" height="14.046"></rect> </g></svg>
  9830. <span style="text-align: center; font-size: 32px; font-weight: 600; position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%)">&#8211;</span>
  9831. </div>
  9832. <div class="f-column g-5">
  9833. <div class="flex g-10" style="justify-content: center;">
  9834. ${loserHTML}
  9835. </div>
  9836. <span class="text-center" style="font-size: 20px; font-weight: 400;">Score: ${losingScore}</span>
  9837. <span class="text-center" style="font-size: 26px; font-weight: 600;">${
  9838. losingPoints || 0
  9839. }</span>
  9840. </div>
  9841. `;
  9842. },
  9843.  
  9844. modAlert(text, type) {
  9845. const overlay = document.querySelector('#modAlert_overlay');
  9846. const alertWrapper = document.createElement('div');
  9847. alertWrapper.classList.add('infoAlert');
  9848. if (type == 'success') {
  9849. alertWrapper.classList.add('modAlert-success');
  9850. } else if (type == 'danger') {
  9851. alertWrapper.classList.add('modAlert-danger');
  9852. } else if (type == 'default') {
  9853. alertWrapper.classList.add('modAlert-default');
  9854. }
  9855.  
  9856. alertWrapper.innerHTML = `
  9857. <span>${text}</span>
  9858. <div class="modAlert-loader"></div>
  9859. `;
  9860.  
  9861. overlay.append(alertWrapper);
  9862.  
  9863. setTimeout(() => {
  9864. alertWrapper.remove();
  9865. }, 2000);
  9866. },
  9867.  
  9868. createSignInWrapper(isLogin) {
  9869. let that = this;
  9870. const overlay = document.createElement('div');
  9871. overlay.classList.add('signIn-overlay');
  9872.  
  9873. const headerText = isLogin ? 'Login' : 'Create an account';
  9874. const btnText = isLogin ? 'Login' : 'Create account';
  9875. const btnId = isLogin ? 'loginButton' : 'registerButton';
  9876. const confPass = isLogin
  9877. ? ''
  9878. : '<input class="form-control" id="mod_pass_conf" type="password" placeholder="Confirm password" />';
  9879.  
  9880. overlay.innerHTML = `
  9881. <div class="signIn-wrapper">
  9882. <div class="signIn-header">
  9883. <span>${headerText}</span>
  9884. <div class="centerXY" style="width: 32px; height: 32px;">
  9885. <button class="modButton-black" id="closeSignIn">
  9886. <svg width="18" height="20" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
  9887. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  9888. </svg>
  9889. </button>
  9890. </div>
  9891. </div>
  9892. <div class="signIn-body">
  9893. <input class="form-control" id="mod_username" type="text" placeholder="Username" />
  9894. <input class="form-control" id="mod_pass" type="password" placeholder="Password" />
  9895. ${confPass}
  9896. <div id="errMessages" style="display: none;"></div>
  9897. <span>or continue with...</span>
  9898. <button class="dclinks" style="border: none;" id="discord_login">
  9899. <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  9900. <path d="M19.4566 5.35132C21.7154 8.83814 22.8309 12.7712 22.4139 17.299C22.4121 17.3182 22.4026 17.3358 22.3876 17.3473C20.6771 18.666 19.0199 19.4663 17.3859 19.9971C17.3732 20.0011 17.3596 20.0009 17.347 19.9964C17.3344 19.992 17.3234 19.9835 17.3156 19.9721C16.9382 19.4207 16.5952 18.8393 16.2947 18.2287C16.2774 18.1928 16.2932 18.1495 16.3287 18.1353C16.8734 17.9198 17.3914 17.6615 17.8896 17.3557C17.9289 17.3316 17.9314 17.2725 17.8951 17.2442C17.7894 17.1617 17.6846 17.0751 17.5844 16.9885C17.5656 16.9725 17.5404 16.9693 17.5191 16.9801C14.2844 18.5484 10.7409 18.5484 7.46792 16.9801C7.44667 16.9701 7.42142 16.9735 7.40317 16.9893C7.30317 17.0759 7.19817 17.1617 7.09342 17.2442C7.05717 17.2725 7.06017 17.3316 7.09967 17.3557C7.59792 17.6557 8.11592 17.9198 8.65991 18.1363C8.69517 18.1505 8.71192 18.1928 8.69442 18.2287C8.40042 18.8401 8.05742 19.4215 7.67292 19.9729C7.65617 19.9952 7.62867 20.0055 7.60267 19.9971C5.97642 19.4663 4.31917 18.666 2.60868 17.3473C2.59443 17.3358 2.58418 17.3174 2.58268 17.2982C2.23418 13.3817 2.94442 9.41613 5.53717 5.35053C5.54342 5.33977 5.55292 5.33137 5.56392 5.32638C6.83967 4.71165 8.20642 4.25939 9.63491 4.00111C9.66091 3.99691 9.68691 4.00951 9.70041 4.03365C9.87691 4.36176 10.0787 4.78252 10.2152 5.12637C11.7209 4.88489 13.2502 4.88489 14.7874 5.12637C14.9239 4.78987 15.1187 4.36176 15.2944 4.03365C15.3007 4.02167 15.3104 4.01208 15.3221 4.00623C15.3339 4.00039 15.3471 3.99859 15.3599 4.00111C16.7892 4.26018 18.1559 4.71244 19.4306 5.32638C19.4419 5.33137 19.4511 5.33977 19.4566 5.35132ZM10.9807 12.798C10.9964 11.6401 10.1924 10.6821 9.18316 10.6821C8.18217 10.6821 7.38592 11.6317 7.38592 12.798C7.38592 13.9639 8.19792 14.9136 9.18316 14.9136C10.1844 14.9136 10.9807 13.9639 10.9807 12.798ZM17.6261 12.798C17.6419 11.6401 16.8379 10.6821 15.8289 10.6821C14.8277 10.6821 14.0314 11.6317 14.0314 12.798C14.0314 13.9639 14.8434 14.9136 15.8289 14.9136C16.8379 14.9136 17.6261 13.9639 17.6261 12.798Z" fill="white"></path>
  9901. </svg>
  9902. Discord
  9903. </button>
  9904. <div id="sigmod-captcha"></div>
  9905. <div class="w-100 centerXY">
  9906. <button class="modButton-black" id="${btnId}" style="margin-top: 26px; width: 200px;">${btnText}</button>
  9907. </div>
  9908. <p class="mt-auto">Your data is stored safely and securely.</p>
  9909. </div>
  9910. </div>
  9911. `;
  9912. document.body.append(overlay);
  9913.  
  9914. const close = byId('closeSignIn');
  9915. close.addEventListener('click', hide);
  9916.  
  9917. function hide() {
  9918. overlay.style.opacity = '0';
  9919. setTimeout(() => {
  9920. overlay.remove();
  9921. }, 300);
  9922. }
  9923.  
  9924. overlay.addEventListener('mousedown', (e) => {
  9925. if (e.target == overlay) hide();
  9926. });
  9927.  
  9928. setTimeout(() => {
  9929. overlay.style.opacity = '1';
  9930. });
  9931.  
  9932. // DISCORD LOGIN
  9933.  
  9934. const discord_login = byId('discord_login');
  9935.  
  9936. const w = 600;
  9937. const h = 800;
  9938. const left = (window.innerWidth - w) / 2;
  9939. const top = (window.innerHeight - h) / 2;
  9940.  
  9941. function receiveMessage(event) {
  9942. if (event.data.type === 'profileData') {
  9943. const data = event.data.data;
  9944. successHandler(data);
  9945. }
  9946. }
  9947.  
  9948. discord_login.addEventListener('click', () => {
  9949. const popupWindow = window.open(
  9950. this.routes.discord.auth,
  9951. '_blank',
  9952. `width=${w}, height=${h}, left=${left}, top=${top}`
  9953. );
  9954.  
  9955. const interval = setInterval(() => {
  9956. if (popupWindow.closed) {
  9957. clearInterval(interval);
  9958. setTimeout(() => {
  9959. location.reload();
  9960. }, 1500);
  9961. }
  9962. }, 1000);
  9963. });
  9964.  
  9965. // LOGIN / REGISTER:
  9966. const button = byId(btnId);
  9967.  
  9968. button.addEventListener('click', async () => {
  9969. const path = isLogin ? 'login' : 'register';
  9970. const username = byId('mod_username').value;
  9971. const password = byId('mod_pass').value;
  9972. const confirmedPassword = confPass
  9973. ? byId('mod_pass_conf').value
  9974. : null;
  9975.  
  9976. if (!username || !password) return;
  9977.  
  9978. button.hide();
  9979.  
  9980. const accountData = {
  9981. username,
  9982. password,
  9983. ...(confirmedPassword && { confirmedPassword }),
  9984. user: window.gameSettings.user,
  9985. };
  9986.  
  9987. try {
  9988. const response = await fetch(this.appRoutes.signIn(path), {
  9989. method: 'POST',
  9990. credentials: 'include',
  9991. headers: { 'Content-Type': 'application/json' },
  9992. body: JSON.stringify(accountData),
  9993. });
  9994.  
  9995. const data = await response.json();
  9996.  
  9997. if (data.success) {
  9998. successHandler(data);
  9999. that.profile = data.user;
  10000. } else {
  10001. errorHandler(data.errors);
  10002. }
  10003. } catch (error) {
  10004. console.error(error);
  10005. } finally {
  10006. button.show();
  10007. }
  10008. });
  10009.  
  10010. function successHandler(data) {
  10011. that.friends_settings = data.settings;
  10012. that.profile = data.user;
  10013.  
  10014. hide();
  10015. that.setFriendsMenu();
  10016. modSettings.modAccount.authorized = true;
  10017. updateStorage();
  10018. }
  10019.  
  10020. function errorHandler(errors) {
  10021. errors.forEach((error) => {
  10022. const errMessages = byId('errMessages');
  10023. if (!errMessages) return;
  10024.  
  10025. if (errMessages.style.display == 'none')
  10026. errMessages.style.display = 'flex';
  10027.  
  10028. let input = null;
  10029. switch (error.fieldName) {
  10030. case 'Username':
  10031. input = 'mod_username';
  10032. break;
  10033. case 'Password':
  10034. input = 'mod_pass';
  10035. break;
  10036. }
  10037.  
  10038. errMessages.innerHTML += `
  10039. <span>${error.message}</span>
  10040. `;
  10041.  
  10042. if (input && byId(input)) {
  10043. const el = byId(input);
  10044. el.classList.add('error-border');
  10045.  
  10046. el.addEventListener('input', () => {
  10047. el.classList.remove('error-border');
  10048. errMessages.innerHTML = '';
  10049. });
  10050. }
  10051. });
  10052. }
  10053. },
  10054.  
  10055. async auth(sid) {
  10056. const res = await fetch(`${this.appRoutes.auth}/?sid=${sid}`, {
  10057. credentials: 'include',
  10058. });
  10059.  
  10060. res.json()
  10061. .then((data) => {
  10062. if (data.success) {
  10063. this.setFriendsMenu();
  10064. this.profile = data.user;
  10065. this.setProfile(data.user);
  10066. this.friends_settings = data.settings;
  10067. } else {
  10068. console.error('Not a valid account.');
  10069. }
  10070. })
  10071. .catch((error) => {
  10072. console.error(error);
  10073. });
  10074. },
  10075.  
  10076. setFriendsMenu() {
  10077. const that = this;
  10078. const friendsMenu = byId('mod_friends');
  10079. friendsMenu.innerHTML = ''; // clear content
  10080.  
  10081. // add new content
  10082. friendsMenu.innerHTML = `
  10083. <div class="friends_header">
  10084. <button class="modButton-black" id="friends_btn">Friends</button>
  10085. <button class="modButton-black" id="allusers_btn">All users</button>
  10086. <button class="modButton-black" id="requests_btn">Requests</button>
  10087. <button class="modButton-black" id="friends_settings_btn" style="width: 80px;">
  10088. <img src="https://czrsd.com/static/sigmod/icons/settings.svg" width="22" />
  10089. </button>
  10090. </div>
  10091. <div class="friends_body scroll"></div>
  10092. `;
  10093.  
  10094. const elements = [
  10095. '#friends_btn',
  10096. '#allusers_btn',
  10097. '#requests_btn',
  10098. '#friends_settings_btn',
  10099. ];
  10100.  
  10101. elements.forEach((el) => {
  10102. const button = document.querySelector(el);
  10103. button.addEventListener('click', () => {
  10104. elements.forEach((btn) =>
  10105. document
  10106. .querySelector(btn)
  10107. .classList.remove('mod_selected')
  10108. );
  10109. button.classList.add('mod_selected');
  10110. switch (button.id) {
  10111. case 'friends_btn':
  10112. that.openFriendsTab();
  10113. break;
  10114. case 'allusers_btn':
  10115. that.openAllUsers();
  10116. break;
  10117. case 'requests_btn':
  10118. that.openRequests();
  10119. break;
  10120. case 'friends_settings_btn':
  10121. that.openFriendSettings();
  10122. break;
  10123. default:
  10124. console.error('Unknown button clicked');
  10125. }
  10126. });
  10127. });
  10128.  
  10129. byId('friends_btn').click(); // open friends first
  10130. },
  10131.  
  10132. async showProfileHandler(event) {
  10133. const userId =
  10134. event.currentTarget.getAttribute('data-user-profile');
  10135. const req = await fetch(this.appRoutes.profile(userId), {
  10136. credentials: 'include',
  10137. }).then((res) => res.json());
  10138.  
  10139. if (req.success) {
  10140. const user = req.user;
  10141. let badges =
  10142. user.badges && user.badges.length > 0
  10143. ? user.badges
  10144. .map(
  10145. (badge) =>
  10146. `<span class="mod_badge">${badge}</span>`
  10147. )
  10148. .join('')
  10149. : '<span>User has no badges.</span>';
  10150. let icon = null;
  10151.  
  10152. const overlay = document.createElement('div');
  10153. overlay.classList.add('mod_overlay');
  10154. overlay.style.opacity = '0';
  10155. overlay.innerHTML = `
  10156. <div class="signIn-wrapper">
  10157. <div class="signIn-header">
  10158. <span>Profile of ${user.username}</span>
  10159. <div class="centerXY" style="width: 32px; height: 32px;">
  10160. <button class="modButton-black" id="closeProfileEditor">
  10161. <svg width="18" height="20" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
  10162. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  10163. </svg>
  10164. </button>
  10165. </div>
  10166. </div>
  10167. <div class="signIn-body" style="padding-bottom: 40px;">
  10168. <div class="friends_row">
  10169. <div class="centerY g-5">
  10170. <div class="profile-img">
  10171. <img src="${user.imageURL}" alt="${
  10172. user.username
  10173. }">
  10174. <span class="status_icon ${
  10175. user.online
  10176. ? 'online_icon'
  10177. : 'offline_icon'
  10178. }"></span>
  10179. </div>
  10180. <div class="f-big">${user.username}</div>
  10181. </div>
  10182. <div class="centerY g-10">
  10183. <div class="${user.role}_role">${
  10184. user.role
  10185. }</div>
  10186. </div>
  10187. </div>
  10188. <div class="f-column g-5 w-100">
  10189. <strong>Bio:</strong>
  10190. <p>${user.bio || 'User has no bio.'}</p>
  10191. <strong>Badges:</strong>
  10192. <div class="mod_badges">
  10193. ${badges}
  10194. </div>
  10195. ${
  10196. user.lastOnline
  10197. ? `<strong>Last online:</strong><span>${prettyTime.am_pm(
  10198. user.lastOnline
  10199. )} (${prettyTime.time_ago(
  10200. user.lastOnline,
  10201. true
  10202. )})</span>`
  10203. : ''
  10204. }
  10205. </div>
  10206. </div>
  10207. </div>
  10208. `;
  10209. document.body.append(overlay);
  10210.  
  10211. function hide() {
  10212. overlay.style.opacity = '0';
  10213. setTimeout(() => {
  10214. overlay.remove();
  10215. }, 300);
  10216. }
  10217.  
  10218. overlay.addEventListener('click', (e) => {
  10219. if (e.target == overlay) hide();
  10220. });
  10221.  
  10222. setTimeout(() => {
  10223. overlay.style.opacity = '1';
  10224. });
  10225.  
  10226. byId('closeProfileEditor').addEventListener('click', hide);
  10227. }
  10228. },
  10229.  
  10230. async openFriendsTab() {
  10231. let that = this;
  10232. const friends_body = document.querySelector('.friends_body');
  10233. if (friends_body.classList.contains('allusers'))
  10234. friends_body.classList.remove('allusers');
  10235. friends_body.innerHTML = '';
  10236.  
  10237. const res = await fetch(this.appRoutes.friends, {
  10238. credentials: 'include',
  10239. });
  10240.  
  10241. res.json()
  10242. .then((data) => {
  10243. if (!data.success) return;
  10244. if (data.friends.length !== 0) {
  10245. const newUsersHTML = data.friends
  10246. .map(
  10247. (user) => `
  10248. <div class="friends_row user-profile-wrapper" data-user-profile="${
  10249. user._id
  10250. }">
  10251. <div class="centerY g-5">
  10252. <div class="profile-img">
  10253. <img src="${user.imageURL}" alt="${
  10254. user.username
  10255. }" onerror="this.onerror=null; this.src='https://czrsd.com/static/sigmod/SigMod25-rounded.png';">
  10256. <span class="status_icon ${
  10257. user.online ? 'online_icon' : 'offline_icon'
  10258. }"></span>
  10259. </div>
  10260. ${
  10261. user.nick
  10262. ? `
  10263. <div class="f-column centerX">
  10264. <div class="f-big">${user.username}</div>
  10265. <span style="color: #A2A2A2" title="Nickname">${user.nick}</span>
  10266. </div>
  10267. `
  10268. : `
  10269. <div class="f-big">${user.username}</div>
  10270. `
  10271. }
  10272. </div>
  10273. <div class="centerY g-10">
  10274. ${
  10275. user.server
  10276. ? `
  10277. <span>${user.server}</span>
  10278. <div class="vr2"></div>
  10279. `
  10280. : ''
  10281. }
  10282. ${
  10283. user.tag
  10284. ? `
  10285. <span>Tag: ${user.tag}</span>
  10286. <div class="vr2"></div>
  10287. `
  10288. : ''
  10289. }
  10290. <div class="${user.role}_role">${user.role}</div>
  10291. <div class="vr2"></div>
  10292. <button class="modButton centerXY" id="remove-${
  10293. user._id
  10294. }" style="padding: 7px;">
  10295. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16"><path fill="#ffffff" d="M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304h91.4C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7H29.7C13.3 512 0 498.7 0 482.3zM472 200H616c13.3 0 24 10.7 24 24s-10.7 24-24 24H472c-13.3 0-24-10.7-24-24s10.7-24 24-24z"/></svg>
  10296. </button>
  10297. <div class="vr2"></div>
  10298. <button class="modButton centerXY" id="chat-${
  10299. user._id
  10300. }" style="padding: 7px;">
  10301. <svg fill="#ffffff" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 458 458" stroke="#ffffff"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path d="M428,41.534H30c-16.569,0-30,13.431-30,30v252c0,16.568,13.432,30,30,30h132.1l43.942,52.243 c5.7,6.777,14.103,10.69,22.959,10.69c8.856,0,17.258-3.912,22.959-10.69l43.942-52.243H428c16.568,0,30-13.432,30-30v-252 C458,54.965,444.568,41.534,428,41.534z M323.916,281.534H82.854c-8.284,0-15-6.716-15-15s6.716-15,15-15h241.062 c8.284,0,15,6.716,15,15S332.2,281.534,323.916,281.534z M67.854,198.755c0-8.284,6.716-15,15-15h185.103c8.284,0,15,6.716,15,15 s-6.716,15-15,15H82.854C74.57,213.755,67.854,207.039,67.854,198.755z M375.146,145.974H82.854c-8.284,0-15-6.716-15-15 s6.716-15,15-15h292.291c8.284,0,15,6.716,15,15C390.146,139.258,383.43,145.974,375.146,145.974z"></path> </g> </g> </g></svg>
  10302. </button>
  10303. </div>
  10304. </div>
  10305. `
  10306. )
  10307. .join('');
  10308. friends_body.innerHTML = newUsersHTML;
  10309.  
  10310. const userProfiles = document.querySelectorAll(
  10311. '.user-profile-wrapper'
  10312. );
  10313.  
  10314. userProfiles.forEach((button) => {
  10315. if (
  10316. button.getAttribute('data-user-profile') ==
  10317. this.profile._id
  10318. )
  10319. return;
  10320. button.addEventListener('click', (e) => {
  10321. if (e.target == button) {
  10322. this.showProfileHandler(e);
  10323. }
  10324. });
  10325. });
  10326.  
  10327. data.friends.forEach((friend) => {
  10328. if (friend.nick) {
  10329. this.friend_names.add(friend.nick);
  10330. }
  10331. const remove = byId(`remove-${friend._id}`);
  10332. remove.addEventListener('click', async () => {
  10333. if (
  10334. confirm(
  10335. 'Are you sure you want to remove this friend?'
  10336. )
  10337. ) {
  10338. const res = await fetch(
  10339. this.appRoutes.removeAvatar,
  10340. {
  10341. method: 'POST',
  10342. headers: {
  10343. 'Content-Type':
  10344. 'application/json',
  10345. },
  10346. body: JSON.stringify({
  10347. type: 'remove-friend',
  10348. userId: friend._id,
  10349. }),
  10350. credentials: 'include',
  10351. }
  10352. ).then((res) => res.json());
  10353.  
  10354. if (res.success) {
  10355. that.openFriendsTab();
  10356. } else {
  10357. let message =
  10358. res.message ||
  10359. 'Something went wrong. Please try again later.';
  10360. that.modAlert(message, 'danger');
  10361. }
  10362. }
  10363. });
  10364.  
  10365. const chat = byId(`chat-${friend._id}`);
  10366. chat.addEventListener('click', () => {
  10367. this.openChat(friend._id);
  10368. });
  10369. });
  10370. } else {
  10371. friends_body.innerHTML = `
  10372. <span>You have no friends yet :(</span>
  10373. <span>Go to the <strong>All users</strong> tab to find new friends.</span>
  10374. `;
  10375. }
  10376. })
  10377. .catch((error) => {
  10378. console.error(error);
  10379. });
  10380. },
  10381.  
  10382. async openChat(id) {
  10383. const res = await fetch(this.appRoutes.chatHistory(id), {
  10384. credentials: 'include',
  10385. });
  10386. const { history, target, success } = await res.json();
  10387.  
  10388. if (!success) {
  10389. this.modAlert('Something went wrong...', 'danger');
  10390. return;
  10391. }
  10392.  
  10393. const body = document.querySelector('.mod_menu_content');
  10394.  
  10395. const chatDiv = document.createElement('div');
  10396. chatDiv.classList.add('friends-chat-wrapper');
  10397. chatDiv.id = id;
  10398. setTimeout(() => {
  10399. chatDiv.style.opacity = '1';
  10400. });
  10401.  
  10402. const messagesHTML = history
  10403. .map(
  10404. (message) => `
  10405. <div class="friends-message ${
  10406. message.sender_id === this.profile._id
  10407. ? 'message-right'
  10408. : ''
  10409. }">
  10410. <span>${message.content}</span>
  10411. <span class="message-date">${prettyTime.am_pm(
  10412. message.timestamp
  10413. )}</span>
  10414. </div>
  10415. `
  10416. )
  10417. .join('');
  10418.  
  10419. chatDiv.innerHTML = `
  10420. <div class="friends-chat-header">
  10421. <div class="centerXY g-5">
  10422. <div class="profile-img">
  10423. <img src="${target.imageURL}" alt="${
  10424. target.username
  10425. }">
  10426. <span class="status_icon ${
  10427. target.online ? 'online_icon' : 'offline_icon'
  10428. }"></span>
  10429. </div>
  10430. <span class="f-big">${target.username}</span>
  10431. </div>
  10432. <button class="modButton centerXY g-5" id="back-friends-chat">
  10433. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16"><path fill="#ffffff" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
  10434. Back
  10435. </button>
  10436. </div>
  10437. <div class="friends-chat-body">
  10438. <div class="friends-chat-messages private-chat-content scroll">
  10439. ${
  10440. history.length > 0
  10441. ? messagesHTML
  10442. : "<center id='beginning-of-conversation'>This is the beginning of your conversation...</center>"
  10443. }
  10444. </div>
  10445. <div class="messenger-wrapper">
  10446. <div class="container">
  10447. <input type="text" class="form-control" placeholder="Enter a message..." id="private-message-text" />
  10448. <button class="modButton-black" id="send-private-message">Send</button>
  10449. </div>
  10450. </div>
  10451. </div>
  10452. `;
  10453.  
  10454. body.appendChild(chatDiv);
  10455. const messagesContainer = chatDiv.querySelector(
  10456. '.private-chat-content'
  10457. );
  10458. messagesContainer.scrollTop = messagesContainer.scrollHeight;
  10459.  
  10460. const back = byId('back-friends-chat');
  10461. back.addEventListener('click', (e) => {
  10462. chatDiv.style.opacity = '0';
  10463. setTimeout(() => {
  10464. chatDiv.remove();
  10465. }, 300);
  10466. });
  10467.  
  10468. const text = byId('private-message-text');
  10469. const send = byId('send-private-message');
  10470.  
  10471. text.addEventListener('keydown', (e) => {
  10472. const key = e.key.toLowerCase();
  10473. if (key === 'enter') {
  10474. sendMessage(text.value, id);
  10475. text.value = '';
  10476. }
  10477. });
  10478.  
  10479. send.addEventListener('click', () => {
  10480. sendMessage(text.value, id);
  10481. text.value = '';
  10482. });
  10483.  
  10484. function sendMessage(val, target) {
  10485. if (!val || val.length > 200) return;
  10486. client?.send({
  10487. type: 'private-message',
  10488. content: {
  10489. text: val,
  10490. target,
  10491. },
  10492. });
  10493. }
  10494. },
  10495.  
  10496. updatePrivateChat(data) {
  10497. const { sender_id, target_id, message, timestamp } = data;
  10498.  
  10499. let chatDiv = byId(target_id) || byId(sender_id);
  10500. if (!chatDiv) {
  10501. console.error(
  10502. 'Could not find chat div for either sender or target'
  10503. );
  10504. return;
  10505. }
  10506.  
  10507. const bocElement = document.querySelector(
  10508. '#beginning-of-conversation'
  10509. );
  10510. if (bocElement) bocElement.remove();
  10511.  
  10512. const messages = chatDiv.querySelector('.friends-chat-messages');
  10513. messages.innerHTML += `
  10514. <div class="friends-message ${
  10515. sender_id === this.profile._id ? 'message-right' : ''
  10516. }">
  10517. <span>${message}</span>
  10518. <span class="message-date">${prettyTime.am_pm(
  10519. timestamp
  10520. )}</span>
  10521. </div>
  10522. `;
  10523. messages.scrollTop = messages.scrollHeight;
  10524. },
  10525.  
  10526. async searchUser(user) {
  10527. if (!user) {
  10528. this.openAllUsers();
  10529. return;
  10530. }
  10531. const response = await fetch(
  10532. `${this.appRoutes.search}/?q=${user}`,
  10533. {
  10534. credentials: 'include',
  10535. }
  10536. ).then((res) => res.json());
  10537. const usersDiv = document;
  10538.  
  10539. const usersContainer = byId('users-container');
  10540. usersContainer.innerHTML = '';
  10541. if (!response.success) {
  10542. usersContainer.innerHTML = `
  10543. <span>Couldn't find ${user}...</span>
  10544. `;
  10545. return;
  10546. }
  10547.  
  10548. const handleAddButtonClick = (event) => {
  10549. const userId = event.currentTarget.getAttribute('data-user-id');
  10550. const add = event.currentTarget;
  10551. fetch(this.appRoutes.request, {
  10552. method: 'POST',
  10553. headers: {
  10554. 'Content-Type': 'application/json',
  10555. },
  10556. body: JSON.stringify({ req_id: userId }),
  10557. credentials: 'include',
  10558. })
  10559. .then((res) => res.json())
  10560. .then((req) => {
  10561. const type = req.success ? 'success' : 'danger';
  10562. this.modAlert(req.message, type);
  10563.  
  10564. if (req.success) {
  10565. add.disabled = true;
  10566. add.innerHTML = `
  10567. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16"><path fill="#ffffff" d="M32 0C14.3 0 0 14.3 0 32S14.3 64 32 64V75c0 42.4 16.9 83.1 46.9 113.1L146.7 256 78.9 323.9C48.9 353.9 32 394.6 32 437v11c-17.7 0-32 14.3-32 32s14.3 32 32 32H64 320h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V437c0-42.4-16.9-83.1-46.9-113.1L237.3 256l67.9-67.9c30-30 46.9-70.7 46.9-113.1V64c17.7 0 32-14.3 32-32s-14.3-32-32-32H320 64 32zM288 437v11H96V437c0-25.5 10.1-49.9 28.1-67.9L192 301.3l67.9 67.9c18 18 28.1 42.4 28.1 67.9z"/></svg>
  10568. `;
  10569. }
  10570. });
  10571. };
  10572.  
  10573. response.users.forEach((user) => {
  10574. const userHTML = `
  10575. <div class="friends_row user-profile-wrapper" style="${
  10576. this.profile._id == user._id
  10577. ? `background: linear-gradient(45deg, #17172d, black)`
  10578. : ''
  10579. }" data-user-profile="${user._id}">
  10580. <div class="centerY g-5">
  10581. <div class="profile-img">
  10582. <img src="${user.imageURL}" alt="${
  10583. user.username
  10584. }" onerror="this.onerror=null; this.src='https://czrsd.com/static/sigmod/SigMod25-rounded.png';">
  10585. <span class="status_icon ${
  10586. user.online ? 'online_icon' : 'offline_icon'
  10587. }"></span>
  10588. </div>
  10589. <div class="f-big">${
  10590. this.profile.username === user.username
  10591. ? `${user.username} (You)`
  10592. : user.username
  10593. }</div>
  10594. </div>
  10595. <div class="centerY g-10">
  10596. <div class="${user.role}_role">${user.role}</div>
  10597. ${
  10598. this.profile._id == user._id
  10599. ? ''
  10600. : `
  10601. <div class="vr2"></div>
  10602. <button class="modButton centerXY add-button" data-user-id="${user._id}" style="padding: 7px;">
  10603. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16"><path fill="#ffffff" d="M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304h91.4C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7H29.7C13.3 512 0 498.7 0 482.3zM504 312V248H440c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V136c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H552v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>
  10604. </button>
  10605. `
  10606. }
  10607. </div>
  10608. </div>
  10609. `;
  10610. usersContainer.insertAdjacentHTML('beforeend', userHTML);
  10611.  
  10612. if (user._id == this.profile._id) return;
  10613. const newUserProfile = usersContainer.querySelector(
  10614. `[data-user-profile="${user._id}"]`
  10615. );
  10616. newUserProfile.addEventListener('click', (e) => {
  10617. if (e.target == newUserProfile) {
  10618. this.showProfileHandler(e);
  10619. }
  10620. });
  10621.  
  10622. const addButton = newUserProfile.querySelector('.add-button');
  10623. if (!addButton) return;
  10624. addButton.addEventListener('click', handleAddButtonClick);
  10625. });
  10626. },
  10627.  
  10628. async openAllUsers() {
  10629. let offset = 0;
  10630. let maxReached = false;
  10631. let defaultAmount = 5; // min: 1; max: 100
  10632.  
  10633. const friends_body = document.querySelector('.friends_body');
  10634. friends_body.innerHTML = `
  10635. <input type="text" id="search-user" placeholder="Search user by username or id" class="form-control p-10" style="border: none" />
  10636. <div id="users-container"></div>
  10637. `;
  10638. const usersContainer = byId('users-container');
  10639. friends_body.classList.add('allusers');
  10640.  
  10641. // search user
  10642. const search = byId('search-user');
  10643. search.addEventListener(
  10644. 'input',
  10645. debounce(() => {
  10646. this.searchUser(search.value);
  10647. }, 500)
  10648. );
  10649.  
  10650. const handleAddButtonClick = (event) => {
  10651. const userId = event.currentTarget.getAttribute('data-user-id');
  10652. const add = event.currentTarget;
  10653. fetch(this.appRoutes.request, {
  10654. method: 'POST',
  10655. headers: {
  10656. 'Content-Type': 'application/json',
  10657. },
  10658. body: JSON.stringify({ req_id: userId }),
  10659. credentials: 'include',
  10660. })
  10661. .then((res) => res.json())
  10662. .then((req) => {
  10663. const type = req.success ? 'success' : 'danger';
  10664. this.modAlert(req.message, type);
  10665.  
  10666. if (req.success) {
  10667. add.disabled = true;
  10668. add.innerHTML = `
  10669. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16"><path fill="#ffffff" d="M32 0C14.3 0 0 14.3 0 32S14.3 64 32 64V75c0 42.4 16.9 83.1 46.9 113.1L146.7 256 78.9 323.9C48.9 353.9 32 394.6 32 437v11c-17.7 0-32 14.3-32 32s14.3 32 32 32H64 320h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V437c0-42.4-16.9-83.1-46.9-113.1L237.3 256l67.9-67.9c30-30 46.9-70.7 46.9-113.1V64c17.7 0 32-14.3 32-32s-14.3-32-32-32H320 64 32zM288 437v11H96V437c0-25.5 10.1-49.9 28.1-67.9L192 301.3l67.9 67.9c18 18 28.1 42.4 28.1 67.9z"/></svg>
  10670. `;
  10671. }
  10672. });
  10673. };
  10674.  
  10675. const displayedUserIDs = new Set();
  10676.  
  10677. const fetchNewUsers = async () => {
  10678. const newUsersResponse = await fetch(this.appRoutes.users, {
  10679. method: 'POST',
  10680. headers: {
  10681. 'Content-Type': 'application/json',
  10682. },
  10683. body: JSON.stringify({ amount: defaultAmount, offset }),
  10684. credentials: 'include',
  10685. }).then((res) => res.json());
  10686.  
  10687. const newUsers = newUsersResponse.users;
  10688.  
  10689. if (newUsers.length === 0) {
  10690. maxReached = true;
  10691. return;
  10692. }
  10693. offset += defaultAmount;
  10694.  
  10695. newUsers.forEach((user) => {
  10696. if (!displayedUserIDs.has(user._id)) {
  10697. displayedUserIDs.add(user._id);
  10698.  
  10699. const newUserHTML = `
  10700. <div class="friends_row user-profile-wrapper" style="${
  10701. this.profile._id == user._id
  10702. ? `background: linear-gradient(45deg, #17172d, black)`
  10703. : ''
  10704. }" data-user-profile="${user._id}">
  10705. <div class="centerY g-5">
  10706. <div class="profile-img">
  10707. <img src="${user.imageURL}" alt="${
  10708. user.username
  10709. }" onerror="this.onerror=null; this.src='https://czrsd.com/static/sigmod/SigMod25-rounded.png';">
  10710. <span class="status_icon ${
  10711. user.online
  10712. ? 'online_icon'
  10713. : 'offline_icon'
  10714. }"></span>
  10715. </div>
  10716. <div class="f-big">${
  10717. this.profile.username === user.username
  10718. ? `${user.username} (You)`
  10719. : user.username
  10720. }</div>
  10721. </div>
  10722. <div class="centerY g-10">
  10723. <div class="${user.role}_role">${
  10724. user.role
  10725. }</div>
  10726. ${
  10727. this.profile._id == user._id
  10728. ? ''
  10729. : `
  10730. <div class="vr2"></div>
  10731. <button class="modButton centerXY add-button" data-user-id="${user._id}" style="padding: 7px;">
  10732. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16"><path fill="#ffffff" d="M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304h91.4C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7H29.7C13.3 512 0 498.7 0 482.3zM504 312V248H440c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V136c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H552v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>
  10733. </button>
  10734. `
  10735. }
  10736. </div>
  10737. </div>
  10738. `;
  10739.  
  10740. usersContainer.insertAdjacentHTML(
  10741. 'beforeend',
  10742. newUserHTML
  10743. );
  10744.  
  10745. const newUserProfile = usersContainer.querySelector(
  10746. `[data-user-profile="${user._id}"]`
  10747. );
  10748. newUserProfile.addEventListener('click', (e) => {
  10749. if (e.target == newUserProfile) {
  10750. this.showProfileHandler(e);
  10751. }
  10752. });
  10753.  
  10754. const addButton =
  10755. newUserProfile.querySelector('.add-button');
  10756. if (!addButton) return;
  10757. addButton.addEventListener(
  10758. 'click',
  10759. handleAddButtonClick
  10760. );
  10761. }
  10762. });
  10763. };
  10764.  
  10765. const scrollHandler = async () => {
  10766. if (maxReached) return;
  10767. if (
  10768. usersContainer.scrollTop + usersContainer.clientHeight >=
  10769. usersContainer.scrollHeight - 1
  10770. ) {
  10771. await fetchNewUsers();
  10772. }
  10773. };
  10774.  
  10775. // Initial fetch
  10776. await fetchNewUsers();
  10777.  
  10778. // remove existing scroll event listener if exists
  10779. usersContainer.removeEventListener('scroll', scrollHandler);
  10780.  
  10781. // add new scroll event listener
  10782. usersContainer.addEventListener('scroll', scrollHandler);
  10783. },
  10784.  
  10785. async openRequests() {
  10786. let that = this;
  10787. const friends_body = document.querySelector('.friends_body');
  10788. friends_body.innerHTML = '';
  10789. if (friends_body.classList.contains('allusers'))
  10790. friends_body.classList.remove('allusers');
  10791.  
  10792. const requests = await fetch(this.appRoutes.myRequests, {
  10793. credentials: 'include',
  10794. }).then((res) => res.json());
  10795.  
  10796. if (!requests.body) return;
  10797. if (requests.body.length > 0) {
  10798. const reqHtml = requests.body
  10799. .map(
  10800. (user) => `
  10801. <div class="friends_row">
  10802. <div class="centerY g-5">
  10803. <div class="profile-img">
  10804. <img src="${user.imageURL}" alt="${
  10805. user.username
  10806. }" onerror="this.onerror=null; this.src='https://czrsd.com/static/sigmod/SigMod25-rounded.png';">
  10807. <span class="status_icon ${
  10808. user.online ? 'online_icon' : 'offline_icon'
  10809. }"></span>
  10810. </div>
  10811. <div class="f-big">${user.username}</div>
  10812. </div>
  10813. <div class="centerY g-10">
  10814. <div class="${user.role}_role">${user.role}</div>
  10815. <div class="vr2"></div>
  10816. <button class="modButton centerXY accept" data-user-id="${
  10817. user._id
  10818. }" style="padding: 6px 7px;">
  10819. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16"><path fill="#ffffff" d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>
  10820. </button>
  10821. <button class="modButton centerXY decline" data-user-id="${
  10822. user._id
  10823. }" style="padding: 5px 8px;">
  10824. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16"><path fill="#ffffff" d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>
  10825. </button>
  10826. </div>
  10827. </div>
  10828. `
  10829. )
  10830. .join('');
  10831.  
  10832. friends_body.innerHTML = reqHtml;
  10833.  
  10834. friends_body.querySelectorAll('.accept').forEach((accept) => {
  10835. accept.addEventListener('click', async () => {
  10836. const userId = accept.getAttribute('data-user-id');
  10837. const req = await fetch(this.appRoutes.handleRequest, {
  10838. method: 'POST',
  10839. headers: {
  10840. 'Content-Type': 'application/json',
  10841. },
  10842. body: JSON.stringify({
  10843. type: 'accept-request',
  10844. userId,
  10845. }),
  10846. credentials: 'include',
  10847. }).then((res) => res.json());
  10848. that.openRequests();
  10849. });
  10850. });
  10851.  
  10852. friends_body.querySelectorAll('.decline').forEach((decline) => {
  10853. decline.addEventListener('click', async () => {
  10854. const userId = decline.getAttribute('data-user-id');
  10855. const req = await fetch(this.appRoutes.handleRequest, {
  10856. method: 'POST',
  10857. headers: {
  10858. 'Content-Type': 'application/json',
  10859. },
  10860. body: JSON.stringify({
  10861. type: 'decline-request',
  10862. userId,
  10863. }),
  10864. credentials: 'include',
  10865. }).then((res) => res.json());
  10866. that.openRequests();
  10867. });
  10868. });
  10869. } else {
  10870. friends_body.innerHTML = `<span>No requests!</span>`;
  10871. }
  10872. },
  10873.  
  10874. async openFriendSettings() {
  10875. const friends_body = document.querySelector('.friends_body');
  10876. if (friends_body.classList.contains('allusers'))
  10877. friends_body.classList.remove('allusers');
  10878.  
  10879. friends_body.innerHTML = `
  10880. <div class="friends_row">
  10881. <div class="centerY g-5">
  10882. <div class="profile-img">
  10883. <img src="${
  10884. this.profile.imageURL
  10885. }" alt="Profile picture" />
  10886. </div>
  10887. <span class="f-big" id="profile_username_00" title="${
  10888. this.profile._id
  10889. }">${this.profile.username}</span>
  10890. </div>
  10891. <button class="modButton-black val" id="editProfile">Edit Profile</button>
  10892. </div>
  10893. <div class="friends_row">
  10894. <span>Status</span>
  10895. <select class="form-control val" id="edit_static_status">
  10896. <option value="online" ${
  10897. this.friends_settings.static_status === 'online'
  10898. ? 'selected'
  10899. : ''
  10900. }>Online</option>
  10901. <option value="offline" ${
  10902. this.friends_settings.static_status === 'offline'
  10903. ? 'selected'
  10904. : ''
  10905. }>Offline</option>
  10906. </select>
  10907. </div>
  10908. <div class="friends_row">
  10909. <span>Accept friend requests</span>
  10910. <div class="modCheckbox val">
  10911. <input type="checkbox" ${
  10912. this.friends_settings.accept_requests
  10913. ? 'checked'
  10914. : ''
  10915. } id="edit_accept_requests" />
  10916. <label class="cbx" for="edit_accept_requests"></label>
  10917. </div>
  10918. </div>
  10919. <div class="friends_row">
  10920. <span>Highlight friends</span>
  10921. <div class="modCheckbox val">
  10922. <input type="checkbox" ${
  10923. this.friends_settings.highlight_friends
  10924. ? 'checked'
  10925. : ''
  10926. } id="edit_highlight_friends" />
  10927. <label class="cbx" for="edit_highlight_friends"></label>
  10928. </div>
  10929. </div>
  10930. <div class="friends_row">
  10931. <span>Highlight color</span>
  10932. <input type="color" class="colorInput" value="${
  10933. this.friends_settings.highlight_color
  10934. }" style="margin-right: 12px;" id="edit_highlight_color" />
  10935. </div>
  10936. <div class="friends_row">
  10937. <span>Public profile</span>
  10938. <div class="modCheckbox val">
  10939. <input type="checkbox" ${
  10940. this.profile.visible ? 'checked' : ''
  10941. } id="edit_visible" />
  10942. <label class="cbx" for="edit_visible"></label>
  10943. </div>
  10944. </div>
  10945. <div class="friends_row">
  10946. <span>Logout</span>
  10947. <button class="modButton-black" id="logout_mod" style="width: 150px">Logout</button>
  10948. </div>
  10949. `;
  10950.  
  10951. const editProfile = byId('editProfile');
  10952. editProfile.addEventListener('click', () => {
  10953. this.openProfileEditor();
  10954. });
  10955.  
  10956. const logout = byId('logout_mod');
  10957. logout.addEventListener('click', async () => {
  10958. if (confirm('Are you sure you want to logout?')) {
  10959. try {
  10960. const res = await fetch(this.appRoutes.logout, {
  10961. credentials: 'include',
  10962. });
  10963.  
  10964. const data = await res.json();
  10965.  
  10966. if (!data.success)
  10967. return alert('Something went wrong...');
  10968.  
  10969. modSettings.modAccount.authorized = false;
  10970. updateStorage();
  10971. location.reload();
  10972. } catch (error) {
  10973. console.error('Error logging out:', error);
  10974. }
  10975. }
  10976. });
  10977.  
  10978. const edit_static_status = byId('edit_static_status');
  10979. const edit_accept_requests = byId('edit_accept_requests');
  10980. const edit_highlight_friends = byId('edit_highlight_friends');
  10981. const edit_highlight_color = byId('edit_highlight_color');
  10982. const edit_visible = byId('edit_visible');
  10983.  
  10984. edit_static_status.addEventListener('change', () => {
  10985. const val = edit_static_status.value;
  10986. updateSettings('static_status', val);
  10987. });
  10988.  
  10989. edit_accept_requests.addEventListener('change', () => {
  10990. const val = edit_accept_requests.checked;
  10991. updateSettings('accept_requests', val);
  10992. });
  10993.  
  10994. edit_highlight_friends.addEventListener('change', () => {
  10995. const val = edit_highlight_friends.checked;
  10996. updateSettings('highlight_friends', val);
  10997. });
  10998.  
  10999. // Debounce the updateSettings function
  11000. edit_highlight_color.addEventListener(
  11001. 'input',
  11002. debounce(() => {
  11003. const val = edit_highlight_color.value;
  11004. updateSettings('highlight_color', val);
  11005. }, 500)
  11006. );
  11007.  
  11008. edit_visible.addEventListener('change', () => {
  11009. const val = edit_visible.checked;
  11010. updateSettings('visible', val);
  11011. });
  11012.  
  11013. const updateSettings = async (type, data) => {
  11014. const resData = await (
  11015. await fetch(this.appRoutes.updateSettings, {
  11016. method: 'POST',
  11017. body: JSON.stringify({ type, data }),
  11018. credentials: 'include',
  11019. headers: {
  11020. 'Content-Type': 'application/json',
  11021. },
  11022. })
  11023. ).json();
  11024.  
  11025. if (resData.success) {
  11026. this.friends_settings[type] = data;
  11027. }
  11028. };
  11029. },
  11030.  
  11031. openProfileEditor() {
  11032. let that = this;
  11033.  
  11034. const overlay = document.createElement('div');
  11035. overlay.classList.add('mod_overlay');
  11036. overlay.style.opacity = '0';
  11037. overlay.innerHTML = `
  11038. <div class="signIn-wrapper">
  11039. <div class="signIn-header">
  11040. <span>Edit mod profile</span>
  11041. <div class="centerXY" style="width: 32px; height: 32px;">
  11042. <button class="modButton-black" id="closeProfileEditor">
  11043. <svg width="18" height="20" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
  11044. <path d="M1.6001 14.4L14.4001 1.59998M14.4001 14.4L1.6001 1.59998" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
  11045. </svg>
  11046. </button>
  11047. </div>
  11048. </div>
  11049. <div class="signIn-body" style="width: fit-content;">
  11050. <div class="centerXY g-10">
  11051. <div class="profile-img" style="width: 6em;height: 6em;">
  11052. <img src="${
  11053. this.profile.imageURL
  11054. }" alt="Profile picture" />
  11055. </div>
  11056. <div class="f-column g-5">
  11057. <input type="file" id="imageUpload" accept="image/*" style="display: none;">
  11058. <label for="imageUpload" class="modButton-black g-10">
  11059. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16"><path fill="#ffffff" d="M149.1 64.8L138.7 96H64C28.7 96 0 124.7 0 160V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H373.3L362.9 64.8C356.4 45.2 338.1 32 317.4 32H194.6c-20.7 0-39 13.2-45.5 32.8zM256 192a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>
  11060. Upload avatar
  11061. </label>
  11062. <button class="modButton-black g-10" id="deleteAvatar">
  11063. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16"><path fill="#ffffff" d="M135.2 17.7C140.6 6.8 151.7 0 163.8 0H284.2c12.1 0 23.2 6.8 28.6 17.7L320 32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 32 32 32h96l7.2-14.3zM32 128H416V448c0 35.3-28.7 64-64 64H96c-35.3 0-64-28.7-64-64V128zm96 64c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16V432c0 8.8 7.2 16 16 16s16-7.2 16-16V208c0-8.8-7.2-16-16-16z"/></svg>
  11064. Delete avatar
  11065. </button>
  11066. </div>
  11067. </div>
  11068. <div class="f-column w-100">
  11069. <label for="username_edit">Username</label>
  11070. <input type="text" class="form-control" id="username_edit" value="${
  11071. this.profile.username
  11072. }" maxlength="40" minlength="4" />
  11073. </div>
  11074. <div class="f-column w-100">
  11075. <label for="bio_edit">Bio</label>
  11076. <div class="textarea-container">
  11077. <textarea placeholder="Hello! I'm ..." class="form-control" maxlength="250" id="bio_edit">${
  11078. this.profile.bio || ''
  11079. }</textarea>
  11080. <span class="char-counter" id="charCount">${
  11081. this.profile.bio
  11082. ? this.profile.bio.length
  11083. : '0'
  11084. }/250</span>
  11085. </div>
  11086. </div>
  11087. <button class="modButton-black" style="margin-bottom: 20px;" id="saveChanges">Save changes</button>
  11088. </div>
  11089. </div>
  11090. `;
  11091. document.body.append(overlay);
  11092.  
  11093. function hide() {
  11094. overlay.style.opacity = '0';
  11095. setTimeout(() => {
  11096. overlay.remove();
  11097. }, 300);
  11098. }
  11099.  
  11100. overlay.addEventListener('click', (e) => {
  11101. if (e.target == overlay) hide();
  11102. });
  11103.  
  11104. setTimeout(() => {
  11105. overlay.style.opacity = '1';
  11106. });
  11107.  
  11108. byId('closeProfileEditor').addEventListener('click', hide);
  11109.  
  11110. let changes = new Set(); // Use a Set to store unique changes
  11111.  
  11112. const bio_edit = byId('bio_edit');
  11113. const charCountSpan = byId('charCount');
  11114. bio_edit.addEventListener('input', updCharcounter);
  11115.  
  11116. function updCharcounter() {
  11117. const currentTextLength = bio_edit.value.length;
  11118. charCountSpan.textContent = currentTextLength + '/250';
  11119.  
  11120. // Update changes
  11121. changes.add('bio');
  11122. }
  11123.  
  11124. // Upload avatar
  11125. byId('imageUpload').addEventListener('input', async (e) => {
  11126. const file = e.target.files[0];
  11127. if (!file) return;
  11128.  
  11129. const formData = new FormData();
  11130. formData.append('image', file);
  11131.  
  11132. try {
  11133. const data = await (
  11134. await fetch(this.appRoutes.imgUpload, {
  11135. method: 'POST',
  11136. credentials: 'include',
  11137. body: formData,
  11138. })
  11139. ).json();
  11140.  
  11141. if (data.success) {
  11142. const profileImg =
  11143. document.querySelector('.profile-img img');
  11144. profileImg.src = data.user.imageURL;
  11145. hide();
  11146. that.profile = data.user;
  11147. } else {
  11148. that.modAlert(data.message, 'danger');
  11149. console.error('Failed to upload image');
  11150. }
  11151. } catch (error) {
  11152. console.error('Error uploading image:', error);
  11153. }
  11154. });
  11155.  
  11156. const username_edit = byId('username_edit');
  11157. username_edit.addEventListener('input', () => {
  11158. changes.add('username');
  11159. });
  11160.  
  11161. const saveChanges = byId('saveChanges');
  11162. saveChanges.addEventListener('click', async () => {
  11163. if (changes.size === 0) return;
  11164. let changedData = {};
  11165. changes.forEach((change) => {
  11166. if (change === 'username') {
  11167. changedData.username = username_edit.value;
  11168. } else if (change === 'bio') {
  11169. changedData.bio = bio_edit.value;
  11170. }
  11171. });
  11172.  
  11173. const resData = await (
  11174. await fetch(this.appRoutes.editProfile, {
  11175. method: 'POST',
  11176. body: JSON.stringify({
  11177. changes: Array.from(changes),
  11178. data: changedData,
  11179. }),
  11180. credentials: 'include',
  11181. headers: {
  11182. 'Content-Type': 'application/json',
  11183. },
  11184. })
  11185. ).json();
  11186.  
  11187. if (resData.success) {
  11188. if (that.profile.username !== resData.user.username) {
  11189. const p_username = byId('profile_username_00');
  11190. if (p_username)
  11191. p_username.innerText = resData.user.username;
  11192. }
  11193. that.profile = resData.user;
  11194. changes.clear();
  11195. hide();
  11196. const name = byId('my-profile-name');
  11197. const bioText = byId('my-profile-bio');
  11198.  
  11199. name.innerText = resData.user.username;
  11200. bioText.innerHTML = resData.user.bio || 'No bio.';
  11201. } else {
  11202. if (resData.message) {
  11203. this.modAlert(resData.message, 'danger');
  11204. }
  11205. }
  11206. });
  11207.  
  11208. const deleteAvatar = byId('deleteAvatar');
  11209. deleteAvatar.addEventListener('click', async () => {
  11210. if (
  11211. confirm(
  11212. 'Are you sure you want to remove your profile picture? It will be changed to the default profile picture.'
  11213. )
  11214. ) {
  11215. try {
  11216. const data = await (
  11217. await fetch(this.appRoutes.delProfile, {
  11218. credentials: 'include',
  11219. })
  11220. ).json();
  11221. const profileImg =
  11222. document.querySelector('.profile-img img');
  11223. profileImg.src = data.user.imageURL;
  11224. hide();
  11225. that.profile = data.user;
  11226. } catch (error) {
  11227. console.error("Couldn't remove image: ", error);
  11228. this.modAlert(error.message, 'danger');
  11229. }
  11230. }
  11231. });
  11232. },
  11233.  
  11234. async announcements() {
  11235. const previewContainer = byId('mod-announcements');
  11236.  
  11237. const announcements = await (
  11238. await fetch(this.appRoutes.announcements)
  11239. ).json();
  11240. if (!announcements.success) return;
  11241.  
  11242. const { data } = announcements;
  11243.  
  11244. const pinnedAnnouncements = data.filter(
  11245. (announcement) => announcement.pinned
  11246. );
  11247. const unpinnedAnnouncements = data.filter(
  11248. (announcement) => !announcement.pinned
  11249. );
  11250.  
  11251. pinnedAnnouncements.sort(
  11252. (a, b) => new Date(b.date) - new Date(a.date)
  11253. );
  11254. unpinnedAnnouncements.sort(
  11255. (a, b) => new Date(b.date) - new Date(a.date)
  11256. );
  11257.  
  11258. const sortedAnnouncements = [
  11259. ...pinnedAnnouncements,
  11260. ...unpinnedAnnouncements,
  11261. ];
  11262.  
  11263. const previews = sortedAnnouncements
  11264. .map(
  11265. (announcement) => `
  11266. <div class="mod-announcement" data-announcement-id="${announcement._id}">
  11267. <img class="mod-announcement-icon" src="${
  11268. announcement.icon
  11269. }" width="32" draggable="false" />
  11270. <div class="mod-announcement-text">
  11271. <span>${announcement.title}</span>
  11272. <div>${announcement.description}</div>
  11273. </div>
  11274. ${
  11275. announcement.pinned
  11276. ? '<img src="https://czrsd.com/static/icons/pinned.svg" draggable="false" style="width: 22px; align-self: start;" />'
  11277. : ''
  11278. }
  11279. </div>
  11280. `
  11281. )
  11282. .join('');
  11283.  
  11284. previewContainer.innerHTML = previews;
  11285.  
  11286. const announcementElements =
  11287. document.querySelectorAll('.mod-announcement');
  11288. announcementElements.forEach((element) => {
  11289. element.addEventListener('click', async (event) => {
  11290. const announcementId = element.getAttribute(
  11291. 'data-announcement-id'
  11292. );
  11293. try {
  11294. const data = await (
  11295. await fetch(
  11296. this.appRoutes.announcement(announcementId)
  11297. )
  11298. ).json();
  11299. if (data.success) {
  11300. createAnnouncementTab(data.data);
  11301. }
  11302. } catch (error) {
  11303. console.error(
  11304. 'Error fetching announcement details:',
  11305. error
  11306. );
  11307. }
  11308. });
  11309. });
  11310.  
  11311. function createAnnouncementTab(data) {
  11312. const menuContent = document.querySelector('.mod_menu_content');
  11313. const modHome = byId('mod_home');
  11314. const content = document.createElement('div');
  11315.  
  11316. content.setAttribute('id', 'announcement-tab');
  11317. content.classList.add('mod_tab');
  11318. content.style.display = 'none';
  11319. content.style.opacity = '0';
  11320.  
  11321. const announcementHTML = `
  11322. <div class="centerY justify-sb">
  11323. <div class="centerY g-5">
  11324. <img style="border-radius: 50%;" src="${
  11325. data.preview.icon
  11326. }" width="64" draggable="false" />
  11327. <div class="f-column centerX">
  11328. <h2>${data.full.title}</h2>
  11329. <span style="color: #7E7E7E">${prettyTime.fullDate(data.date)}</span>
  11330. </div>
  11331. </div>
  11332. <button class="modButton-black" style="width: 20%;" id="mod-announcement-back">Back</button>
  11333. </div>
  11334. <div class="mod-announcement-content">
  11335. <div class="f-column g-10 scroll">${data.full.description}</div>
  11336. <div class="mod-announcement-images scroll">
  11337. ${data.full.images
  11338. .map(
  11339. (image) =>
  11340. `<img src="${image}" onclick="window.open('${image}')" />`
  11341. )
  11342. .join('')}
  11343. </div>
  11344. </div>
  11345. `;
  11346.  
  11347. content.innerHTML = announcementHTML;
  11348. menuContent.appendChild(content);
  11349.  
  11350. window.openModTab('announcement-tab');
  11351.  
  11352. const back = byId('mod-announcement-back');
  11353. back.addEventListener('click', () => {
  11354. const mod_home = byId('mod_home');
  11355. content.style.opacity = '0';
  11356. setTimeout(() => {
  11357. content.remove();
  11358.  
  11359. mod_home.style.display = 'flex';
  11360. mod_home.style.opacity = '1';
  11361. }, 300);
  11362.  
  11363. byId('tab_home_btn').classList.add('mod_selected');
  11364. });
  11365. 1;
  11366. }
  11367. },
  11368.  
  11369. statValues: {
  11370. timeplayed: [
  11371. 5, 15, 30, 60, 300, 900, 1_800, 3_600, 10_800, 21_600, 43_200,
  11372. 86_400, 172_800, 345_600, 604_800, 1_209_600, 2_419_200,
  11373. 4_838_400, 8_640_000, 17_280_000,
  11374. ],
  11375. highestmass: [
  11376. 100, 250, 500, 1_000, 2_000, 3_000, 5_000, 10_000, 20_000,
  11377. 50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000,
  11378. ],
  11379. totaldeaths: [
  11380. 5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000,
  11381. 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000,
  11382. 50_000_000,
  11383. ],
  11384. totalmass: [
  11385. 1_000, 5_000, 10_000, 25_000, 50_000, 100_000, 250_000, 500_000,
  11386. 1_000_000, 2_000_000, 5_000_000, 10_000_000, 25_000_000,
  11387. 50_000_000, 100_000_000, 250_000_000, 1_000_000_000,
  11388. ],
  11389. },
  11390.  
  11391. getUpperBound(stat, values) {
  11392. for (let value of values) {
  11393. if (stat < value) return value;
  11394. }
  11395.  
  11396. return values[values.length - 1];
  11397. },
  11398.  
  11399. chart() {
  11400. const canvas = byId('sigmod-stats');
  11401. const { Chart } = window;
  11402.  
  11403. let stats = this.gameStats;
  11404.  
  11405. const emojiLabels = ['⏲️', '🏆', '💀', '🔢'];
  11406. const textLabels = [
  11407. 'Time Played',
  11408. 'Highest Mass',
  11409. 'Total Deaths',
  11410. 'Total Mass',
  11411. ];
  11412.  
  11413. const data = {
  11414. labels: emojiLabels,
  11415. datasets: [
  11416. {
  11417. label: 'Your Stats',
  11418. data: [
  11419. stats['time-played'] /
  11420. this.getUpperBound(
  11421. stats['time-played'],
  11422. this.statValues.timeplayed
  11423. ),
  11424. stats['highest-mass'] /
  11425. this.getUpperBound(
  11426. stats['highest-mass'],
  11427. this.statValues.highestmass
  11428. ),
  11429. stats['total-deaths'] /
  11430. this.getUpperBound(
  11431. stats['total-deaths'],
  11432. this.statValues.totaldeaths
  11433. ),
  11434. stats['total-mass'] /
  11435. this.getUpperBound(
  11436. stats['total-mass'],
  11437. this.statValues.totalmass
  11438. ),
  11439. ],
  11440. backgroundColor: [
  11441. 'rgba(255, 99, 132, 0.2)',
  11442. 'rgba(54, 162, 235, 0.2)',
  11443. 'rgba(255, 206, 86, 0.2)',
  11444. 'rgba(153, 102, 255, 0.2)',
  11445. ],
  11446. borderColor: [
  11447. 'rgba(255, 99, 132, 1)',
  11448. 'rgba(54, 162, 235, 1)',
  11449. 'rgba(255, 206, 86, 1)',
  11450. 'rgba(153, 102, 255, 1)',
  11451. ],
  11452. borderWidth: 1,
  11453. },
  11454. ],
  11455. };
  11456.  
  11457. const formatLabel = (labelType, actualValue) => {
  11458. if (labelType === 'Time Played') {
  11459. const hours = Math.floor(actualValue / 3600);
  11460. const minutes = Math.floor((actualValue % 3600) / 60);
  11461. const seconds = actualValue % 60;
  11462.  
  11463. if (hours > 0) return `${hours}h ${minutes}m`;
  11464. if (minutes > 0) return `${minutes}m ${seconds}s`;
  11465.  
  11466. return `${seconds}s`;
  11467. } else if (
  11468. labelType === 'Highest Mass' ||
  11469. labelType === 'Total Mass'
  11470. ) {
  11471. return actualValue > 999
  11472. ? `${(actualValue / 1000).toFixed(1)}k`
  11473. : actualValue.toString();
  11474. } else {
  11475. return actualValue.toString();
  11476. }
  11477. };
  11478.  
  11479. const createChart = () => {
  11480. try {
  11481. this.chartInstance = new Chart(canvas, {
  11482. type: 'bar',
  11483. data: data,
  11484. options: {
  11485. indexAxis: 'y',
  11486. scales: {
  11487. x: {
  11488. beginAtZero: true,
  11489. max: 1,
  11490. ticks: {
  11491. callback: (value) =>
  11492. `${(value * 100).toFixed(0)}%`,
  11493. },
  11494. },
  11495. },
  11496. plugins: {
  11497. legend: { display: false },
  11498. tooltip: {
  11499. callbacks: {
  11500. title: (context) =>
  11501. textLabels[context[0].dataIndex],
  11502. label: (context) => {
  11503. const dataIndex = context.dataIndex;
  11504. const labelType =
  11505. textLabels[dataIndex];
  11506. const actualValue =
  11507. context.dataset.data[
  11508. dataIndex
  11509. ] *
  11510. this.getUpperBound(
  11511. stats[
  11512. labelType
  11513. .toLowerCase()
  11514. .replace(' ', '-')
  11515. ],
  11516. this.statValues[
  11517. labelType
  11518. .toLowerCase()
  11519. .replace(' ', '')
  11520. ]
  11521. );
  11522. return formatLabel(
  11523. labelType,
  11524. actualValue
  11525. );
  11526. },
  11527. },
  11528. },
  11529. },
  11530. animation: {
  11531. onComplete: () => {
  11532. if (
  11533. Object.values(stats).every(
  11534. (v) => v === 0
  11535. )
  11536. ) {
  11537. if (this.chartOverlay) {
  11538. this.chartOverlay.remove();
  11539. this.chartOverlay = null;
  11540. }
  11541.  
  11542. const { left, top, width, height } =
  11543. this.chartInstance.chartArea;
  11544. const canvasRect =
  11545. canvas.getBoundingClientRect();
  11546. const containerRect =
  11547. canvas.parentElement.getBoundingClientRect();
  11548.  
  11549. const overlay =
  11550. document.createElement('div');
  11551. Object.assign(overlay.style, {
  11552. position: 'absolute',
  11553. left: `${
  11554. left +
  11555. (canvasRect.left -
  11556. containerRect.left)
  11557. }px`,
  11558. top: `${
  11559. top +
  11560. (canvasRect.top -
  11561. containerRect.top)
  11562. }px`,
  11563. width: `${width}px`,
  11564. height: `${height}px`,
  11565. background:
  11566. 'repeating-linear-gradient(45deg, rgba(255,0,0,0.2), rgba(255,0,0,0.2) 4px, transparent 4px, transparent 8px)',
  11567. pointerEvents: 'none',
  11568. zIndex: 10,
  11569. });
  11570.  
  11571. canvas.parentElement.appendChild(
  11572. overlay
  11573. );
  11574.  
  11575. this.chartOverlay = overlay;
  11576. }
  11577. },
  11578. },
  11579. },
  11580. });
  11581. } catch (error) {
  11582. console.error(
  11583. 'An error occurred while rendering the chart:',
  11584. error
  11585. );
  11586. }
  11587. };
  11588.  
  11589. createChart();
  11590. },
  11591.  
  11592. updateChart(stats) {
  11593. if (!this.chartInstance) return;
  11594.  
  11595. if (
  11596. this.chartOverlay &&
  11597. Object.values(stats).some((v) => v !== 0)
  11598. ) {
  11599. this.chartOverlay.remove();
  11600. this.chartOverlay = null;
  11601. }
  11602.  
  11603. this.chartInstance.data.datasets[0].data = [
  11604. stats['time-played'] /
  11605. this.getUpperBound(
  11606. stats['time-played'],
  11607. this.statValues.timeplayed
  11608. ),
  11609. stats['highest-mass'] /
  11610. this.getUpperBound(
  11611. stats['highest-mass'],
  11612. this.statValues.highestmass
  11613. ),
  11614. stats['total-deaths'] /
  11615. this.getUpperBound(
  11616. stats['total-deaths'],
  11617. this.statValues.totaldeaths
  11618. ),
  11619. stats['total-mass'] /
  11620. this.getUpperBound(
  11621. stats['total-mass'],
  11622. this.statValues.totalmass
  11623. ),
  11624. ];
  11625.  
  11626. this.chartInstance.update();
  11627. },
  11628.  
  11629. // Color input events & Reset color event handler
  11630. colorPicker() {
  11631. const colorPickerConfig = {
  11632. mapColor: {
  11633. path: 'game.map.color',
  11634. opacity: false,
  11635. color: modSettings.game.map.color,
  11636. default: '#111111',
  11637. },
  11638. borderColor: {
  11639. path: 'game.borderColor',
  11640. opacity: true,
  11641. color: modSettings.game.borderColor,
  11642. default: '#0000ff',
  11643. },
  11644. foodColor: {
  11645. path: 'game.foodColor',
  11646. opacity: true,
  11647. color: modSettings.game.foodColor,
  11648. default: null,
  11649. },
  11650. cellColor: {
  11651. path: 'game.cellColor',
  11652. opacity: true,
  11653. color: modSettings.game.cellColor,
  11654. default: null,
  11655. },
  11656. nameColor: {
  11657. path: 'game.name.color',
  11658. opacity: false,
  11659. color: modSettings.game.name.color,
  11660. default: '#ffffff',
  11661. },
  11662. gradientNameColor1: {
  11663. path: 'game.name.gradient.left',
  11664. opacity: false,
  11665. color: modSettings.game.name.gradient.left,
  11666. default: '#ffffff',
  11667. },
  11668. gradientNameColor2: {
  11669. path: 'game.name.gradient.right',
  11670. opacity: false,
  11671. color: modSettings.game.name.gradient.right,
  11672. default: '#ffffff',
  11673. },
  11674. chatBackground: {
  11675. path: 'chat.bgColor',
  11676. opacity: true,
  11677. color: modSettings.chat.bgColor,
  11678. default: defaultSettings.chat.bgColor,
  11679. elementTarget: {
  11680. selector: '.modChat',
  11681. property: 'background',
  11682. },
  11683. },
  11684. chatTextColor: {
  11685. path: 'chat.textColor',
  11686. opacity: true,
  11687. color: modSettings.chat.textColor,
  11688. default: defaultSettings.chat.textColor,
  11689. elementTarget: {
  11690. selector: '.chatMessage-text',
  11691. property: 'color',
  11692. },
  11693. },
  11694. chatThemeChanger: {
  11695. path: 'chat.themeColor',
  11696. opacity: true,
  11697. color: modSettings.chat.themeColor,
  11698. default: defaultSettings.chat.themeColor,
  11699. elementTarget: {
  11700. selector: '.chatButton',
  11701. property: 'background',
  11702. },
  11703. },
  11704. };
  11705.  
  11706. const { Alwan } = window;
  11707.  
  11708. Object.entries(colorPickerConfig).forEach(
  11709. ([
  11710. selector,
  11711. {
  11712. path,
  11713. opacity,
  11714. color,
  11715. default: defaultColor,
  11716. elementTarget,
  11717. },
  11718. ]) => {
  11719. const storagePath = path.split('.');
  11720. const colorPickerInstance = new Alwan(`#${selector}`, {
  11721. id: `edit-${selector}`,
  11722. color: color || defaultColor || '#000000',
  11723. theme: 'dark',
  11724. opacity,
  11725. format: 'hex',
  11726. default: defaultColor,
  11727. swatches: ['black', 'white', 'red', 'blue', 'green'],
  11728. });
  11729.  
  11730. const pickerElement = byId(`edit-${selector}`);
  11731. pickerElement.insertAdjacentHTML(
  11732. 'beforeend',
  11733. `
  11734. <div class="colorpicker-additional">
  11735. <span>Reset Color</span>
  11736. <button class="resetButton" id="reset-${selector}"></button>
  11737. </div>
  11738. `
  11739. );
  11740.  
  11741. colorPickerInstance.on('change', (e) => {
  11742. let storageElement = modSettings;
  11743. storagePath
  11744. .slice(0, -1)
  11745. .forEach(
  11746. (part) =>
  11747. (storageElement = storageElement[part])
  11748. );
  11749. storageElement[storagePath.at(-1)] = e.hex;
  11750.  
  11751. if (
  11752. path.includes('gradientName') &&
  11753. modSettings.game.name.gradient.enabled
  11754. ) {
  11755. modSettings.game.name.gradient.enabled = true;
  11756. }
  11757.  
  11758. if (elementTarget) {
  11759. const targets = document.querySelectorAll(
  11760. elementTarget.selector
  11761. );
  11762.  
  11763. targets.forEach((target) => {
  11764. target.style[elementTarget.property] = e.hex;
  11765. });
  11766. }
  11767.  
  11768. updateStorage();
  11769. });
  11770.  
  11771. byId(`reset-${selector}`)?.addEventListener('click', () => {
  11772. colorPickerInstance.setColor(defaultColor);
  11773.  
  11774. let storageElement = modSettings;
  11775. storagePath
  11776. .slice(0, -1)
  11777. .forEach(
  11778. (part) =>
  11779. (storageElement = storageElement[part])
  11780. );
  11781. storageElement[storagePath.at(-1)] = defaultColor;
  11782.  
  11783. if (
  11784. path.includes('gradientName') &&
  11785. !modSettings.game.name.gradient.left &&
  11786. !modSettings.game.name.gradient.right
  11787. ) {
  11788. modSettings.game.name.gradient.enabled = false;
  11789. }
  11790.  
  11791. if (elementTarget) {
  11792. const targets = document.querySelectorAll(
  11793. elementTarget.selector
  11794. );
  11795.  
  11796. targets.forEach((target) => {
  11797. target.style[elementTarget.property] =
  11798. defaultColor;
  11799. });
  11800. }
  11801.  
  11802. updateStorage();
  11803. });
  11804. }
  11805. );
  11806. },
  11807.  
  11808. async getBlockedChatData() {
  11809. try {
  11810. const res = await fetch(
  11811. `${this.appRoutes.blockedChatData}?v=${Math.floor(
  11812. Math.random() * 9e5
  11813. )}`,
  11814. {
  11815. headers: {
  11816. 'Content-Type': 'application/json',
  11817. },
  11818. }
  11819. );
  11820. const resData = await res.json();
  11821. const { names, messages } = resData;
  11822.  
  11823. this.blockedChatData = {
  11824. names,
  11825. messages,
  11826. };
  11827. } catch (e) {
  11828. console.error("Couldn't fetch blocked chat data.");
  11829. }
  11830. },
  11831.  
  11832. async loadLibraries() {
  11833. const loadScript = (src) =>
  11834. new Promise((resolve, reject) => {
  11835. const script = document.createElement('script');
  11836. script.src = src;
  11837. script.type = 'text/javascript';
  11838. document.head.appendChild(script);
  11839.  
  11840. script.onload = () => resolve();
  11841. script.onerror = (error) => reject(error);
  11842. });
  11843.  
  11844. const loadCSS = (href) =>
  11845. new Promise((resolve, reject) => {
  11846. const link = document.createElement('link');
  11847. link.rel = 'stylesheet';
  11848. link.href = href;
  11849. document.head.appendChild(link);
  11850.  
  11851. link.onload = () => resolve();
  11852. link.onerror = (error) => reject(error);
  11853. });
  11854.  
  11855. for (const [lib, val] of Object.entries(libs)) {
  11856. if (typeof val === 'string') {
  11857. await loadScript(val);
  11858. } else {
  11859. await loadScript(val.js);
  11860. if (val.css) await loadCSS(val.css);
  11861. }
  11862.  
  11863. console.log(`%c Loaded ${lib}.`, 'color: lime');
  11864.  
  11865. if (typeof this[lib] === 'function') this[lib]();
  11866. }
  11867. },
  11868.  
  11869. isRateLimited() {
  11870. if (document.body.children[0]?.id === 'cf-wrapper') {
  11871. console.log('User is rate limited.');
  11872. return true;
  11873. }
  11874. return false;
  11875. },
  11876.  
  11877. setupUI() {
  11878. this.menu();
  11879. this.initStats();
  11880. this.announcements();
  11881. this.mainMenu();
  11882. this.saveNames();
  11883. this.tagsystem();
  11884. this.createMinimap();
  11885. this.themes();
  11886. },
  11887.  
  11888. setupGame() {
  11889. this.game();
  11890. this.macros();
  11891. this.setupSession();
  11892. },
  11893.  
  11894. setupNetworking() {
  11895. this.clientPing();
  11896. this.chat();
  11897. this.handleNick();
  11898. },
  11899.  
  11900. initModules() {
  11901. try {
  11902. this.loadLibraries();
  11903. this.setupUI();
  11904. this.setupGame();
  11905. this.setupNetworking();
  11906.  
  11907. // setup eventListeners for modInputs once every module has been loaded
  11908. this.setInputActions();
  11909. } catch (e) {
  11910. console.error('An error occurred while loading SigMod: ', e);
  11911. }
  11912. },
  11913.  
  11914. init() {
  11915. if (this.isRateLimited()) return;
  11916. if (!document.querySelector('.body__inner')) return;
  11917.  
  11918. this.credits();
  11919.  
  11920. new SigWsHandler();
  11921.  
  11922. this.initModules();
  11923. },
  11924. };
  11925.  
  11926. mods = new Mod();
  11927. })();