")
468 | .append($("| ", { text: title }))
469 | .append($(" | ").append($input)));
470 | }
471 | }
472 |
473 | // begin hotkey capture on click
474 | function startHotkeyRecord() {
475 | const $input = $(this);
476 | if ($input.hasClass("recording")) return;
477 | const action = $input.data("action");
478 | const capture = (e) => {
479 | e.stopImmediatePropagation();
480 | e.preventDefault();
481 | if (!e.key) return;
482 | if (["Shift", "Control", "Alt", "Meta"].includes(e.key)) return;
483 | if ((e.key === "Delete" || e.key === "Backspace" || e.key === "Escape") && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
484 | hotKeys[action] = {
485 | key: "",
486 | ctrl: false,
487 | alt: false,
488 | shift: false
489 | };
490 | }
491 | else {
492 | hotKeys[action] = {
493 | key: e.key.toUpperCase(),
494 | ctrl: e.ctrlKey,
495 | alt: e.altKey,
496 | shift: e.shiftKey
497 | };
498 | }
499 | saveSettings();
500 | finish();
501 | };
502 | const finish = () => {
503 | document.removeEventListener("keydown", capture, true);
504 | $input.removeClass("recording").val(bindingToText(hotKeys[action])).off("blur", finish);
505 | };
506 | document.addEventListener("keydown", capture, true);
507 | $input.addClass("recording").val("Press keys…").on("blur", finish);
508 | }
509 |
510 | // input hotKeys[action] and convert the data to a string for the input field
511 | function bindingToText(b) {
512 | if (!b) return "";
513 | const keys = [];
514 | if (b.ctrl) keys.push("CTRL");
515 | if (b.alt) keys.push("ALT");
516 | if (b.shift) keys.push("SHIFT");
517 | if (b.key) keys.push(b.key === " " ? "SPACE" : b.key);
518 | return keys.join(" + ");
519 | }
520 |
521 | // validate json data in local storage
522 | function validateLocalStorage(item) {
523 | try {
524 | const json = JSON.parse(localStorage.getItem(item));
525 | if (!json || typeof json !== "object") return {};
526 | return json;
527 | }
528 | catch {
529 | return {};
530 | }
531 | }
532 |
533 | // save settings
534 | function saveSettings() {
535 | localStorage.setItem("anisongdbSearch", JSON.stringify({
536 | injectSearchButtons,
537 | hotKeys
538 | }));
539 | }
540 |
541 | // apply styles
542 | function applyStyles() {
543 | let css = /*css*/ `
544 | #anisongdbWindow .modal-header {
545 | padding: 0;
546 | height: 74px;
547 | }
548 | #anisongdbWindow .modal-header h2 {
549 | font-size: 22px;
550 | text-align: left;
551 | height: 45px;
552 | margin: 0;
553 | padding: 10px;
554 | display: block;
555 | }
556 | #anisongdbWindow .modal-header .tabContainer {
557 | border-bottom: none;
558 | }
559 | #anisongdbWindow .modal-header .tabContainer .tab::before {
560 | box-shadow: none;
561 | }
562 | #anisongdbWindow .modal-header i.fa:hover {
563 | opacity: .7;
564 | }
565 | #anisongdbWindow select, #anisongdbWindow input {
566 | color: black;
567 | }
568 | #adbsSearchButtonRow {
569 | margin-bottom: 10px;
570 | }
571 | #adbsSearchButtonRow button {
572 | background: #D9D9D9;
573 | color: #1B1B1B;
574 | border: 1px solid #6D6D6D;
575 | border-radius: 4px;
576 | margin: 3px 2px 0 2px;
577 | padding: 2px 5px;
578 | font-weight: bold;
579 | }
580 | #adbsSearchButtonRow button:hover {
581 | opacity: .8;
582 | }
583 | #anisongdbWindow input[type="checkbox"] {
584 | width: 17px;
585 | height: 17px;
586 | margin: 0 0 0 3px;
587 | vertical-align: -4px;
588 | }
589 | #anisongdbWindow button {
590 | color: black;
591 | padding: 0 5px;
592 | }
593 | #anisongdbSearchRow {
594 | margin: 2px;
595 | }
596 | #adbsTable {
597 | width: 100%;
598 | }
599 | #adbsTable th, #adbsTable td {
600 | padding: 0 2px;
601 | }
602 | #adbsTable thead tr {
603 | font-weight: bold;
604 | cursor: pointer;
605 | user-select: none;
606 | }
607 | #adbsTable tbody tr:hover {
608 | color: #70B7FF;
609 | }
610 | #adbsTable .anime {
611 | width: 25%;
612 | }
613 | #adbsTable .artist {
614 | width: 25%;
615 | }
616 | #adbsTable .song {
617 | width: 25%;
618 | }
619 | #adbsTable .type {
620 | width: 10%;
621 | }
622 | #adbsTable .vintage {
623 | width: 15%;
624 | }
625 | table.styledTable thead tr {
626 | background-color: #282828;
627 | }
628 | table.styledTable tbody tr:nth-child(odd) {
629 | background-color: #424242;
630 | }
631 | table.styledTable tbody tr:nth-child(even) {
632 | background-color: #353535;
633 | }
634 | #adbsHotkeyTable th {
635 | font-weight: bold;
636 | padding: 0 20px 5px 0;
637 | }
638 | #adbsHotkeyTable td {
639 | padding: 2px 20px 2px 0;
640 | }
641 | #adbsHotkeyTable input.hk-input {
642 | width: 200px;
643 | color: black;
644 | cursor: pointer;
645 | user-select: none;
646 | }
647 | `;
648 | let style = document.getElementById("anisongdbSearchStyle");
649 | if (style) {
650 | style.textContent = css.trim();
651 | }
652 | else {
653 | style = document.createElement("style");
654 | style.id = "anisongdbSearchStyle";
655 | style.textContent = css.trim();
656 | document.head.appendChild(style);
657 | }
658 | }
659 |
--------------------------------------------------------------------------------
/amqCustomScoreCounter.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name AMQ Custom Score Counter
3 | // @namespace https://github.com/kempanator
4 | // @version 0.8
5 | // @description Adds a user interface to keep track of custom score game modes
6 | // @author kempanator
7 | // @match https://*.animemusicquiz.com/*
8 | // @grant none
9 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js
10 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqWindows.js
11 | // @downloadURL https://github.com/kempanator/amq-scripts/raw/main/amqCustomScoreCounter.user.js
12 | // @updateURL https://github.com/kempanator/amq-scripts/raw/main/amqCustomScoreCounter.user.js
13 | // ==/UserScript==
14 |
15 | "use strict";
16 | if (typeof Listener === "undefined") return;
17 | const loadInterval = setInterval(() => {
18 | if (document.querySelector("#loadingScreen.hidden")) {
19 | clearInterval(loadInterval);
20 | setup();
21 | }
22 | }, 500);
23 |
24 | const saveData = validateLocalStorage("customScoreCounter");
25 | let teams = {}; //[1: {number: 1, players: ["name"], correct: 0, score: 0}]
26 | let soloBonus = 0; //bonus points for solo correct guess
27 | let scoreMap = { 1: {}, 2: {}, 3: {}, 4: {} };
28 | let scoreTableSort = { mode: "team", ascending: true };
29 | let cscWindow;
30 | let hotKeys = {
31 | cscWindow: loadHotkey("cscWindow")
32 | };
33 |
34 | // setup
35 | function setup() {
36 | new Listener("game chat update", (data) => {
37 | for (const message of data.messages) {
38 | if (message.sender === selfName) parseMessage(message.message);
39 | }
40 | }).bindListener();
41 | new Listener("Game Chat Message", (data) => {
42 | if (data.sender === selfName) parseMessage(data.message);
43 | }).bindListener();
44 | new Listener("Game Starting", (data) => {
45 | resetScores();
46 | }).bindListener();
47 | new Listener("Join Game", (data) => {
48 | createTeamTable(data.players || data.quizState?.players);
49 | }).bindListener();
50 | new Listener("Spectate Game", (data) => {
51 | createTeamTable(data.players || data.quizState?.players);
52 | }).bindListener();
53 | new Listener("Player Changed To Spectator", (data) => {
54 | setTimeout(() => {
55 | createTeamTable(Object.values(lobby.players));
56 | }, 0);
57 | }).bindListener();
58 | new Listener("Spectator Change To Player", (data) => {
59 | setTimeout(() => {
60 | createTeamTable(Object.values(lobby.players));
61 | }, 0);
62 | }).bindListener();
63 | new Listener("answer results", (data) => {
64 | if (Object.keys(teams).length === 0) return;
65 | let totalCorrect = 0;
66 | let totalCorrectMap = Object.fromEntries(Object.keys(teams).map(t => [t, 0])); //{teamNumber: #correct}
67 | for (const player of data.players) {
68 | if (player.correct) {
69 | const name = quiz.players[player.gamePlayerId]?.name;
70 | const team = Object.values(teams).find(t => t.players.includes(name));
71 | if (team) {
72 | team.correct += 1;
73 | totalCorrect += 1;
74 | totalCorrectMap[team.number] += 1;
75 | }
76 | }
77 | }
78 | for (const teamNumber of Object.keys(totalCorrectMap)) {
79 | const teamSize = teams[teamNumber].players.length;
80 | if (scoreMap.hasOwnProperty(teamSize)) {
81 | if (scoreMap[teamSize].hasOwnProperty(totalCorrectMap[teamNumber])) {
82 | teams[teamNumber].score += scoreMap[teamSize][totalCorrectMap[teamNumber]];
83 | }
84 | }
85 | }
86 | if (soloBonus && totalCorrect === 1) {
87 | const teamNumber = Object.keys(totalCorrectMap).find(t => totalCorrectMap[t]);
88 | if (teamNumber) teams[teamNumber].score += soloBonus;
89 | }
90 | updateScoreTable();
91 | }).bindListener();
92 |
93 | cscWindow = new AMQWindow({
94 | id: "cscWindow",
95 | title: "",
96 | width: 420,
97 | height: 300,
98 | minWidth: 300,
99 | minHeight: 90,
100 | zIndex: 1060,
101 | resizable: true,
102 | draggable: true
103 | });
104 | cscWindow.addPanel({
105 | id: "cscPanel",
106 | width: 1.0,
107 | height: "100%",
108 | scrollable: { x: true, y: true }
109 | });
110 |
111 | const hotkeyActions = {
112 | cscWindow: () => {
113 | cscWindow.isVisible() ? cscWindow.close() : cscWindow.open();
114 | }
115 | };
116 |
117 | document.addEventListener("keydown", (event) => {
118 | const key = event.key.toUpperCase();
119 | const ctrl = event.ctrlKey;
120 | const alt = event.altKey;
121 | const shift = event.shiftKey;
122 | const match = (b) => {
123 | if (!b.key) return false;
124 | if (key !== b.key) return false;
125 | if (ctrl !== b.ctrl) return false;
126 | if (alt !== b.alt) return false;
127 | if (shift !== b.shift) return false;
128 | return true;
129 | }
130 | for (const [action, bind] of Object.entries(hotKeys)) {
131 | if (match(bind)) {
132 | event.preventDefault();
133 | hotkeyActions[action]();
134 | }
135 | }
136 | });
137 |
138 | $("#qpOptionContainer")
139 | .width((index, width) => width + 35)
140 | .children("div")
141 | .append($("", { id: "qpCSC", class: "clickAble qpOption" })
142 | .append(``)
143 | .on("click", () => {
144 | cscWindow.isVisible() ? cscWindow.close() : cscWindow.open();
145 | })
146 | .popover({
147 | content: "Custom Score Counter",
148 | trigger: "hover",
149 | placement: "bottom"
150 | })
151 | );
152 |
153 | setupcscWindow();
154 | applyStyles();
155 | AMQ_addScriptData({
156 | name: "Custom Score Counter",
157 | author: "kempanator",
158 | version: GM_info.script.version,
159 | link: "https://github.com/kempanator/amq-scripts/raw/main/amqCustomScoreCounter.user.js",
160 | description: `
161 | Type /csc or click the + button in the options bar during quiz to open the custom score counter user interface
162 | `
163 | });
164 | }
165 |
166 | // parse message
167 | function parseMessage(content) {
168 | if (content === "/csc") {
169 | cscWindow.isVisible() ? cscWindow.close() : cscWindow.open();
170 | }
171 | }
172 |
173 | // send chat message
174 | function sendChatMessage(text, teamChat) {
175 | socket.sendCommand({
176 | type: "lobby",
177 | command: "game chat message",
178 | data: { msg: String(text), teamMessage: Boolean(teamChat) }
179 | });
180 | }
181 |
182 | // load hotkey from local storage, input optional default values
183 | function loadHotkey(action, key = "", ctrl = false, alt = false, shift = false) {
184 | const item = saveData.hotKeys?.[action];
185 | return {
186 | key: (item?.key ?? key).toUpperCase(),
187 | ctrl: item?.ctrl ?? item?.ctrlKey ?? ctrl,
188 | alt: item?.alt ?? item?.altKey ?? alt,
189 | shift: item?.shift ?? item?.shiftKey ?? shift
190 | }
191 | }
192 |
193 | // create hotkey rows and add to table
194 | function createHotkeyTable(data) {
195 | const $tbody = $("#cscHotkeyTable tbody");
196 | for (const { action, title } of data) {
197 | const $input = $(" ", { type: "text", class: "hk-input", readonly: true, "data-action": action })
198 | .val(bindingToText(hotKeys[action]))
199 | .on("click", startHotkeyRecord);
200 | $tbody.append($(" ")
201 | .append($("| ", { text: title }))
202 | .append($(" | ").append($input)));
203 | }
204 | }
205 |
206 | // begin hotkey capture on click
207 | function startHotkeyRecord() {
208 | const $input = $(this);
209 | if ($input.hasClass("recording")) return;
210 | const action = $input.data("action");
211 | const capture = (e) => {
212 | e.stopImmediatePropagation();
213 | e.preventDefault();
214 | if (!e.key) return;
215 | if (["Shift", "Control", "Alt", "Meta"].includes(e.key)) return;
216 | if ((e.key === "Delete" || e.key === "Backspace" || e.key === "Escape") && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
217 | hotKeys[action] = {
218 | key: "",
219 | ctrl: false,
220 | alt: false,
221 | shift: false
222 | };
223 | }
224 | else {
225 | hotKeys[action] = {
226 | key: e.key.toUpperCase(),
227 | ctrl: e.ctrlKey,
228 | alt: e.altKey,
229 | shift: e.shiftKey
230 | };
231 | }
232 | saveSettings();
233 | finish();
234 | };
235 | const finish = () => {
236 | document.removeEventListener("keydown", capture, true);
237 | $input.removeClass("recording").val(bindingToText(hotKeys[action])).off("blur", finish);
238 | };
239 | document.addEventListener("keydown", capture, true);
240 | $input.addClass("recording").val("Press keys…").on("blur", finish);
241 | }
242 |
243 | // input hotKeys[action] and convert the data to a string for the input field
244 | function bindingToText(b) {
245 | if (!b) return "";
246 | const keys = [];
247 | if (b.ctrl) keys.push("CTRL");
248 | if (b.alt) keys.push("ALT");
249 | if (b.shift) keys.push("SHIFT");
250 | if (b.key) keys.push(b.key === " " ? "SPACE" : b.key);
251 | return keys.join(" + ");
252 | }
253 |
254 | // get players list
255 | function getPlayers() {
256 | if (lobby.inLobby) {
257 | return Object.values(lobby.players);
258 | }
259 | if (quiz.inQuiz) {
260 | return Object.values(quiz.players);
261 | }
262 | return [];
263 | }
264 |
265 | // crteate team table, input array of Player objects
266 | function createTeamTable(players) {
267 | if (!players) return;
268 | const $tbody = $("#cscTeamTable tbody").empty();
269 | players.sort((a, b) => a.name.localeCompare(b.name));
270 | for (const player of players) {
271 | const team = Object.values(teams).find(t => t.players.includes(player.name));
272 | $tbody.append($(" | ")
273 | .append($("| ", { class: "name", text: player.name }))
274 | .append($(" | ", { class: "team" })
275 | .append($("", { type: "text" }).val(team?.number || ""))
276 | )
277 | );
278 | }
279 | }
280 |
281 | // update team table
282 | function updateTeamTable() {
283 | teams = {};
284 | for (const element of $("#cscTeamTable tbody tr")) {
285 | const $tr = $(element);
286 | const name = $tr.find("td.name").text();
287 | const teamNumber = $tr.find("input").val();
288 | if (!isNaN(teamNumber) && teamNumber > 0) {
289 | if (teams.hasOwnProperty(teamNumber)) {
290 | teams[teamNumber].players.push(name);
291 | }
292 | else {
293 | teams[teamNumber] = { number: teamNumber, players: [name], correct: 0, score: 0 };
294 | }
295 | }
296 | }
297 | }
298 |
299 | // shuffle teams
300 | function shuffleTeams() {
301 | const players = getPlayers();
302 | const teamSize = parseInt($("#cscTeamSizeInput").val());
303 | if (isNaN(teamSize) || teamSize < 1) return;
304 | const numTeams = Math.ceil(players.length / teamSize);
305 | teams = {};
306 | for (let i = 1; i <= numTeams; i++) {
307 | teams[i] = { number: i, players: [], correct: 0, score: 0 };
308 | }
309 | const ids = shuffleArray(players.map(p => p.gamePlayerId));
310 | for (let i = 0; i < ids.length; i++) {
311 | const id = ids[i];
312 | const name = players.find(p => p.gamePlayerId === id)?._name;
313 | teams[i % numTeams + 1].players.push(name);
314 | }
315 | createTeamTable(players);
316 | }
317 |
318 | // import current team setup from lobby
319 | function importTeamsFromLobby() {
320 | const teamSize = parseInt(hostModal.$teamSize.val()) || 0;
321 | if (teamSize > 1) {
322 | $("#cscTeamSizeInput").val(teamSize);
323 | const players = getPlayers();
324 | teams = {};
325 | for (const player of players) {
326 | const teamNumber = player.teamNumber ?? Number(player.lobbySlot.$TEAM_DISPLAY_TEXT.text());
327 | if (teamNumber) {
328 | if (teams.hasOwnProperty(teamNumber)) {
329 | teams[teamNumber].players.push(player.name);
330 | }
331 | else {
332 | teams[teamNumber] = { number: teamNumber, players: [player.name], correct: 0, score: 0 };
333 | }
334 | }
335 | }
336 | createTeamTable(players);
337 | }
338 | else {
339 | messageDisplayer.displayMessage("Invalid team players", "Set the quiz to team mode and this script will import those values");
340 | }
341 | }
342 |
343 | // import current team setup from chat
344 | function importTeamsFromChat() {
345 | const players = getPlayers();
346 | let importStarted = false;
347 | const tempTeams = {};
348 | for (const message of $("#gcMessageContainer .gcMessage").toArray().reverse()) {
349 | const text = $(message).text();
350 | const regex = /^Team ([0-9]+):(.+) - [0-9]+$/.exec(text);
351 | if (regex) {
352 | importStarted = true;
353 | const teamNumber = parseInt(regex[1]);
354 | const names = regex[2].split(/[\s,]+/).filter(Boolean);
355 | if (!tempTeams.hasOwnProperty(teamNumber)) {
356 | tempTeams[teamNumber] = { number: teamNumber, players: names, correct: 0, score: 0 };
357 | }
358 | }
359 | else if (importStarted) {
360 | break;
361 | }
362 | }
363 | if (importStarted) {
364 | teams = tempTeams;
365 | if (Object.keys(teams).length) {
366 | const teamSize = Math.max(...Object.values(teams).map(t => t.players.length));
367 | $("#cscTeamSizeInput").val(teamSize);
368 | }
369 | createTeamTable(players);
370 | }
371 | else {
372 | messageDisplayer.displayMessage("Error", "Couldn't find team list in chat");
373 | }
374 | }
375 |
376 | // update score table
377 | function updateScoreTable() {
378 | if (Object.keys(teams).length === 0) return;
379 | const $tbody = $("#cscScoreTable tbody").empty();
380 | const sortedKeys = Object.keys(teams);
381 | if (scoreTableSort.mode === "team") {
382 | sortedKeys.sort((a, b) => a - b);
383 | }
384 | else if (scoreTableSort.mode === "correct") {
385 | sortedKeys.sort((a, b) => teams[a].correct - teams[b].correct);
386 | }
387 | else if (scoreTableSort.mode === "score") {
388 | sortedKeys.sort((a, b) => teams[a].score - teams[b].score);
389 | }
390 | if (!scoreTableSort.ascending) {
391 | sortedKeys.reverse();
392 | }
393 | for (const key of sortedKeys) {
394 | const team = teams[key];
395 | $tbody.append($(" | ")
396 | .append($("| ", { text: `${team.number}: ${team.players.join(", ")}` }))
397 | .append($(" | ", { class: "correct", text: team.correct }))
398 | .append($(" | ", { class: "score", text: team.score }))
399 | );
400 | }
401 | }
402 |
403 | // create score map user inputs
404 | function createScoreMap(teamSize) {
405 | const $thead = $("#cscScoreMapTable thead").empty();
406 | const $tbody = $("#cscScoreMapTable tbody").empty();
407 | if (teamSize) $("#cscTeamSizeInput").val(teamSize);
408 | else teamSize = parseInt($("#cscTeamSizeInput").val());
409 | if (isNaN(teamSize) || teamSize < 1) return;
410 | $thead.append(` | | # Correct |
|---|
`);
411 | for (let i = 0; i <= teamSize; i++) {
412 | const $tr = $(" ");
413 | for (let j = 0; j <= teamSize + 1; j++) {
414 | const $td = $("");
415 | if (i === 0 && j === 0) {
416 | //$td.text("");
417 | }
418 | else if (i === 0) {
419 | $td.text(j - 1).css("text-align", "center");
420 | }
421 | else if (j === 0) {
422 | $td.text(i + "P").css("padding-right", "5px");
423 | }
424 | else if (j - 1 <= i) {
425 | $td.append($(``).val(scoreMap[i]?.[j - 1] ?? ""));
426 | }
427 | $tr.append($td);
428 | }
429 | $tbody.append($tr);
430 | }
431 | }
432 |
433 | // read score map user inputs and update score maps
434 | function updateScoreMap() {
435 | const inputs = $("#cscScoreMapTable input").toArray().map(x => parseFloat(x.value) || 0);
436 | if (inputs.length === 0) return;
437 | scoreMap = {};
438 | let start = 0;
439 | let teamSize = 1;
440 | while (start < inputs.length) {
441 | scoreMap[teamSize] = Object.assign({}, inputs.slice(start, start + teamSize + 1));
442 | start += teamSize + 1;
443 | teamSize++;
444 | }
445 | }
446 |
447 | // reset scores for all teams
448 | function resetScores() {
449 | if (Object.keys(teams).length === 0) return;
450 | for (const team of Object.values(teams)) {
451 | team.correct = 0;
452 | team.score = 0;
453 | }
454 | updateScoreTable();
455 | }
456 |
457 | // reset all tabs and switch to the inputted tab
458 | function switchTab(tab) {
459 | const $w = $("#cscWindow");
460 | $w.find(".tab").removeClass("selected");
461 | $w.find(".tabSection").hide();
462 | $w.find(`#${tab}Tab`).addClass("selected");
463 | $w.find(`#${tab}Container`).show();
464 | }
465 |
466 | // used for sorting table contents when you click a header
467 | function tableSortChange(obj, mode) {
468 | if (obj.mode === mode) {
469 | obj.ascending = !obj.ascending;
470 | }
471 | else {
472 | obj.mode = mode;
473 | obj.ascending = true;
474 | }
475 | }
476 |
477 | // setup csc window
478 | function setupcscWindow() {
479 | cscWindow.window.find(".modal-header").empty()
480 | .append($("", { class: "fa fa-times clickAble", style: "font-size: 25px; top: 8px; right: 15px; position: absolute;", "aria-hidden": "true" })
481 | .on("click", () => {
482 | cscWindow.close();
483 | }))
484 | .append($("", { text: "Custom Score Counter" }))
485 | .append($("", { class: "tabContainer" })
486 | .append($(" ", { id: "cscTeamTab", class: "tab clickAble selected" })
487 | .append($(" ", { text: "Teams" }))
488 | .on("click", () => {
489 | switchTab("cscTeam");
490 | }))
491 | .append($("", { id: "cscScoreTab", class: "tab clickAble" })
492 | .append($(" ", { text: "Score" }))
493 | .on("click", () => {
494 | switchTab("cscScore");
495 | }))
496 | .append($("", { id: "cscSettingsTab", class: "tab clickAble" })
497 | .append($(" ", { text: "Settings" }))
498 | .on("click", () => {
499 | switchTab("cscSettings");
500 | }))
501 | );
502 | cscWindow.panels[0].panel
503 | .append($("", { id: "cscTeamContainer", class: "tabSection", style: "padding: 10px;" })
504 | .append($(" | |