Greasy Fork is available in English.

VoidVerified

Social enhancements for AniList.

// ==UserScript==
// @name        VoidVerified
// @namespace   http://tampermonkey.net/
// @version     1.14.0
// @author      voidnyan
// @source      https://github.com/voidnyan/void-verified
// @license     MIT
// @match       https://anilist.co/*
// @grant       GM.xmlHttpRequest
// @description Social enhancements for AniList.
// ==/UserScript==

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
var __webpack_exports__ = {};

;// CONCATENATED MODULE: ./src/assets/defaultSettings.ts
const categories = {
    users: "users",
    paste: "paste",
    css: "css",
    misc: "misc",
};
const defaultSettings = {
    copyColorFromProfile: {
        defaultValue: true,
        description: "Copy user color from their profile.",
        category: categories.users,
    },
    moveSubscribeButtons: {
        defaultValue: false,
        description: "Move activity subscribe button next to comments and likes.",
        category: categories.misc,
    },
    hideLikeCount: {
        defaultValue: false,
        description: "Hide activity and reply like counts.",
        category: categories.misc,
    },
    enabledForUsername: {
        defaultValue: true,
        description: "Display a verified sign next to usernames.",
        category: categories.users,
    },
    enabledForProfileName: {
        defaultValue: false,
        description: "Display a verified sign next to a profile name.",
        category: categories.users,
    },
    defaultSign: {
        defaultValue: "✔",
        description: "The default sign displayed next to a username.",
        category: categories.users,
    },
    highlightEnabled: {
        defaultValue: true,
        description: "Highlight user activity with a border.",
        category: categories.users,
    },
    highlightEnabledForReplies: {
        defaultValue: true,
        description: "Highlight replies with a border.",
        category: categories.users,
    },
    highlightSize: {
        defaultValue: "5px",
        description: "Width of the highlight border.",
        category: categories.users,
    },
    colorUserActivity: {
        defaultValue: false,
        description: "Color user activity links with user color.",
        category: categories.users,
    },
    colorUserReplies: {
        defaultValue: false,
        description: "Color user reply links with user color.",
        category: categories.users,
    },
    useDefaultHighlightColor: {
        defaultValue: false,
        description: "Use fallback highlight color when user color is not specified.",
        category: categories.users,
    },
    defaultHighlightColor: {
        defaultValue: "#FFFFFF",
        description: "Fallback highlight color.",
        category: categories.users,
    },
    globalCssEnabled: {
        defaultValue: false,
        description: "Enable custom global CSS.",
        category: categories.css,
    },
    globalCssAutoDisable: {
        defaultValue: true,
        description: "Disable global CSS when a profile has custom CSS.",
        category: categories.css,
    },
    profileCssEnabled: {
        defaultValue: false,
        description: "Load user's custom CSS when viewing their profile.",
        category: categories.css,
        authRequired: true,
    },
    activityCssEnabled: {
        defaultValue: false,
        description: "Load user's custom CSS when viewing their activity (direct link).",
        category: categories.css,
        authRequired: true,
    },
    onlyLoadCssFromVerifiedUser: {
        defaultValue: false,
        description: "Only load custom CSS from verified users.",
        category: categories.css,
    },
    layoutDesignerEnabled: {
        defaultValue: false,
        description: "Enable Layout Designer in the settings tab.",
        category: categories.misc,
        authRequired: true,
    },
    replaceNotifications: {
        defaultValue: false,
        description: "Replace AniList notification system.",
        category: categories.misc,
        authRequired: true,
    },
    quickAccessNotificationsEnabled: {
        defaultValue: false,
        description: "Display quick access of notifications in home page.",
        category: categories.misc,
        authRequired: true,
    },
    quickAccessEnabled: {
        defaultValue: false,
        description: "Display quick access of users in home page.",
        category: categories.users,
    },
    quickAccessBadge: {
        defaultValue: false,
        description: "Display a badge on quick access when changes are detected on user's layout.",
        category: categories.users,
    },
    quickAccessTimer: {
        defaultValue: true,
        description: "Display a timer until next update of Quick Access.",
        category: categories.users,
    },
    pasteEnabled: {
        defaultValue: false,
        description: "Automatically wrap pasted links and images with link and image tags.",
        category: categories.paste,
    },
    pasteWrapImagesWithLink: {
        defaultValue: false,
        description: "Wrap images with a link tag.",
        category: categories.paste,
    },
    pasteImageWidth: {
        defaultValue: "420",
        description: "Width used when pasting images.",
        category: categories.paste,
    },
    pasteImagesToHostService: {
        defaultValue: false,
        description: "Upload image from the clipboard to image host (configure below).",
        category: categories.paste,
    },
    toasterEnabled: {
        defaultValue: true,
        description: "Enable toast notifications.",
        category: categories.misc,
    },
    // useElevatedFetch: {
    // 	defaultValue: false,
    // 	description:
    // 		"Query AniList API with elevated browser access (this might solve some API issues).",
    // 	category: categories.misc,
    // },
    removeAnilistBlanks: {
        defaultValue: false,
        description: "Open AniList links in the same tab.",
        category: categories.misc,
    },
    gifKeyboardEnabled: {
        defaultValue: false,
        description: "Add a GIF keyboard to activity editor.",
        category: categories.paste,
    },
    gifKeyboardLikeButtonsEnabled: {
        defaultValue: true,
        description: "Add like buttons to add media to GIF keyboard.",
        category: categories.paste,
    },
    changeLogEnabled: {
        defaultValue: true,
        description: "Display a changelog when a new version is detected.",
        category: categories.misc,
    },
    selfMessageEnabled: {
        defaultValue: false,
        description: "Enable a self-message button on your profile (requires authentication).",
        category: categories.misc,
        authRequired: true,
    },
    hideMessagesFromListFeed: {
        defaultValue: false,
        description: "Fix AniList bug where private messages are displayed in List activity feed.",
        category: categories.misc,
    },
    csspyEnabled: {
        defaultValue: false,
        description: "Enable CSSpy in Layout & CSS tab.",
        category: categories.css,
    },
    replyActivityUpdate: {
        defaultValue: false,
        description: "Add insta-reply to activity update in home feed.",
        category: categories.misc,
        authRequired: true,
    },
};

;// CONCATENATED MODULE: ./src/utils/colorFunctions.js
class ColorFunctions {
	static hexToRgb(hex) {
		const r = parseInt(hex.slice(1, 3), 16);
		const g = parseInt(hex.slice(3, 5), 16);
		const b = parseInt(hex.slice(5, 7), 16);

		return `${r}, ${g}, ${b}`;
	}

	static rgbToHex(rgb) {
		const [r, g, b] = rgb.split(",");
		const hex = this.generateHex(r, g, b);
		return hex;
	}

	static generateHex(r, g, b) {
		return (
			"#" +
			[r, g, b]
				.map((x) => {
					const hex = Number(x).toString(16);
					return hex.length === 1 ? "0" + hex : hex;
				})
				.join("")
		);
	}

	static defaultColors = [
		"blue",
		"purple",
		"green",
		"orange",
		"red",
		"pink",
		"gray",
	];

	static defaultColorRgb = {
		gray: "103, 123, 148",
		blue: "61, 180, 242",
		purple: "192, 99, 255",
		green: "76, 202, 81",
		orange: "239, 136, 26",
		red: "225, 51, 51",
		pink: "252, 157, 214",
	};

	static handleAnilistColor(color) {
		if (this.defaultColors.includes(color)) {
			return this.defaultColorRgb[color];
		}

		return this.hexToRgb(color);
	}
}

;// CONCATENATED MODULE: ./src/utils/DOM.js
class DOM_DOM {
	static create(element, classes = null, children = null) {
		const el = document.createElement(element);
		if (classes !== null) {
			for (const className of classes?.split(" ")) {
				if (className.startsWith("#")) {
					el.setAttribute("id", `void-${className.slice(1)}`);
					continue;
				}
				el.classList.add(`void-${className}`);
			}
		}

		if (children) {
			if (Array.isArray(children)) {
				el.append(...children);
			} else {
				el.append(children);
			}
		}

		return el;
	}

	static render(element, parent) {
		const htmlElement = document.createElement(element);
		parent.append(htmlElement);
	}

	static renderReplace(element, parent) {
		const htmlElement = document.createElement(element);
		htmlElement.setAttribute("title", "wow");
		parent.replaceChildren(htmlElement);
	}

	static getOrCreate(element, classes) {
		const id = classes
			.split(" ")
			.find((className) => className.startsWith("#"));
		return this.get(id) ?? this.create(element, classes);
	}

	static get(selector) {
		const convertedSelector = this.#convertSelector(selector);
		return document.querySelector(convertedSelector);
	}

	static getAll(selector) {
		const convertedSelector = this.#convertSelector(selector);
		return document.querySelectorAll(convertedSelector);
	}

	static #convertSelector(selector) {
		let results = [];
		for (const className of selector?.split(" ")) {
			if (className.startsWith("#")) {
				results.push(`#void-${className.slice(1)}`);
				continue;
			}
			results.push(`.void-${className}`);
		}
		return results.join(" ");
	}
}

;// CONCATENATED MODULE: ./src/assets/icons.js
// MIT License

// Copyright (c) 2020 Refactoring UI Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

const RefreshIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="w-6 h-6"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
            />
        </svg>`,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const EyeIcon = () => {
	const icon = new DOMParser().parseFromString(
		`	<svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                stroke-width="1.5"
                stroke="currentColor"
                class="w-6 h-6"
            >
                <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
                />
                <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
                />
            </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const EyeClosedIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="w-6 h-6"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
            />
        </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const TrashcanIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="w-6 h-6"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
            />
        </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const AddIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="w-6 h-6"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
            />
        </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};
const GifIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="w-6 h-6"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M12.75 8.25v7.5m6-7.5h-3V12m0 0v3.75m0-3.75H18M9.75 9.348c-1.03-1.464-2.698-1.464-3.728 0-1.03 1.465-1.03 3.84 0 5.304 1.03 1.464 2.699 1.464 3.728 0V12h-1.5M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"
            />
        </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};
const HeartIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="currentColor"
                class="w-6 h-6"
            >
                <path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
            </svg>;
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};
const DoubleChevronLeftIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
      </svg>
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};
const DoubleChevronRightIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
      </svg>
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const CloseIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
      </svg>

      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const CogIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
        <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
      </svg>
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const PensilSquareIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
      </svg>

      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const CheckBadgeIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 0 1-1.043 3.296 3.745 3.745 0 0 1-3.296 1.043A3.745 3.745 0 0 1 12 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 0 1-3.296-1.043 3.745 3.745 0 0 1-1.043-3.296A3.745 3.745 0 0 1 3 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 0 1 1.043-3.296 3.746 3.746 0 0 1 3.296-1.043A3.746 3.746 0 0 1 12 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 0 1 3.296 1.043 3.746 3.746 0 0 1 1.043 3.296A3.745 3.745 0 0 1 21 12Z" />
      </svg>`,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const SearchDocumentIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
        <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Zm3.75 11.625a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
      </svg>
      `,
		"text/html",
	).body.childNodes[0];
	return icon;
};

const FilmIcon = () => {
	const icon = new DOMParser().parseFromString(
		`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
				  <path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h1.5C5.496 19.5 6 18.996 6 18.375m-3.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-1.5A1.125 1.125 0 0 1 18 18.375M20.625 4.5H3.375m17.25 0c.621 0 1.125.504 1.125 1.125M20.625 4.5h-1.5C18.504 4.5 18 5.004 18 5.625m3.75 0v1.5c0 .621-.504 1.125-1.125 1.125M3.375 4.5c-.621 0-1.125.504-1.125 1.125M3.375 4.5h1.5C5.496 4.5 6 5.004 6 5.625m-3.75 0v1.5c0 .621.504 1.125 1.125 1.125m0 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m1.5-3.75C5.496 8.25 6 7.746 6 7.125v-1.5M4.875 8.25C5.496 8.25 6 8.754 6 9.375v1.5m0-5.25v5.25m0-5.25C6 5.004 6.504 4.5 7.125 4.5h9.75c.621 0 1.125.504 1.125 1.125m1.125 2.625h1.5m-1.5 0A1.125 1.125 0 0 1 18 7.125v-1.5m1.125 2.625c-.621 0-1.125.504-1.125 1.125v1.5m2.625-2.625c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125M18 5.625v5.25M7.125 12h9.75m-9.75 0A1.125 1.125 0 0 1 6 10.875M7.125 12C6.504 12 6 12.504 6 13.125m0-2.25C6 11.496 5.496 12 4.875 12M18 10.875c0 .621-.504 1.125-1.125 1.125M18 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m-12 5.25v-5.25m0 5.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125m-12 0v-1.5c0-.621-.504-1.125-1.125-1.125M18 18.375v-5.25m0 5.25v-1.5c0-.621.504-1.125 1.125-1.125M18 13.125v1.5c0 .621.504 1.125 1.125 1.125M18 13.125c0-.621.504-1.125 1.125-1.125M6 13.125v1.5c0 .621-.504 1.125-1.125 1.125M6 13.125C6 12.504 5.496 12 4.875 12m-1.5 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M19.125 12h1.5m0 0c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h1.5m14.25 0h1.5" />
				</svg>
				`,
		"text/html",
	).body.childNodes[0];
	return icon;
}

;// CONCATENATED MODULE: ./src/components/components.js



const ColorPicker = (value, onChange) => {
	const container = DOM_DOM.create("div", "color-picker-container");
	const colorPicker = DOM_DOM.create("input", "color-picker");
	colorPicker.setAttribute("type", "color");
	colorPicker.value = value;
	colorPicker.addEventListener("change", (event) => {
		onChange(event);
	});

	container.append(colorPicker);
	const inputField = DOM_DOM.create("input", "color-picker-input");
	inputField.value = value ?? "#";
	inputField.setAttribute("type", "text");
	inputField.addEventListener("change", (event) => {
		onChange(event);
	});
	container.append(inputField);
	return container;
};

const InputField = (value, onChange, classes) => {
	const inputField = DOM_DOM.create("input", transformClasses("input", classes));
	inputField.value = value;
	inputField.addEventListener("change", (event) => {
		onChange(event);
	});
	return inputField;
};

const SecretField = (value, onChange) => {
	const secret = InputField(value, onChange);
	secret.setAttribute("type", "password");
	const eyeIcon = EyeIcon();
	const closedEyeIcon = EyeClosedIcon();

	const container = DOM_DOM.create("div", "action-container", secret);
	const iconButton = IconButton(eyeIcon, (event) => {
		if (event.target.firstChild === eyeIcon) {
			event.target.replaceChildren(closedEyeIcon);
			secret.setAttribute("type", "text");
			return;
		}
		event.target.replaceChildren(eyeIcon);
		secret.setAttribute("type", "password");
	});
	container.append(iconButton);
	return container;
};

const ActionInputField = (value, onClick, icon) => {
	const inputField = InputField(value, () => {});

	const container = DOM_DOM.create("div", "action-container", inputField);
	const iconButton = IconButton(icon, (event) => {
		onClick(event, inputField);
	});
	container.append(iconButton);
	return container;
};

const RangeField = (value, onChange, max, step = 1, min = 0, unit) => {
	const container = DOM_DOM.create("div", "range-container");
	const range = DOM_DOM.create("input", "range");
	const display = DOM_DOM.create("div", "range-display", `${value}${unit ?? ""}`);
	range.setAttribute("type", "range");
	range.addEventListener("change", (event) => {
		onChange(event);
	});
	range.addEventListener("input", (event) => {
		display.replaceChildren(`${event.target.value}${unit ?? ""}`);
	});
	range.setAttribute("max", max);
	range.setAttribute("min", min);
	range.setAttribute("step", step);
	range.setAttribute("value", value);
	container.append(range, display);
	return container;
};

const Image = (url, classes) => {
	const image = DOM_DOM.create("img", classes);
	image.setAttribute("src", url);
	image.setAttribute("draggable", false);
	return image;
};

const Button = (text, onClick, classes) => {
	const button = DOM_DOM.create(
		"button",
		transformClasses("button", classes),
		text,
	);
	button.addEventListener("click", (event) => {
		onClick(event);
	});
	return button;
};

const IconButton = (text, onClick, classes) => {
	const button = DOM_DOM.create(
		"div",
		transformClasses("icon-button", classes),
		text,
	);
	button.addEventListener("click", (event) => {
		onClick(event);
	});
	return button;
};

const Note = (text) => {
	const note = DOM_DOM.create("div", "notice", text);
	return note;
};

const Link = (text, href, target = "_blank", classes) => {
	const link = DOM_DOM.create("a", transformClasses("link", classes), text);
	link.setAttribute("href", href);
	link.setAttribute("target", target);
	return link;
};

const TextArea = (text, onChange, classes) => {
	const textArea = DOM_DOM.create(
		"textarea",
		transformClasses("textarea", classes),
		text,
	);
	textArea.addEventListener("change", (event) => {
		onChange(event);
	});

	return textArea;
};

const Toast = (message, type) => {
	const toast = DOM_DOM.create("div", transformClasses("toast", type), message);
	return toast;
};

const Select = (options) => {
	const container = DOM_DOM.create("div", "select");
	for (const option of options) {
		container.append(option);
	}
	return container;
};

const Option = (value, selected, onClick) => {
	const option = DOM_DOM.create("div", "option", value);
	if (selected) {
		option.classList.add("active");
	}
	option.addEventListener("click", onClick);
	return option;
};

const Label = (text, element) => {
	const container = DOM_DOM.create("div", "label-container");
	const label = DOM_DOM.create("label", "label-span", text);
	const id = Math.random();
	label.setAttribute("for", id);
	element.setAttribute("id", id);
	container.append(label, element);
	return container;
};

const Table = (head, body) => {
	const table = DOM_DOM.create("table", "table", [head, body]);
	return table;
};

const TableHead = (...headers) => {
	const headerCells = headers.map((header) => DOM_DOM.create("th", null, header));
	const headerRow = DOM_DOM.create("tr", null, headerCells);
	const head = DOM_DOM.create("thead", null, headerRow);
	return head;
};

const TableBody = (rows) => {
	const tableBody = DOM_DOM.create("tbody", null, rows);
	return tableBody;
};

const Checkbox = (checked, onChange, disabled = false) => {
	const checkbox = DOM_DOM.create("input", "checkbox");
	checkbox.setAttribute("type", "checkbox");
	checkbox.checked = checked;

	if (disabled) {
		checkbox.setAttribute("disabled", "");
	}

	checkbox.addEventListener("change", onChange);
	return checkbox;
};

const SettingLabel = (text, input) => {
	const container = DOM_DOM.create("div", "setting-label-container", input);
	const label = DOM_DOM.create("label", "setting-label", text);
	const id = Math.random();
	label.setAttribute("for", id);
	input.setAttribute("id", id);
	container.append(label);
	return container;
};

const GifKeyboard = (header) => {
	const container = DOM_DOM.create("div", "gif-keyboard-container");
	container.append(header);
	const gifList = DOM_DOM.create("div", "gif-keyboard-list");
	const controls = DOM_DOM.create("div", "gif-keyboard-control-container");
	container.append(
		DOM_DOM.create("div", "gif-keyboard-list-container", [controls, gifList]),
	);

	return container;
};

const GifItem = (url, onClick, onLike, gifs) => {
	const container = DOM_DOM.create("div", "gif-keyboard-item");
	container.addEventListener("click", () => {
		onClick();
	});
	const img = DOM_DOM.create("img");
	img.setAttribute("src", url);
	container.append(GifContainer(img, onLike, gifs));
	return container;
};

const GifContainer = (imgElement, onLike, gifs) => {
	const container = DOM_DOM.create("div", "gif-like-container");
	container.append(
		imgElement,
		IconButton(
			HeartIcon(),
			(event) => {
				event.stopPropagation();
				event.preventDefault();
				onLike(event);
				if (event.target.classList.contains("void-liked")) {
					event.target.classList.remove("void-liked");
				} else {
					event.target.classList.add("void-liked");
				}
			},
			transformClasses(
				"gif-like",
				gifs.includes(imgElement.src) && "liked",
			),
		),
	);
	return container;
};

const Pagination = (currentPage, maxPage, onClick) => {
	const container = DOM_DOM.create("div", "pagination-container");

	if (maxPage < 1) {
		return container;
	}

	let displayedPages = [];
	if (maxPage >= 3) {
		container.append(
			IconButton(
				DoubleChevronLeftIcon(),
				() => {
					onClick(0);
				},
				`pagination-skip ${currentPage === 0 && "active"}`,
			),
		);
		if (currentPage >= maxPage - 1) {
			displayedPages.push(maxPage - 2, maxPage - 1, maxPage);
		} else if (currentPage === 0) {
			displayedPages.push(currentPage, currentPage + 1, currentPage + 2);
		} else {
			displayedPages.push(currentPage - 1, currentPage, currentPage + 1);
		}
	} else {
		for (let i = 0; i <= maxPage; i++) {
			displayedPages.push(i);
		}
	}

	for (const page of displayedPages) {
		container.append(
			IconButton(
				page + 1,
				() => {
					onClick(page);
				},
				currentPage === page && "active",
			),
		);
	}

	if (maxPage >= 3) {
		container.append(
			IconButton(
				DoubleChevronRightIcon(),
				() => {
					onClick(maxPage);
				},
				`pagination-skip ${currentPage === maxPage && "active"}`,
			),
		);
	}

	return container;
};

const Modal = (content, onClose) => {
	const background = DOM_DOM.create("dialog", "modal-background");
	const container = DOM_DOM.create("div", "modal");

	const closeButton = IconButton(CloseIcon(), (event) => {
		background.close();
		onClose(event);
	});

	const header = DOM_DOM.create("div", "modal-header", "VoidVerified");
	header.append(closeButton);
	container.append(header);

	const contentContainer = DOM_DOM.create("div", "modal-content", content);
	container.append(contentContainer);
	background.append(container);

	background.setAttribute("open", true);
	return background;
};

const Tooltip = (text, child) => {
	const tooltipContainer = DOM_DOM.create("div", "tooltip-container");
	const tooltip = DOM_DOM.create("div", "tooltip", text);
	tooltipContainer.append(tooltip);
	tooltipContainer.append(child);
	return tooltipContainer;
};

const Chip = (text) => {
	const chip = DOM.create("span", "chip", text);
	return chip;
};

const transformClasses = (base, additional) => {
	let classes = base;
	if (additional && additional !== "") {
		classes += ` ${additional}`;
	}
	return classes;
};

;// CONCATENATED MODULE: ./src/utils/toaster.js



const toastTypes = {
	info: "info",
	success: "success",
	warning: "warning",
	error: "error",
};

const toastLevels = {
	info: 0,
	success: 1,
	warning: 2,
	error: 3,
};

const toastDurations = [1, 3, 5, 10];

const toastLocations = ["top-left", "top-right", "bottom-left", "bottom-right"];

class ToasterConfig {
	toastLevel;
	duration;
	location;

	constructor(config) {
		this.toastLevel = config?.toastLevel ?? 2;
		this.duration = config?.duration ?? 5;
		this.location = config?.location ?? "bottom-left";
	}
}

class ToastInstance {
	type;
	message;
	duration;
	// durationLeft;
	// interval;
	constructor(message, type, duration) {
		this.type = type;
		this.message = message;
		this.duration = duration * 1000;
	}

	toast() {
		const toast = Toast(this.message, this.type);
		this.durationLeft = this.duration;
		// This code can be used for a visual indicator

		// this.interval = setInterval(
		// 	(toast) => {
		// 		if (this.durationLeft <= 0) {
		// 			this.delete(toast);
		// 			clearInterval(this.interval);
		// 			return;
		// 		}
		// 		this.durationLeft -= 100;
		// 	},
		// 	100,
		// 	toast
		// );

		setTimeout(() => {
			this.delete(toast);
		}, this.duration);
		return toast;
	}

	delete(toast) {
		toast.remove();
	}
}

class Toaster {
	static #config;
	static #configInLocalStorage = "void-verified-toaster-config";
	static #settings;

	static initializeToaster(settings) {
		this.#settings = settings;
		const config = JSON.parse(
			localStorage.getItem(this.#configInLocalStorage),
		);
		this.#config = new ToasterConfig(config);
		const toastContainer = DOM_DOM.create(
			"div",
			`#toast-container ${this.#config.location}`,
		);
		document.body.append(toastContainer);
	}

	static debug(message) {
		if (!this.#shouldToast(toastTypes.info)) {
			return;
		}
		DOM_DOM.get("#toast-container").append(
			new ToastInstance(
				message,
				toastTypes.info,
				this.#config.duration,
			).toast(),
		);
	}

	static success(message) {
		if (!this.#shouldToast(toastTypes.success)) {
			return;
		}

		DOM_DOM.get("#toast-container").append(
			new ToastInstance(
				message,
				toastTypes.success,
				this.#config.duration,
			).toast(),
		);
	}

	static warning(message) {
		if (!this.#shouldToast(toastTypes.warning)) {
			return;
		}

		DOM_DOM.get("#toast-container").append(
			new ToastInstance(
				message,
				toastTypes.warning,
				this.#config.duration,
			).toast(),
		);
	}

	static error(message) {
		if (!this.#shouldToast(toastTypes.error)) {
			return;
		}

		DOM_DOM.get("#toast-container").append(
			new ToastInstance(
				message,
				toastTypes.error,
				this.#config.duration,
			).toast(),
		);
	}

	static critical(message) {
		DOM_DOM.get("#toast-container").append(
			new ToastInstance(message, toastTypes.error, 8).toast(),
		);
	}

	static notify(message) {
		DOM_DOM.get("#toast-container").append(
			new ToastInstance(message, toastTypes.info, this.#config.duration).toast(),
		);
	}

	static #shouldToast(type) {
		return (
			this.#settings.options.toasterEnabled.getValue() &&
			this.#config.toastLevel <= toastLevels[type]
		);
	}

	static renderSettings(settingsUi) {
		const container = DOM_DOM.create("div");

		container.append(DOM_DOM.create("h3", null, "Configure Toasts"));

		container.append(
			DOM_DOM.create(
				"p",
				null,
				"Toasts are notifications that pop up in the corner of your screen when things are happening.",
			),
		);

		const options = Object.values(toastTypes).map((type) =>
			Option(type, this.#config.toastLevel === toastLevels[type], () => {
				this.#handleLevelChange(type);
				settingsUi.renderSettingsUiContent();
			}),
		);
		container.append(Label("Toast level", Select(options)));

		const locationOptions = toastLocations.map((location) =>
			Option(location, this.#config.location === location, () => {
				this.#handleLocationChange(location);
				settingsUi.renderSettingsUiContent();
			}),
		);

		container.append(Label("Toast location", Select(locationOptions)));

		const durationOptions = toastDurations.map((duration) =>
			Option(`${duration}s`, duration === this.#config.duration, () => {
				this.#handleDurationChange(duration);
				settingsUi.renderSettingsUiContent();
			}),
		);

		container.append(Label("Toast duration", Select(durationOptions)));

		container.append(
			Button("Test Toasts", () => {
				Toaster.debug("This is a debug toast.");
				Toaster.success("This is a success toast.");
				Toaster.warning("This is a warning toast.");
				Toaster.error("This is an error toast.");
			}),
		);

		return container;
	}

	static #handleLevelChange(type) {
		this.#config.toastLevel = toastLevels[type];
		this.#saveConfig();
	}

	static #handleLocationChange(location) {
		this.#config.location = location;
		this.#saveConfig();

		const container = DOM_DOM.get("#toast-container");
		for (const className of container.classList) {
			container.classList.remove(className);
		}
		container.classList.add(`void-${location}`);
	}

	static #handleDurationChange(duration) {
		this.#config.duration = duration;
		this.#saveConfig();
	}

	static #saveConfig() {
		localStorage.setItem(
			this.#configInLocalStorage,
			JSON.stringify(this.#config),
		);
	}
}

;// CONCATENATED MODULE: ./src/api/anilistAPI.js


class AnilistAPI {
	settings;
	#userId;
	#url = "https://graphql.anilist.co";
	constructor(settings) {
		this.settings = settings;
		this.#userId = Number(JSON.parse(localStorage.getItem("auth")).id);
	}

	async getActivityCss(activityId) {
		const query = `query ($activityId: Int) {
            Activity(id: $activityId) {
                ... on ListActivity {
                    user {
                        name
                        about
                        options {
                            profileColor
                        }
                }}
                ... on TextActivity {
                    user {
                        name
                        about
                        options {
                            profileColor
                        }
                    }
                }
                ... on MessageActivity {
                    recipient {
                        name
                        about
                        options {
                            profileColor
                        }
                    }
                }
            }
        }`;

		const variables = { activityId };
		const options = this.#getQueryOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return data.Activity;
		} catch (error) {
			throw new Error("Error querying activity.", error);
		}
	}

	async getUserAbout(username) {
		const query = `query ($username: String) {
            User(name: $username) {
                about
            }
        }`;

		const variables = { username };
		const options = this.#getQueryOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return data.User.about;
		} catch (error) {
			throw new Error("Error querying user about.", error);
		}
	}

	async saveUserAbout(about) {
		const query = `mutation ($about: String) {
            UpdateUser(about: $about) {
                about
            }
        }`;
		const variables = { about };
		const options = this.#getMutationOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return data;
		} catch (error) {
			throw new Error("failed to save user about.", error);
		}
	}

	async saveUserColor(color) {
		const query = `mutation ($color: String) {
            UpdateUser(profileColor: $color) {
                options {
                    profileColor
                }
            }
        }`;

		const variables = { color };
		const options = this.#getMutationOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return data;
		} catch (error) {
			throw new Error("Failed to publish profile color", error);
		}
	}

	async saveDonatorBadge(text) {
		const query = `mutation ($text: String) {
            UpdateUser(donatorBadge: $text) {
                donatorBadge
            }
        }`;

		const variables = { text };
		const options = this.#getMutationOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return data;
		} catch (error) {
			throw new Error("Failed to publish donator badge", error);
		}
	}

	async queryVerifiedUsers() {
		const accountUser = await this.queryUser(this.settings.anilistUser);
		this.settings.updateUserFromApi(accountUser);
		await this.#queryUsers(1);
	}

	async queryUser(username) {
		const variables = { username };
		const query = `query ($username: String!) {
                User(name: $username) {
                  name
                  id
                  avatar {
                    large
                  }
                  bannerImage
                  options {
                    profileColor
                  }
              }
            }
        `;

		const options = this.#getQueryOptions(query, variables);

		try {
			const data = await this.#elevatedFetch(options);
			return data.User;
		} catch (error) {
			throw new Error("Failed to query user from Anilist API", error);
		}
	}

	async selfMessage(message) {
		const variables = { message, recipientId: this.#userId };
		const query = `
            mutation($recipientId: Int, $message: String) {
                SaveMessageActivity(message: $message, private: false, recipientId: $recipientId) {
                    id
                }
            }
        `;

		const options = this.#getMutationOptions(query, variables);

		try {
			const data = await this.#elevatedFetch(options);
			return data.SaveMessageActivity;
		} catch (error) {
			throw new Error("Failed to publish a self-message");
		}
	}

	async getNotifications(
		notificationTypes,
		page = 1,
		resetNotificationCount = false,
	) {
		const query = `
        query($notificationTypes: [NotificationType], $page: Int, $resetNotificationCount: Boolean) {
            Page(page: $page) {
                notifications(type_in: $notificationTypes,
                    resetNotificationCount: $resetNotificationCount) {
                    ... on ActivityMessageNotification {${activityQuery}}
                    ... on ActivityReplyNotification {${activityQuery}}
                    ... on ActivityMentionNotification{${activityQuery}}
                    ... on ActivityReplySubscribedNotification{${activityQuery}}
                    ... on ActivityLikeNotification{${activityQuery}}
                    ... on ActivityReplyLikeNotification{${activityQuery}}
                    ... on FollowingNotification{${followingQuery}}
                    ... on AiringNotification{${airingQuery}}
                    ... on RelatedMediaAdditionNotification{${relatedMediaQuery}}
                    ... on ThreadCommentMentionNotification{${threadCommentQuery}}
                    ... on ThreadCommentReplyNotification{${threadCommentQuery}}
                    ... on ThreadCommentSubscribedNotification{${threadCommentQuery}}
                    ... on ThreadCommentLikeNotification{${threadCommentQuery}}
                    ... on ThreadLikeNotification{${threadQuery}}
                    ... on MediaDataChangeNotification{${mediaDataChange}}
                    ... on MediaDeletionNotification{${mediaDeleted}}
                }
                pageInfo {
                    currentPage
                    hasNextPage
                }
            }
        }`;

		const variables = {
			notificationTypes,
			page,
			resetNotificationCount,
		};
		const options = this.#getMutationOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			return [data.Page.notifications, data.Page.pageInfo];
		} catch (error) {
			console.error(error);
			throw new Error("Failed to query notifications.");
		}
	}

	async getActivityNotificationRelations(activityIds) {
		const userQuery = `
            name
            avatar {
                large
            }`;

		const activitiesQuery = ` ... on ListActivity {
                id
                type
                media {
                    coverImage {large}
                    id
                    type
                }
            }
            ... on TextActivity {
                id
                type
                user {${userQuery}}
            }
            ... on MessageActivity {
                id
                type
                recipient {${userQuery}}
            }`;
		const query = `query($activityIds: [Int]) {
            public: Page(page: 1) {
                activities(id_in: $activityIds, isFollowing: true) {${activitiesQuery}}
            }
            following: Page(page: 1) {
                activities(id_in: $activityIds, isFollowing: false) {${activitiesQuery}}
            }
        }`;

		const variables = { activityIds };
		const options = this.#getMutationOptions(query, variables);
		try {
			const data = await this.#elevatedFetch(options);
			const activities = new Set([
				...data.public.activities,
				...data.following.activities,
			]);
			return Array.from(activities);
		} catch (error) {
			console.error(error);
			throw new Error("Failed to query activities.");
		}
	}

	async resetNotificationCount() {
		const query = `query {
            Page(page: 1, perPage: 1) {
                notifications(resetNotificationCount: true) {
                    __typename
                }
            }
        }`;

		const options = this.#getMutationOptions(query, {});
		try {
			const data = await this.#elevatedFetch(options);
			return data;
		} catch (error) {
			console.error(error);
			throw new Error("Failed to reset notification count.");
		}
	}

	async searchMedia(searchword) {
		const query = `query($searchword: String) {
            Page(page: 1, perPage: 10) {
                media(search: $searchword) {
                    id
                    title {
                        userPreferred
                    }
                    coverImage {
                        large
                    }
                    type
                    startDate {
                        year
                    }
                    episodes
                    chapters
                }
            }
        }`;
		const options = this.#getMutationOptions(query, { searchword });
		try {
			const data = await this.#elevatedFetch(options);
			return data.Page.media;
		} catch (error) {
			throw new Error(`Failed to query media (${searchword})`);
		}
	}

	async getMediaProgress(mediaId){
		const query = `query($mediaId: Int, $userId: Int) {
			  MediaList(mediaId: $mediaId, userId: $userId) {
				id
				mediaId
				status
				progress
				media {title {
				  romaji
				  english
				  native
				  userPreferred
				}}
				media {
				  episodes
				  chapters
				}
			  }
			}`;

		const options = this.#getMutationOptions(query, {mediaId, userId: this.#userId})
		try {
			const data = await this.#elevatedFetch(options);
			return data.MediaList;
		} catch (error) {
			throw new Error(`Failed to query media progress with media id ${mediaId}`);
		}
	}

	async updateMediaProgress(id, mediaId, status, progress) {
		const query = `mutation ($id: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int) {
			  SaveMediaListEntry(id: $id, mediaId: $mediaId, status: $status, progress: $progress) {
				id
			  }
			}
		`;

		const options = this.#getMutationOptions(query, {id, status, progress, mediaId})
		try {
			const data = await this.#elevatedFetch(options);
			return data.MediaList;
		} catch (error){
			throw new Error(`Failed to update media progress with media id ${mediaId}`);
		}
	}

	async getCreatedMediaActivity(mediaId){
		const query = `query ($userId: Int, $mediaId: Int) {
				Activity(userId: $userId, mediaId: $mediaId, sort: ID_DESC, type_in: [ANIME_LIST, MANGA_LIST]) {
					... on ListActivity {
					  id
					}
				  }
				}`;

		const options = this.#getMutationOptions(query, {mediaId, userId: this.#userId})
		try {
			const data = await this.#elevatedFetch(options);
			return data.Activity;
		} catch (error) {
			throw new Error(`Failed to get created media activity with media id ${mediaId}`);
		}
	}

	async replyToActivity(activityId, reply) {
		const query = `mutation ($activityId: Int, $reply: String) {
		SaveActivityReply(activityId: $activityId, text: $reply) {
			  activityId
			}}`;

		const options = this.#getMutationOptions(query, {activityId, reply});
		try {
			const data = await this.#elevatedFetch(options);
			return data.ActivityReply;
		} catch (error) {
			throw new Error(`Failed to reply to activity with id ${activityId}`);
		}
	}

	async #elevatedFetch(options) {
		// const runElevated = this.settings.options.useElevatedFetch.getValue();
		// if (runElevated && GM.xmlHttpRequest) {
		// 	try {
		// 		const response = await GM.xmlHttpRequest({
		// 			method: "POST",
		// 			url: this.#url,
		// 			data: options.body,
		// 			headers: options.headers,
		// 		});
		// 		const data = JSON.parse(response.response);
		// 		console.log(
		// 			"remaining",
		// 			this.#parseStringHeaders(response.responseHeaders)[
		// 				"x-ratelimit-remaining"
		// 			],
		// 		);
		// 		return data.data;
		// 	} catch (error) {
		// 		if (error.error?.includes("Request was blocked by the user")) {
		// 			Toaster.warning(
		// 				"Elevated access has been enabled in the userscript settings but user has refused permissions to run it.",
		// 			);
		// 		}
		// 		console.error(error);
		// 		throw error;
		// 	}
		// }

		return await this.#regularFetch(options);
	}

	async #regularFetch(options) {
		try {
			const response = await fetch(this.#url, options);
			this.#setApiLimitRemaining(response);
			const data = await response.json();
			if (data.errors) {
				console.error(data.errors);
			}
			return data.data;
		} catch (error) {
			console.error(error);
			if (error instanceof TypeError) {
				console.log("reset:", error.headers?.get("X-RateLimit-Reset"));
				Toaster.warning(
					`Preflight check failed. This might be caused by too many requests. Last successful query returned ${this.#getApiLimitRemaining()} queries remaining.`,
				);
				console.error("Network error occured: ", error.message);
				console.log(
					`Last successful query by VoidVerified returned ${this.#getApiLimitRemaining()} queries remaining.`,
				);
			}
			throw error;
		}
	}

	#parseStringHeaders(responseHeaders) {
		const headersArray = responseHeaders.split("\r\n");
		const headers = {};
		headersArray
			.filter((x) => x !== "")
			.forEach((headerRow) => {
				const [key, value] = headerRow.split(":");
				headers[key] = value.trim();
			});
		return headers;
	}

	async #queryUsers(page) {
		const variables = { page, userId: this.#userId };
		const query = `query ($page: Int, $userId: Int!) {
            Page(page: $page) {
                following(userId: $userId) {
                  name
                  id
                  avatar {
                    large
                  }
                  bannerImage
                  options {
                    profileColor
                  }
                },
                pageInfo {
                  total
                  perPage
                  currentPage
                  lastPage
                  hasNextPage
                }
              }
            }
        `;

		const options = this.#getQueryOptions(query, variables);

		try {
			const data = await this.#elevatedFetch(options);
			this.#handleQueriedUsers(data.Page.following);
			const pageInfo = data.Page.pageInfo;
			if (pageInfo.hasNextPage) {
				await this.#queryUsers(pageInfo.currentPage + 1);
			}
		} catch (error) {
			throw new Error("Failed to query followed users.", error);
		}
	}

	#handleQueriedUsers(users) {
		for (const user of users) {
			this.settings.updateUserFromApi(user);
		}
	}

	#getQueryOptions(query, variables) {
		const options = {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
				Accept: "application/json",
			},
			body: JSON.stringify({
				query,
				variables,
			}),
		};

		if (this.settings.auth?.token) {
			options.headers.Authorization = `Bearer ${this.settings.auth.token}`;
		}

		return options;
	}

	#getMutationOptions(query, variables) {
		if (!this.settings.auth?.token) {
			Toaster.error(
				"Tried to make API query without authorizing VoidVerified. You can do so in the settings.",
			);
			throw new Error("VoidVerified is missing auth token.");
		}
		let queryOptions = this.#getQueryOptions(query, variables);
		return queryOptions;
	}

	#setApiLimitRemaining(response) {
		sessionStorage.setItem(
			"void-verified-api-limit-remaining",
			response.headers.get("X-RateLimit-Remaining"),
		);
	}

	#getApiLimitRemaining() {
		return sessionStorage.getItem("void-verified-api-limit-remaining");
	}
}

const userQuery = `user {
    name
    avatar {
        large
    }
}`;

const mediaQuery = `media {
    title {
        userPreferred
    }
    coverImage {
        large
    }
    id
    type
}`;

const activityQuery = `activityId
    type
    id
    ${userQuery}
    createdAt
    context`;

const followingQuery = `type
    id
    context
    createdAt
    ${userQuery}
    `;

const airingQuery = `type
    id
    contexts
    createdAt
    ${mediaQuery}
    episode
    `;

const relatedMediaQuery = `type
    id
    ${mediaQuery}
    context
    createdAt`;

const threadQuery = `type
    id
    context
    threadId
    thread {title}
    ${userQuery}
    createdAt`;

const threadCommentQuery = `type
    id
    context
    thread {
        id
        title
    }
    commentId
    ${userQuery}
    createdAt`;

const mediaDataChange = `type
    id
    context
    ${mediaQuery}
    reason
    createdAt
    `;

const mediaDeleted = `type
    id
    context
    reason
    deletedMediaTitle
    createdAt`;

;// CONCATENATED MODULE: ./src/utils/settings.ts




class settings_Option {
    value;
    defaultValue;
    description;
    category;
    authRequired;
    constructor(option) {
        this.defaultValue = option.defaultValue;
        this.description = option.description;
        this.category = option.category ?? categories.misc;
        this.authRequired = option.authRequired;
    }
    getValue() {
        if (this.value === "") {
            return this.defaultValue;
        }
        return this.value ?? this.defaultValue;
    }
}
class Settings {
    localStorageUsers = "void-verified-users";
    localStorageSettings = "void-verified-settings";
    localStorageAuth = "void-verified-auth";
    version;
    auth = null;
    anilistUser;
    verifiedUsers = [];
    options = {};
    constructor() {
        this.version = GM_info.script.version;
        this.verifiedUsers =
            JSON.parse(localStorage.getItem(this.localStorageUsers)) ?? [];
        const settingsInLocalStorage = JSON.parse(localStorage.getItem(this.localStorageSettings)) ?? {};
        for (const [key, value] of Object.entries(defaultSettings)) {
            this.options[key] = new settings_Option(value);
        }
        for (const [key, val] of Object.entries(settingsInLocalStorage)) {
            const value = val;
            if (!this.options[key]) {
                continue;
            }
            this.options[key].value = value.value;
        }
        this.auth =
            JSON.parse(localStorage.getItem(this.localStorageAuth)) ?? null;
        const auth = JSON.parse(localStorage.getItem("auth"));
        this.anilistUser = auth?.name;
    }
    isAuthorized() {
        const isAuthorized = this.auth?.token != null;
        return isAuthorized;
    }
    async verifyUser(username) {
        if (this.verifiedUsers.find((user) => user.username.toLowerCase() === username.toLowerCase())) {
            return;
        }
        this.verifiedUsers.push({ username });
        localStorage.setItem(this.localStorageUsers, JSON.stringify(this.verifiedUsers));
        try {
            Toaster.debug(`Querying ${username}.`);
            const anilistAPI = new AnilistAPI(this);
            const user = await anilistAPI.queryUser(username);
            this.updateUserFromApi(user);
        }
        catch (error) {
            Toaster.error("Failed to query new user.");
            console.error(error);
        }
    }
    getUser(username) {
        return this.verifiedUsers.find((user) => user.username === username);
    }
    isVerified(username) {
        return this.verifiedUsers.some((user) => user.username === username);
    }
    updateUserOption(username, key, value) {
        this.verifiedUsers = this.verifiedUsers.map((u) => u.username === username
            ? {
                ...u,
                [key]: value,
            }
            : u);
        localStorage.setItem(this.localStorageUsers, JSON.stringify(this.verifiedUsers));
    }
    updateUserFromApi(apiUser) {
        let user = this.#findVerifiedUser(apiUser);
        if (!user) {
            return;
        }
        const newUser = this.#mapApiUser(user, apiUser);
        this.#mapVerifiedUsers(newUser);
        localStorage.setItem(this.localStorageUsers, JSON.stringify(this.verifiedUsers));
    }
    #findVerifiedUser(apiUser) {
        let user = this.verifiedUsers.find((u) => u.id && u.id === apiUser.id);
        if (user) {
            return user;
        }
        return this.verifiedUsers.find((u) => u.username.toLowerCase() === apiUser.name.toLowerCase());
    }
    #mapVerifiedUsers(newUser) {
        if (this.verifiedUsers.find((u) => u.id && u.id === newUser.id)) {
            this.verifiedUsers = this.verifiedUsers.map((u) => u.id === newUser.id ? newUser : u);
            return;
        }
        this.verifiedUsers = this.verifiedUsers.map((u) => u.username.toLowerCase() === newUser.username.toLowerCase()
            ? newUser
            : u);
    }
    #mapApiUser(user, apiUser) {
        let userObject = { ...user };
        userObject.color = ColorFunctions.handleAnilistColor(apiUser.options.profileColor);
        userObject.username = apiUser.name;
        userObject.avatar = apiUser.avatar.large;
        userObject.banner = apiUser.bannerImage;
        userObject.id = apiUser.id;
        userObject.lastFetch = new Date();
        if (this.options.quickAccessBadge.getValue() || user.quickAccessBadge) {
            if ((user.avatar && user.avatar !== userObject.avatar) ||
                (user.color && user.color !== userObject.color) ||
                (user.banner && user.banner !== userObject.banner) ||
                (user.username &&
                    user.username.toLowerCase() !==
                        userObject.username.toLowerCase())) {
                userObject.quickAccessBadgeDisplay = true;
            }
        }
        return userObject;
    }
    saveAuthToken(tokenObject) {
        this.auth = tokenObject;
        localStorage.setItem(this.localStorageAuth, JSON.stringify(tokenObject));
    }
    removeAuthToken() {
        this.auth = null;
        localStorage.removeItem(this.localStorageAuth);
    }
    removeUser(username) {
        this.verifiedUsers = this.verifiedUsers.filter((user) => user.username !== username);
        localStorage.setItem(this.localStorageUsers, JSON.stringify(this.verifiedUsers));
    }
    saveSettingToLocalStorage(key, value) {
        let localSettings = JSON.parse(localStorage.getItem(this.localStorageSettings));
        this.options[key].value = value;
        if (localSettings === null) {
            const settings = {
                [key]: value,
            };
            localStorage.setItem(this.localStorageSettings, JSON.stringify(settings));
            return;
        }
        localSettings[key] = { value };
        localStorage.setItem(this.localStorageSettings, JSON.stringify(localSettings));
    }
}

;// CONCATENATED MODULE: ./src/handlers/styleHandler.js
class StyleHandler {
	settings;
	usernameStyles = "";
	highlightStyles = "";
	otherStyles = "";

	constructor(settings) {
		this.settings = settings;
	}

	refreshStyles() {
		this.createStyles();
		this.createStyleLink(this.usernameStyles, "username");
		this.createStyleLink(this.highlightStyles, "highlight");
		this.createStyleLink(this.otherStyles, "other");
	}

	createStyles() {
		this.usernameStyles = "";
		this.otherStyles = "";

		for (const user of this.settings.verifiedUsers) {
			if (
				this.settings.options.enabledForUsername.getValue() ||
				user.enabledForUsername
			) {
				this.createUsernameCSS(user);
			}
		}

		if (this.settings.options.moveSubscribeButtons.getValue()) {
			this.otherStyles += `
                .has-label::before {
                top: -30px !important;
                left: unset !important;
                right: -10px;
                }
    
                .has-label[label="Unsubscribe"],
                .has-label[label="Subscribe"] {
                font-size: 0.875em !important;
                }
    
                .has-label[label="Unsubscribe"] {
                color: rgba(var(--color-green),.8);
                }
                `;
		}

		this.createHighlightStyles();

		if (this.settings.options.hideLikeCount.getValue()) {
			this.otherStyles += `
                    .like-wrap .count {
                        display: none;
                    }
                `;
		}
	}

	createHighlightStyles() {
		this.highlightStyles = "";
		for (const user of this.settings.verifiedUsers) {
			if (
				this.settings.options.highlightEnabled.getValue() ||
				user.highlightEnabled
			) {
				this.createHighlightCSS(
					user,
					`div.wrap:has( div.header > a.name[href*="/${user.username}/" i] )`,
				);
				this.createHighlightCSS(
					user,
					`div.wrap:has( div.details > a.name[href*="/${user.username}/" i] )`,
				);
			}

			if (
				this.settings.options.highlightEnabledForReplies.getValue() ||
				user.highlightEnabledForReplies
			) {
				this.createHighlightCSS(
					user,
					`div.reply:has( a.name[href*="/${user.username}/" i] )`,
				);
			}

			this.#createActivityCss(user);
		}

		this.disableHighlightOnSmallCards();
	}

	#createActivityCss(user) {
		const colorUserActivity =
			this.settings.options.colorUserActivity.getValue() ??
			user.colorUserActivity;
		const colorUserReplies =
			this.settings.options.colorUserReplies.getValue() ??
			user.colorUserReplies;

		if (colorUserActivity) {
			this.highlightStyles += `
                div.wrap:has( div.header > a.name[href*="/${
					user.username
				}/"]) a,
                div.wrap:has( div.details > a.name[href*="/${
					user.username
				}/"]) a
                {
                    color: ${this.getUserColor(user)};
                }
            `;
		}
		if (colorUserReplies) {
			this.highlightStyles += `
                .reply:has(a.name[href*="/${user.username}/"]) a,
                .reply:has(a.name[href*="/${
					user.username
				}/"]) .markdown-spoiler::before
                {
                    color: ${this.getUserColor(user)};
                }
            `;
		}
	}

	createUsernameCSS(user) {
		this.usernameStyles += `
            a.name[href*="/${user.username}/" i]::after {
                content: "${
					this.stringIsEmpty(user.sign) ??
					this.settings.options.defaultSign.getValue()
				}";
                color: ${this.getUserColor(user) ?? "rgb(var(--color-blue))"}
            }
        `;
	}

	createHighlightCSS(user, selector) {
		this.highlightStyles += `
                ${selector} {
                    margin-right: -${this.settings.options.highlightSize.getValue()};
                    border-right: ${this.settings.options.highlightSize.getValue()} solid ${
						this.getUserColor(user) ??
						this.getDefaultHighlightColor()
					};
                    border-radius: 5px;
                }
                `;
	}

	disableHighlightOnSmallCards() {
		this.highlightStyles += `
                div.wrap:has(div.small) {
                margin-right: 0px !important;
                border-right: 0px solid black !important;
                }
                `;
	}

	refreshHomePage() {
		if (!this.settings.options.highlightEnabled.getValue()) {
			return;
		}
		this.createHighlightStyles();
		this.createStyleLink(this.highlightStyles, "highlight");
	}

	clearStyles(id) {
		const styles = document.getElementById(`void-verified-${id}-styles`);
		styles?.remove();
	}

	verifyProfile() {
		if (!this.settings.options.enabledForProfileName.getValue()) {
			return;
		}

		const username =
			window.location.pathname.match(/^\/user\/([^/]*)\/?/)[1];

		const user = this.settings.verifiedUsers.find(
			(u) => u.username.toLowerCase() === username.toLowerCase(),
		);

		if (!user) {
			this.clearStyles("profile");
			return;
		}

		const profileStyle = `
                    .name-wrapper h1.name::after {
                    content: "${
						this.stringIsEmpty(user.sign) ??
						this.settings.options.defaultSign.getValue()
					}"
                    }
                `;
		this.createStyleLink(profileStyle, "profile");
	}

	getStyleLink(id) {
		return document.getElementById(`void-verified-${id}-styles`);
	}

	copyUserColor() {
		const usernameHeader = document.querySelector("h1.name");
		const username = usernameHeader.innerHTML.trim();
		const user = this.settings.verifiedUsers.find(
			(u) => u.username === username,
		);

		if (!user) {
			return;
		}

		if (
			!(
				user.copyColorFromProfile ||
				this.settings.options.copyColorFromProfile.getValue()
			)
		) {
			return;
		}

		const color =
			getComputedStyle(usernameHeader).getPropertyValue("--color-blue");

		this.settings.updateUserOption(user.username, "color", color);
	}

	getUserColor(user) {
		return (
			user.colorOverride ??
			(user.color &&
			(user.copyColorFromProfile ||
				this.settings.options.copyColorFromProfile.getValue())
				? `rgb(${user.color})`
				: undefined)
		);
	}

	getDefaultHighlightColor() {
		if (this.settings.options.useDefaultHighlightColor.getValue()) {
			return this.settings.options.defaultHighlightColor.getValue();
		}
		return "rgb(var(--color-blue))";
	}

	createStyleLink(styles, id) {
		const oldLink = document.getElementById(`void-verified-${id}-styles`);
		const link = document.createElement("link");
		link.setAttribute("id", `void-verified-${id}-styles`);
		link.setAttribute("rel", "stylesheet");
		link.setAttribute("type", "text/css");
		link.setAttribute(
			"href",
			"data:text/css;charset=UTF-8," + encodeURIComponent(styles),
		);
		document.head?.append(link);
		oldLink?.remove();
		return link;
	}

	stringIsEmpty(string) {
		if (!string || string.length === 0) {
			return undefined;
		}
		return string;
	}
}

;// CONCATENATED MODULE: ./src/handlers/globalCSS.js


class GlobalCSS {
	settings;
	styleHandler;

	styleId = "global-css";
	isCleared = false;

	cssInLocalStorage = "void-verified-global-css";
	constructor(settings) {
		this.settings = settings;
		this.styleHandler = new StyleHandler(settings);

		this.css = localStorage.getItem(this.cssInLocalStorage) ?? "";
	}

	createCss() {
		if (!this.settings.options.globalCssEnabled.getValue()) {
			this.styleHandler.clearStyles(this.styleId);
			return;
		}

		if (!this.shouldRender()) {
			return;
		}

		this.isCleared = false;
		this.styleHandler.createStyleLink(this.css, this.styleId);
	}

	updateCss(css) {
		this.css = css;
		this.createCss();
		localStorage.setItem(this.cssInLocalStorage, css);
	}

	clearCssForProfile() {
		if (this.isCleared) {
			return;
		}
		if (!this.shouldRender()) {
			this.styleHandler.clearStyles(this.styleId);
			this.isCleared = true;
		}
	}

	shouldRender() {
		if (window.location.pathname.startsWith("/settings")) {
			return false;
		}

		if (!this.settings.options.globalCssAutoDisable.getValue()) {
			return true;
		}

		if (
			!window.location.pathname.startsWith("/user/") &&
			!window.location.pathname.startsWith("/activity/")
		) {
			return true;
		}

		const profileCustomCss = document.getElementById(
			"customCSS-automail-styles",
		);

		const styleHandler = new StyleHandler(this.settings);
		const voidActivityStyles = styleHandler.getStyleLink("activity-css");
		const voidUserStyles = styleHandler.getStyleLink("user-css");

		if (voidActivityStyles || voidUserStyles) {
			return false;
		}

		if (!profileCustomCss) {
			return true;
		}

		const shouldRender = profileCustomCss.innerHTML.trim().length === 0;
		return shouldRender;
	}
}

;// CONCATENATED MODULE: ./src/handlers/activityHandler.js






class ActivityHandler {
	settings;
	constructor(settings) {
		this.settings = settings;
	}

	moveAndDisplaySubscribeButton() {
		if (!this.settings.options.moveSubscribeButtons.getValue()) {
			return;
		}

		const subscribeButtons = document.querySelectorAll(
			"span[label='Unsubscribe'], span[label='Subscribe']",
		);
		for (const subscribeButton of subscribeButtons) {
			if (subscribeButton.parentNode.classList.contains("actions")) {
				continue;
			}

			const container = subscribeButton.parentNode.parentNode;
			const actions = container.querySelector(".actions");
			actions.append(subscribeButton);
		}
	}

	addSelfMessageButton() {
		if (!this.settings.options.selfMessageEnabled.getValue()) {
			return;
		}

		if (
			!window.location.pathname.startsWith(
				`/user/${this.settings.anilistUser}`,
			)
		) {
			return;
		}

		const activityEditActions = document.querySelector(
			".activity-feed-wrap > .activity-edit > .actions",
		);
		if (
			!activityEditActions ||
			activityEditActions?.querySelector(".void-self-message")
		) {
			return;
		}

		activityEditActions.append(
			Button(
				"Message Self",
				() => {
					this.#handleSelfMessage(this.settings);
				},
				"self-message",
			),
		);
	}

	async #handleSelfMessage(settings) {
		const anilistAPI = new AnilistAPI(settings);
		const message = document.querySelector(
			".activity-feed-wrap > .activity-edit textarea",
		).value;
		try {
			Toaster.debug("Self-publishing a message.");
			const response = await anilistAPI.selfMessage(message);
			Toaster.success("Message self-published.");
			window.location.replace(
				`https://anilist.co/activity/${response.id}`,
			);
		} catch (err) {
			console.error(err);
			Toaster.error("There was an error self-publishing a message.");
		}
	}

	removeBlankFromAnilistLinks() {
		if (!this.settings.options.removeAnilistBlanks.getValue()) {
			return;
		}

		const anilistLinks = document.querySelectorAll(
			"a:not(.void-link)[href^='https://anilist.co'][target='_blank']",
		);

		for (const link of anilistLinks) {
			link.removeAttribute("target");
		}
	}
}

;// CONCATENATED MODULE: ./src/api/imageHostBase.js
class ImageHostBase {
	conventToBase64(image) {
		return new Promise(function (resolve, reject) {
			var reader = new FileReader();
			reader.onloadend = function (e) {
				resolve({
					fileName: this.name,
					result: e.target.result,
					error: e.target.error,
				});
			};
			reader.readAsDataURL(image);
		});
	}
}

;// CONCATENATED MODULE: ./src/api/imageHostConfiguration.js
const imageHosts = {
	imgbb: "imgbb",
	imgur: "imgur",
	catbox: "catbox",
};

const imageHostConfiguration = {
	selectedHost: imageHosts.catbox,
	configurations: {
		imgbb: {
			name: "imgbb",
			apiKey: "",
		},
		imgur: {
			name: "imgur",
			clientId: "",
			clientSecret: "",
			expires: null,
			refreshToken: null,
			authToken: null,
		},
		catbox: {
			name: "catbox",
			userHash: "",
		},
	},
};

class ImageHostService {
	#configuration;
	#localStorage = "void-verified-image-host-config";
	constructor() {
		const config = JSON.parse(localStorage.getItem(this.#localStorage));
		if (!config) {
			localStorage.setItem(
				this.#localStorage,
				JSON.stringify(imageHostConfiguration),
			);
		} else {
			for (const key of Object.keys(
				imageHostConfiguration.configurations,
			)) {
				if (config.configurations[key]) {
					continue;
				}
				config.configurations[key] =
					imageHostConfiguration.configurations[key];
			}
			localStorage.setItem(this.#localStorage, JSON.stringify(config));
		}

		this.#configuration = config ?? imageHostConfiguration;
	}

	getImageHostConfiguration(host) {
		return this.#configuration.configurations[host];
	}

	getSelectedHost() {
		return this.#configuration.selectedHost;
	}

	setSelectedHost(host) {
		this.#configuration.selectedHost = host;
		localStorage.setItem(
			this.#localStorage,
			JSON.stringify(this.#configuration),
		);
	}

	setImageHostConfiguration(host, config) {
		this.#configuration.configurations[host] = config;
		localStorage.setItem(
			this.#localStorage,
			JSON.stringify(this.#configuration),
		);
	}
}

;// CONCATENATED MODULE: ./src/api/catboxAPI.js






class CatboxConfig {
	userHash;
	name = "catbox";
	constructor(config) {
		this.userHash = config?.userHash ?? "";
	}
}

class CatboxAPI extends ImageHostBase {
	#url = "https://catbox.moe/user/api.php";
	#configuration;
	constructor(configuration) {
		super();
		this.#configuration = new CatboxConfig(configuration);
	}

	async uploadImage(image) {
		if (!image) {
			return;
		}

		const form = new FormData();
		form.append("reqtype", "fileupload");
		form.append("fileToUpload", image);

		if (this.#configuration.userHash !== "") {
			form.append("userhash", this.#configuration.userHash);
		}

		try {
			if (GM.xmlHttpRequest) {
				Toaster.debug("Uploading image to catbox.");
				const response = await GM.xmlHttpRequest({
					method: "POST",
					url: this.#url,
					data: form,
				});

				if (response.status !== 200) {
					console.error(response.response);
					throw new Error("Image upload to catbox failed.");
				}

				return response.response;
			}
		} catch (error) {
			Toaster.error("Failed to upload image to catbox.");
			return null;
		}
	}

	renderSettings() {
		const container = DOM_DOM.create("div");

		container.append(
			Label(
				"Userhash",
				SecretField(this.#configuration.userHash, (event) => {
					this.#updateUserhash(event, this.#configuration);
				}),
			),
		);

		const p = Note(
			"Catbox.moe works out of the box, but you can provide your userhash to upload images to your account. Your userscript manager should promt you to allow xmlHttpRequest. This is required to upload images to Catbox on AniList.",
		);
		container.append(p);
		return container;
	}

	#updateUserhash(event, configuration) {
		const userHash = event.target.value;
		const config = {
			...configuration,
			userHash,
		};
		new ImageHostService().setImageHostConfiguration(config.name, config);
	}
}

;// CONCATENATED MODULE: ./src/api/imgbbAPI.js





class ImgbbAPI extends ImageHostBase {
	#url = "https://api.imgbb.com/1/upload";
	#configuration;
	constructor(configuration) {
		super();
		this.#configuration = configuration;
	}

	async uploadImage(image) {
		const file = await this.conventToBase64(image);
		if (!file.result) {
			return;
		}

		if (!this.#configuration.apiKey) {
			return;
		}

		const base64 = file.result.split("base64,")[1];

		const settings = {
			method: "POST",
			headers: {
				Accept: "application/json",
				"Content-Type": "application/x-www-form-urlencoded",
			},
			body:
				"image=" +
				encodeURIComponent(base64) +
				"&name=" +
				image.name.split(".")[0],
		};

		try {
			Toaster.debug("Uploading image to imgbb.");
			const response = await fetch(
				`${this.#url}?key=${this.#configuration.apiKey}`,
				settings,
			);
			const data = await response.json();
			Toaster.success("Uploaded image to imgbb.");
			return data.data.url;
		} catch (error) {
			Toaster.error("Failed to upload image to imgbb.");
			console.error(error);
			return null;
		}
	}

	renderSettings() {
		const container = document.createElement("div");

		const apiKey = Label(
			"API key",
			SecretField(this.#configuration.apiKey, (event) => {
				this.#updateApiKey(event, this.#configuration);
			}),
		);

		const note = Note(
			"You need to get the API key from the following link: ",
		);
		note.append(Link("api.imgbb.com", "https://api.imgbb.com/", "_blank"));
		container.append(apiKey, note);

		return container;
	}

	#updateApiKey(event, configuration) {
		const apiKey = event.target.value;
		const config = {
			...configuration,
			apiKey,
		};
		new ImageHostService().setImageHostConfiguration(config.name, config);
	}
}

;// CONCATENATED MODULE: ./src/api/imgurAPI.js






class ImgurAPI extends ImageHostBase {
	#url = "https://api.imgur.com/3/image";
	#configuration;
	constructor(configuration) {
		super();
		this.#configuration = configuration;
	}

	async uploadImage(image) {
		const file = await this.conventToBase64(image);
		if (!file.result) {
			return;
		}

		if (!this.#configuration.clientId) {
			return;
		}

		const base64 = file.result.split("base64,")[1];

		const formData = new FormData();
		formData.append("image", base64);
		formData.append("title", image.name.split(".")[0]);

		const settings = {
			method: "POST",
			headers: {
				Authorization: this.#configuration.authToken
					? `Bearer ${this.#configuration.authToken}`
					: `Client-ID ${this.#configuration.clientId}`,
			},
			body: formData,
		};

		try {
			Toaster.debug("Uploading image to imgur.");
			const response = await fetch(this.#url, settings);
			const data = await response.json();
			Toaster.success("Uploaded image to imgur.");
			return data.data.link;
		} catch (error) {
			Toaster.error("Failed to upload image to imgur.");
			console.error("Failed to upload image to imgur.", error);
			return null;
		}
	}

	renderSettings(settingsUi) {
		const container = DOM_DOM.create("div");

		const clientId = Label(
			"Client ID",
			SecretField(this.#configuration?.clientId ?? "", (event) => {
				this.#updateConfig(event, "clientId", this.#configuration);
				settingsUi.renderSettingsUiContent();
			}),
		);

		const clientSecret = Label(
			"Client Secret",
			SecretField(this.#configuration?.clientSecret ?? "", (event) => {
				this.#updateConfig(event, "clientSecret", this.#configuration);
				settingsUi.renderSettingsUiContent();
			}),
		);

		container.append(clientId, clientSecret);

		if (
			this.#configuration.clientId &&
			this.#configuration.clientSecret &&
			!this.#configuration.authToken
		) {
			const authLink = DOM_DOM.create("a", null, "Authorize Imgur");
			authLink.classList.add("button");
			authLink.setAttribute(
				"href",
				`https://api.imgur.com/oauth2/authorize?client_id=${this.#configuration.clientId}&response_type=token`,
			);
			container.append(authLink);
		}

		if (this.#configuration.authToken) {
			const revokeAuthButton = DOM_DOM.create(
				"button",
				null,
				"Clear Authorization",
			);
			revokeAuthButton.classList.add("button");
			revokeAuthButton.addEventListener("click", () => {
				this.#revokeAuth();
				settingsUi.renderSettingsUiContent();
			});
			container.append(revokeAuthButton);
		}

		this.#renderNote(container);
		return container;
	}

	handleAuth() {
		const hash = window.location.hash.substring(1);
		if (!hash) {
			return;
		}

		const [path, token, expires, _, refreshToken] = hash.split("&");

		if (path !== "void_imgur") {
			return;
		}

		let config = { ...this.#configuration };
		config.authToken = token.split("=")[1];
		config.refreshToken = refreshToken.split("=")[1];

		config.expires = new Date(
			new Date().getTime() + Number(expires.split("=")[1]),
		);

		new ImageHostService().setImageHostConfiguration(
			imageHosts.imgur,
			config,
		);

		window.history.replaceState(
			null,
			"",
			"https://anilist.co/settings/developer",
		);
	}

	async refreshAuthToken() {
		if (
			!this.#configuration.refreshToken ||
			!this.#configuration.clientSecret ||
			!this.#configuration.clientId
		) {
			return;
		}

		if (new Date() < new Date(this.#configuration.expires)) {
			return;
		}

		const formData = new FormData();
		formData.append("refresh_token", this.#configuration.refreshToken);
		formData.append("client_id", this.#configuration.clientId);
		formData.append("client_secret", this.#configuration.clientSecret);
		formData.append("grant_type", "refresh_token");

		try {
			Toaster.debug("Refreshing imgur token.");
			const response = await fetch("https://api.imgur.com/oauth2/token", {
				method: "POST",
				body: formData,
			});
			if (!response.status === 200) {
				console.error("Failed to reauthorize Imgur");
				return;
			}
			const data = await response.json();
			const config = {
				...this.#configuration,
				authToken: data.access_token,
				expires: new Date(new Date().getTime() + data.expires_in),
			};
			new ImageHostService().setImageHostConfiguration(
				imageHosts.imgur,
				config,
			);
			Toaster.success("Refreshed imgur access token.");
		} catch (error) {
			Toaster.error("Error while refreshing imgur token.");
			console.error(error);
		}
	}

	#renderNote(container) {
		const note = Note("How to setup Imgur integration");

		const registerLink = Link(
			"api.imgur.com",
			"https://api.imgur.com/oauth2/addclient",
			"_blank",
		);
		const stepList = DOM_DOM.create("ol", null, [
			DOM_DOM.create("li", null, [
				"Register your application: ",
				registerLink,
				". Use 'https://anilist.co/settings/developer#void_imgur' as callback URL.",
			]),
			DOM_DOM.create(
				"li",
				null,
				"Fill the client id and secret fields with the value Imgur provided.",
			),
			DOM_DOM.create(
				"li",
				null,
				"Click on authorize (you can skip this step if you don't want images tied to your account).",
			),
		]);
		note.append(stepList);
		note.append(
			"Hitting Imgur API limits might get your API access blocked.",
		);

		container.append(note);
	}

	#revokeAuth() {
		const config = {
			...this.#configuration,
			authToken: null,
			refreshToken: null,
		};

		new ImageHostService().setImageHostConfiguration(
			imageHosts.imgur,
			config,
		);
	}

	#updateConfig(event, key, configuration) {
		const value = event.target.value;
		const config = {
			...configuration,
			[key]: value,
		};
		new ImageHostService().setImageHostConfiguration(
			imageHosts.imgur,
			config,
		);
	}
}

;// CONCATENATED MODULE: ./src/api/imageApiFactory.js




class ImageApiFactory {
	getImageHostInstance() {
		const imageHostService = new ImageHostService();
		switch (imageHostService.getSelectedHost()) {
			case imageHosts.imgbb:
				return new ImgbbAPI(
					imageHostService.getImageHostConfiguration(
						imageHosts.imgbb,
					),
				);
			case imageHosts.imgur:
				return new ImgurAPI(
					imageHostService.getImageHostConfiguration(
						imageHosts.imgur,
					),
				);
			case imageHosts.catbox:
				return new CatboxAPI(
					imageHostService.getImageHostConfiguration(
						imageHosts.catbox,
					),
				);
		}
	}
}

;// CONCATENATED MODULE: ./src/assets/changeLog.js
class Version {
	versionNumber;
	featureList;
	constructor(versionNumber, featureList) {
		this.versionNumber = versionNumber ?? "";
		this.featureList = featureList ?? [];
	}
}

class Feature {
	description;
	option;
	constructor(description, option) {
		this.description = description ?? "";
		this.option = option ?? undefined;
	}
}

const changeLog = [
	new Version("1.14", [
		new Feature(
			"Add insta-reply to activity update in home feed.",
			"replyActivityUpdate",
		),
		new Feature("Replace CSS text fields with code editors.")
	]),
	new Version("1.13", [
		new Feature(
			"Replace AniList notification system.",
			"replaceNotifications",
		),
		new Feature(
			"Display quick access of notifications in home page.",
			"quickAccessNotificationsEnabled",
		),
		new Feature(
			"Fixed a bug where pasting text with line breaks inserted extra line breaks.",
		),
		new Feature(
			"Fixed a layout designer bug where a GIF did not loop correctly while previewing.",
		),
	]),
	new Version("1.12", [
		new Feature("Enable CSSpy in Layout & CSS tab.", "csspyEnabled"),
	]),
	new Version("1.11", [
		new Feature(
			"Fix AniList bug where private messages are displayed in List activity feed.",
			"hideMessagesFromListFeed",
		),
		new Feature(
			"Fix layout designer preview crashing the script when viewing a tab other than overview.",
		),
		new Feature(
			"Fix a bug where disabling layout preview would change another user's layout.",
		),
	]),
	new Version("1.10", [
		new Feature(
			"added a change log to introduce new features",
			"changeLogEnabled",
		),
		new Feature(
			"added a self-message button to user's own profile",
			"selfMessageEnabled",
		),
		new Feature("color coded Layout Designer and CSS action buttons"),
		new Feature(
			"fixed a bug where an API error when publishing CSS could lead to user's about being removed",
		),
	]),
	new Version("1.9", [
		new Feature(
			"added gif keyboard to save gifs/images for later use",
			"gifKeyboardEnabled",
		),
		new Feature(
			"open AniList links in the same tab",
			"removeAnilistBlanks",
		),
		new Feature("have multiple layouts in storage"),
		new Feature("secret field to hide API keys"),
	]),
	new Version("1.8", [
		new Feature("added toast notifications", "toasterEnabled"),
		new Feature(
			"added a timer until next refresh to Quick Access",
			"quickAccessTimer",
		),
		new Feature("added a refresh button to Quick Access"),
		new Feature("added Catbox.moe integration"),
		new Feature("fixed an error when publishing custom CSS or about"),
	]),
	new Version("1.7", [
		new Feature("added a Layout Designer", "layoutDesignerEnabled"),
	]),
];

;// CONCATENATED MODULE: ./src/utils/changeLog.js




class ChangeLog {
	#lastVersion;
	#settings;
	#lastVersionInLocalStorage = "void-verified-changelog-last-version";

	constructor(settings) {
		this.#settings = settings;
		this.#lastVersion = localStorage.getItem(
			this.#lastVersionInLocalStorage,
		);
	}

	renderChangeLog(forceDisplay = false) {
		if (
			!this.#settings.options.changeLogEnabled.getValue() &&
			!forceDisplay
		) {
			return;
		}

		if (!this.#newVersionExists() && !forceDisplay) {
			return;
		}

		const modalBody = [
			DOM_DOM.create(
				"div",
				"change-log-note",
				Note(
					"Here are some changes included in recent releases. You can enable new features here or later from settings. You can view this popup again or disable it from settings.",
				),
			),
		];
		modalBody.push(
			...changeLog.map((version) => {
				return this.#createModalContent(version);
			}),
		);

		document.body.append(
			Modal(modalBody, () => {
				this.#handleClose(this);
			}),
		);
	}

	#newVersionExists() {
		if (!this.#lastVersion) {
			return true;
		}
		const versions = changeLog.map((version) =>
			version.versionNumber.split("."),
		);
		const [lastMajorVersion, lastMinorVersion] =
			this.#lastVersion.split(".");

		for (const [majorVersion, minorVersion] of versions) {
			if (
				Number(majorVersion) > Number(lastMajorVersion) ||
				Number(minorVersion) > Number(lastMinorVersion)
			) {
				return true;
			}
		}

		return false;
	}

	#createModalContent(version) {
		const container = DOM_DOM.create("div");
		const header = DOM_DOM.create(
			"h3",
			"change-log-header",
			`Version ${version.versionNumber}`,
		);
		container.append(header);
		const list = DOM_DOM.create("ul", "change-log-list");
		const listItems = version.featureList.map((feature) => {
			return this.#createFeatureListItem(feature);
		});
		list.append(...listItems);
		container.append(list);
		return container;
	}

	#createFeatureListItem(feature) {
		const container = DOM_DOM.create("li");
		if (feature.option) {
			const value = this.#settings.options[feature.option].getValue();
			container.append(
				SettingLabel(
					feature.description,
					Checkbox(value, (event) => {
						this.#handleOptionChange(event, feature.option);
					}),
				),
			);
			return container;
		}
		container.append(
			DOM_DOM.create("span", "change-log-list-item", [
				DOM_DOM.create("span", null, "-"),
				DOM_DOM.create("span", null, feature.description),
			]),
		);
		return container;
	}

	#handleOptionChange(event, option) {
		const value = event.target.checked;
		this.#settings.saveSettingToLocalStorage(option, value);
	}

	#handleClose(_changeLog) {
		const version = changeLog[0].versionNumber;
		_changeLog.#lastVersion = version;
		localStorage.setItem(_changeLog.#lastVersionInLocalStorage, version);
	}
}

;// CONCATENATED MODULE: ./src/utils/aceEditorInitializer.ts
class AceEditorInitializer {
    static initializeEditor(id, value) {
        const siteTheme = localStorage.getItem("site-theme");
        let theme = "monokai";
        switch (siteTheme) {
            case "dark":
            case "system":
                theme = "monokai";
                break;
            case "contrast":
            default:
                theme = "dawn";
        }
        // @ts-ignore
        const editor = ace.edit(id, {
            mode: "ace/mode/css",
            theme: `ace/theme/${theme}`,
            value: value
        });
        editor.setKeyboardHandler("ace/keyboard/vscode");
        editor.setOptions({
            useWorker: false,
            enableBasicAutocompletion: true,
            highlightSelectedWord: true,
            copyWithEmptySelection: true,
            scrollPastEnd: true,
            showPrintMargin: false
        });
    }
    static addChangeHandler(id, callback) {
        // @ts-ignore
        const editor = ace.edit(id);
        editor.on("change", () => {
            callback(editor.getValue());
        });
    }
}

;// CONCATENATED MODULE: ./src/handlers/settingsUserInterface.js













const subCategories = {
	users: "users",
	authorization: "authorization",
	imageHost: "image host",
	layout: "layout & CSS",
	globalCss: "global CSS",
	toasts: "toasts",
};

class SettingsUserInterface {
	settings;
	styleHandler;
	globalCSS;
	userCSS;
	layoutDesigner;
	AnilistBlue = "120, 180, 255";
	#activeCategory = "all";
	#activeSubCategory = subCategories.users;

	constructor(settings, styleHandler, globalCSS, userCSS, layoutDesigner) {
		this.settings = settings;
		this.styleHandler = styleHandler;
		this.globalCSS = globalCSS;
		this.userCSS = userCSS;
		this.layoutDesigner = layoutDesigner;
	}

	renderSettingsUi() {
		this.#checkAuthFromUrl();
		const container = document.querySelector(
			".settings.container > .content",
		);
		const settingsContainerExists = DOM_DOM.get("#verified-settings") !== null;
		if (!settingsContainerExists) {
			const settingsContainer = DOM_DOM.create(
				"div",
				"#verified-settings settings",
			);
			container.append(settingsContainer);
		}

		this.renderSettingsUiContent();
	}

	renderSettingsUiContent() {
		const settingsContainer = DOM_DOM.create("div");

		this.#renderSettingsHeader(settingsContainer);
		this.#renderCategories(settingsContainer);
		this.#renderOptions(settingsContainer);
		this.#handleSubcategories(settingsContainer);

		DOM_DOM.get("#verified-settings").replaceChildren(settingsContainer);
	}

	#handleSubcategories(settingsContainer) {
		this.#renderSubCategoriesNavigation(settingsContainer);
		switch (this.#activeSubCategory) {
			case subCategories.users:
				this.#renderUserTable(settingsContainer);
				break;
			case subCategories.authorization:
				this.#creatAuthenticationSection(settingsContainer);
				break;
			case subCategories.imageHost:
				this.#renderImageHostSettings(settingsContainer);
				break;
			case subCategories.layout:
				settingsContainer.append(
					this.layoutDesigner.renderSettings(this),
				);
				if (
					this.settings.auth?.token &&
					(this.settings.options.profileCssEnabled.getValue() ||
						this.settings.options.activityCssEnabled.getValue())
				) {
					this.#renderCustomCssEditor(
						settingsContainer,
						this.userCSS,
					);
				}
				if (this.settings.options.csspyEnabled.getValue()) {
					settingsContainer.append(this.userCSS.renderCSSpy(this));
				}
				break;
			case subCategories.globalCss:
				if (this.settings.options.globalCssEnabled.getValue()) {
					this.#renderCustomCssEditor(
						settingsContainer,
						this.globalCSS,
					);
				}
				break;
			case subCategories.toasts:
				settingsContainer.append(Toaster.renderSettings(this));
		}
	}

	#renderOptions(settingsContainer) {
		const settingsListContainer = DOM_DOM.create("div", "settings-list");
		for (const [key, setting] of Object.entries(this.settings.options)) {
			if (
				setting.category !== this.#activeCategory &&
				this.#activeCategory !== "all"
			) {
				continue;
			}
			this.#renderSetting(setting, settingsListContainer, key);
		}

		settingsContainer.append(settingsListContainer);
	}

	removeSettingsUi() {
		const settings = document.querySelector("#void-verified-settings");
		settings?.remove();
	}

	#renderSettingsHeader(settingsContainer) {
		const headerContainer = DOM_DOM.create("div", "settings-header");
		const header = DOM_DOM.create("h1", null, "VoidVerified Settings");

		const versionInfo = DOM_DOM.create("p", null, [
			"Version: ",
			DOM_DOM.create("span", null, this.settings.version),
		]);

		headerContainer.append(header);
		headerContainer.append(versionInfo);
		const author = DOM_DOM.create("p", null, [
			"Author: ",
			Link("voidnyan", "https://anilist.co/user/voidnyan/"),
		]);

		const changeLogButton = Button("View Changelog", () => {
			new ChangeLog(this.settings).renderChangeLog(true);
		});

		headerContainer.append(header, versionInfo, author, changeLogButton);

		settingsContainer.append(headerContainer);
	}

	#renderCategories(settingsContainer) {
		const nav = DOM_DOM.create("nav", "nav");
		const list = DOM_DOM.create("ol");

		const onClick = (_category) => {
			this.#activeCategory = _category;
			this.renderSettingsUiContent();
		};

		list.append(
			this.#createNavBtn("all", "all" === this.#activeCategory, () => {
				onClick("all");
			}),
		);

		for (const category of Object.values(categories)) {
			list.append(
				this.#createNavBtn(
					category,
					category === this.#activeCategory,
					() => {
						onClick(category);
					},
				),
			);
		}

		nav.append(list);
		settingsContainer.append(nav);
	}

	#renderSubCategoriesNavigation(settingsContainer) {
		const nav = DOM_DOM.create("nav", "nav");
		const list = DOM_DOM.create("ol");

		for (const subCategory of Object.values(subCategories)) {
			if (!this.#shouldDisplaySubCategory(subCategory)) {
				continue;
			}
			list.append(
				this.#createNavBtn(
					subCategory,
					this.#activeSubCategory === subCategory,
					() => {
						this.#activeSubCategory = subCategory;
						this.renderSettingsUiContent();
					},
				),
			);
		}

		nav.append(list);
		settingsContainer.append(nav);
	}

	#shouldDisplaySubCategory(subCategory) {
		switch (subCategory) {
			case subCategories.users:
				return true;
			case subCategories.authorization:
				return true;
			case subCategories.imageHost:
				return this.settings.options.pasteImagesToHostService.getValue();
			case subCategories.layout:
				return (
					(this.settings.auth?.token &&
						(this.settings.options.profileCssEnabled.getValue() ||
							this.settings.options.activityCssEnabled.getValue())) ||
					this.settings.options.layoutDesignerEnabled.getValue() ||
					this.settings.options.csspyEnabled.getValue()
				);
			case subCategories.globalCss:
				return this.settings.options.globalCssEnabled.getValue();
			case subCategories.toasts:
				return this.settings.options.toasterEnabled.getValue();
		}
	}

	#createNavBtn(category, isActive, onClick) {
		const className = isActive ? "active" : null;
		const li = DOM_DOM.create("li", className, category);

		li.addEventListener("click", () => {
			onClick();
		});

		return li;
	}

	#renderUserTable(settingsContainer) {
		const tableContainer = DOM_DOM.create("div", "table #verified-user-table");

		tableContainer.style = `
            margin-top: 25px;
        `;
		const head = TableHead("Username", "Sign", "Color", "Other");

		const rows = this.settings.verifiedUsers.map((user) =>
			this.#createUserRow(user),
		);
		const body = TableBody(rows);

		const table = Table(head, body);
		tableContainer.append(table);

		const inputForm = DOM_DOM.create("form");

		inputForm.addEventListener("submit", (event) => {
			this.#handleVerifyUserForm(event, this.settings);
		});

		const inputFormLabel = DOM_DOM.create("label", null, "Add user");
		inputFormLabel.setAttribute("for", "void-verified-add-user");

		inputForm.append(inputFormLabel);
		inputForm.append(InputField("", () => {}, "#verified-add-user"));
		tableContainer.append(inputForm);

		settingsContainer.append(tableContainer);

		const fallbackColorOption = this.settings.options.defaultHighlightColor;
		settingsContainer.append(
			DOM_DOM.create("h5", null, "Fallback color"),
			ColorPicker(fallbackColorOption.getValue(), (event) => {
				this.#handleOption(event, "fallbackColor");
			}),
		);
	}

	#createUserRow(user) {
		const row = DOM_DOM.create("tr");
		const userLink = DOM_DOM.create("a", null, user.username);
		userLink.setAttribute(
			"href",
			`https://anilist.co/user/${user.username}/`,
		);
		userLink.setAttribute("target", "_blank");
		row.append(DOM_DOM.create("td", null, userLink));

		const signInput = InputField(
			user.sign ?? "",
			(event) => {
				this.#updateUserOption(
					user.username,
					"sign",
					event.target.value,
				);
			},
			"sign",
		);

		const signCell = DOM_DOM.create("td", null, signInput);
		signCell.append(
			this.#createUserCheckbox(
				user.enabledForUsername,
				user.username,
				"enabledForUsername",
				this.settings.options.enabledForUsername.getValue(),
			),
		);

		row.append(DOM_DOM.create("th", null, signCell));

		const colorInputContainer = DOM_DOM.create("div");

		const colorInput = DOM_DOM.create("input");
		colorInput.setAttribute("type", "color");
		colorInput.value = this.#getUserColorPickerColor(user);
		colorInput.addEventListener(
			"change",
			(event) => this.#handleUserColorChange(event, user.username),
			false,
		);

		colorInputContainer.append(colorInput);

		colorInputContainer.append(
			IconButton(RefreshIcon(), () => {
				this.#handleUserColorReset(user.username);
			}),
		);

		colorInputContainer.append(
			this.#createUserCheckbox(
				user.copyColorFromProfile,
				user.username,
				"copyColorFromProfile",
				this.settings.options.copyColorFromProfile.getValue(),
			),
		);

		colorInputContainer.append(
			this.#createUserCheckbox(
				user.highlightEnabled,
				user.username,
				"highlightEnabled",
				this.settings.options.highlightEnabled.getValue(),
			),
		);

		colorInputContainer.append(
			this.#createUserCheckbox(
				user.highlightEnabledForReplies,
				user.username,
				"highlightEnabledForReplies",
				this.settings.options.highlightEnabledForReplies.getValue(),
			),
		);

		colorInputContainer.append(
			this.#createUserCheckbox(
				user.colorUserActivity,
				user.username,
				"colorUserActivity",
				this.settings.options.colorUserActivity.getValue(),
			),
		);

		colorInputContainer.append(
			this.#createUserCheckbox(
				user.colorUserReplies,
				user.username,
				"colorUserReplies",
				this.settings.options.colorUserReplies.getValue(),
			),
		);

		const colorCell = DOM_DOM.create("td", null, colorInputContainer);
		row.append(colorCell);

		const quickAccessCheckbox = this.#createUserCheckbox(
			user.quickAccessEnabled,
			user.username,
			"quickAccessEnabled",
			this.settings.options.quickAccessEnabled.getValue(),
		);

		const otherCell = DOM_DOM.create("td", null, quickAccessCheckbox);

		const cssEnabledCheckbox = this.#createUserCheckbox(
			user.onlyLoadCssFromVerifiedUser,
			user.username,
			"onlyLoadCssFromVerifiedUser",
			this.settings.options.onlyLoadCssFromVerifiedUser.getValue(),
		);

		otherCell.append(cssEnabledCheckbox);

		row.append(otherCell);

		const deleteButton = DOM_DOM.create("button", null, "❌");
		deleteButton.addEventListener("click", () =>
			this.#removeUser(user.username),
		);
		row.append(DOM_DOM.create("th", null, deleteButton));
		return row;
	}

	#getUserColorPickerColor(user) {
		if (user.colorOverride) {
			return user.colorOverride;
		}

		if (
			user.color &&
			(user.copyColorFromProfile ||
				this.settings.options.copyColorFromProfile.getValue())
		) {
			return ColorFunctions.rgbToHex(user.color);
		}

		if (this.settings.options.useDefaultHighlightColor.getValue()) {
			return this.settings.options.defaultHighlightColor.getValue();
		}

		return ColorFunctions.rgbToHex(this.AnilistBlue);
	}

	#createUserCheckbox(isChecked, username, settingKey, disabled) {
		const onChange = (event) => {
			this.#updateUserOption(username, settingKey, event.target.checked);
			this.renderSettingsUiContent();
		};
		const description = this.settings.options[settingKey].description;
		const checkbox = Checkbox(isChecked, onChange, disabled, true);
		return Tooltip(description, checkbox);
	}

	#handleUserColorReset(username) {
		this.#updateUserOption(username, "colorOverride", undefined);
		this.renderSettingsUiContent();
	}

	#handleUserColorChange(event, username) {
		const color = event.target.value;
		this.#updateUserOption(username, "colorOverride", color);
	}

	async #handleVerifyUserForm(event, settings) {
		event.preventDefault();

		const usernameInput = DOM_DOM.get("#verified-add-user");
		const username = usernameInput.value;
		await settings.verifyUser(username);
		usernameInput.value = "";
		this.renderSettingsUiContent();
	}

	#updateUserOption(username, key, value) {
		this.settings.updateUserOption(username, key, value);
		this.styleHandler.refreshStyles();
	}

	#removeUser(username) {
		this.settings.removeUser(username);
		this.renderSettingsUiContent();
		this.styleHandler.refreshStyles();
	}

	#renderSetting(setting, settingsContainer, settingKey) {
		if (setting.category === categories.hidden) {
			return;
		}
		const value = setting.getValue();
		const type = typeof value;

		let input;

		const onChange = (event) => {
			this.#handleOption(event, settingKey, type);
		};

		if (type === "boolean") {
			input = Checkbox(value, onChange);
		} else if (settingKey == "defaultHighlightColor") {
			return;
		} else if (type === "string") {
			input = InputField(value, onChange);
		}
		input.setAttribute("id", settingKey);

		const settingLabel = SettingLabel(setting.description, input);

		if (setting.authRequired) {
			settingLabel.classList.add("void-auth-required");
		}

		settingsContainer.append(settingLabel);
	}

	#handleOption(event, settingKey, type) {
		const value =
			type === "boolean" ? event.target.checked : event.target.value;
		this.settings.saveSettingToLocalStorage(settingKey, value);
		this.styleHandler.refreshStyles();

		if (!this.#shouldDisplaySubCategory(this.#activeSubCategory)) {
			this.#activeSubCategory = subCategories.users;
		}

		this.renderSettingsUiContent();
	}

	// TODO: separate userCSS
	#renderCustomCssEditor(settingsContainer, cssHandler) {
		const cssName = cssHandler instanceof GlobalCSS ? "global" : "user";
		const container = DOM_DOM.create("div", "css-editor");
		const label = DOM_DOM.create("h3", null, `Custom ${cssName} CSS`);
		container.append(label);

		container.append(DOM_DOM.create("div", `#custom-css-editor-${cssName} ace-editor`, cssHandler.css));

		// use timeout so div has been appended to DOM before trying to access it
		setTimeout(() => {
			AceEditorInitializer.initializeEditor(`void-custom-css-editor-${cssName}`, cssHandler.css);
			AceEditorInitializer.addChangeHandler(`void-custom-css-editor-${cssName}`, (value) => {
				cssHandler.updateCss(value);
			});
		}, 150);

		if (cssName === "global") {
			const notice = DOM_DOM.create("div");
			notice.innerText =
				"Please note that Custom CSS is disabled in the settings. \nIn the event that you accidentally disable rendering of critical parts of AniList, navigate to the settings by URL";
			notice.style.fontSize = "11px";
			container.append(notice);
		} else {
			const resetButton = Button(
				"Reset CSS",
				() => {
					if (window.confirm("Your changes will be lost.")) {
						cssHandler.getAuthUserCss().then(() => {
							textarea.value = cssHandler.css;
						});
					}
				},
				"error",
			);

			const publishButton = Button(
				"Publish CSS",
				(event) => {
					this.#handlePublishCss(event, cssHandler);
				},
				"success",
			);

			const previewButton = Button(
				cssHandler.preview ? "Disable Preview" : "Enable Preview",
				() => {
					cssHandler.togglePreview();
					previewButton.innerText = cssHandler.preview
						? "Disable Preview"
						: "Enable Preview";
				},
			);

			container.append(resetButton);
			container.append(publishButton);
			container.append(previewButton);
		}

		const prettifyButton = Button("Prettify", () => {
			const beautify = ace.require("ace/ext/beautify");
			const editor = ace.edit(`void-custom-css-editor-${cssName}`);
			const value = editor.getValue()
				.replace(/(\n\s*\n)+/g, '\n\n')
				.replace(/\{[^\}]*\}/g, (block) => {
				// Remove all empty lines within the block
				return block.replace(/\n\s*\n/g, '\n');
			});
			editor.setValue(value);
			beautify.beautify(editor.session);
		});
		container.append(prettifyButton);
		settingsContainer.append(container);
	}

	// TODO: separate userCSS
	#handlePublishCss(event, cssHandler) {
		const btn = event.target;
		btn.innerText = "Publishing...";
		cssHandler.publishUserCss().then(() => {
			btn.innerText = "Publish";
		});
	}

	// TODO: separate userCSS
	#handleCustomCssEditor(event, cssHandler) {
		const value = event.target.value;
		cssHandler.updateCss(value);
	}

	// TODO: separate to imageHostService?
	#renderImageHostSettings(settingsContainer) {
		const container = DOM_DOM.create("div");

		const imageHostService = new ImageHostService();
		const imageApiFactory = new ImageApiFactory();

		const imageHostOptions = Object.values(imageHosts).map((imageHost) =>
			Option(
				imageHost,
				imageHost === imageHostService.getSelectedHost(),
				() => {
					imageHostService.setSelectedHost(imageHost);
					this.renderSettingsUiContent();
				},
			),
		);

		const select = Select(imageHostOptions);
		container.append(Label("Image host", select));

		const hostSpecificSettings = DOM_DOM.create("div");
		const imageHostApi = imageApiFactory.getImageHostInstance();
		hostSpecificSettings.append(imageHostApi.renderSettings(this));

		container.append(hostSpecificSettings);
		settingsContainer.append(container);
	}

	#creatAuthenticationSection(settingsContainer) {
		const isAuthenticated =
			this.settings.auth !== null &&
			new Date(this.settings.auth?.expires) > new Date();

		const clientId = 15519;

		const authenticationContainer = DOM_DOM.create("div");

		const header = DOM_DOM.create("h3", null, "Authorize VoidVerified");
		const description = DOM_DOM.create(
			"p",
			null,
			"Some features of VoidVerified might need your access token to work correctly or fully. Below is a list of features using your access token. If you do not wish to use any of these features, you do not need to authenticate. If revoking authentication, be sure to revoke VoidVerified from Anilist Apps as well.",
		);

		const list = DOM_DOM.create("ul");
		for (const option of Object.values(this.settings.options).filter(
			(o) => o.authRequired,
		)) {
			list.append(DOM_DOM.create("li", null, option.description));
		}

		const authLink = DOM_DOM.create("a", "button", "Authenticate VoidVerified");
		authLink.setAttribute(
			"href",
			`https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&response_type=token`,
		);

		const removeAuthButton = DOM_DOM.create(
			"button",
			null,
			"Revoke auth token",
		);
		removeAuthButton.classList.add("button");
		removeAuthButton.addEventListener("click", () => {
			this.settings.removeAuthToken();
			this.renderSettingsUiContent();
		});

		authenticationContainer.append(header);
		authenticationContainer.append(description);
		authenticationContainer.append(list);
		authenticationContainer.append(
			!isAuthenticated ? authLink : removeAuthButton,
		);

		settingsContainer.append(authenticationContainer);
	}

	#checkAuthFromUrl() {
		const hash = window.location.hash.substring(1);
		if (!hash) {
			return;
		}

		const [path, token, type, expiress] = hash.split("&");

		if (path === "void_imgur") {
			const imgurConfig =
				new ImageHostService().getImageHostConfiguration(
					imageHosts.imgur,
				);
			new ImgurAPI(imgurConfig).handleAuth();
		}
		if (path !== "void_auth") {
			return;
		}

		const expiresDate = new Date(
			new Date().getTime() + Number(expiress.split("=")[1]) * 1000,
		);

		this.settings.saveAuthToken({
			token: token.split("=")[1],
			expires: expiresDate,
		});

		window.history.replaceState(
			null,
			"",
			"https://anilist.co/settings/developer",
		);
	}
}

;// CONCATENATED MODULE: ./src/handlers/quickAccessHandler.js






class QuickAccess {
	settings;
	#quickAccessId = "void-quick-access";
	#lastFetchedLocalStorage = "void-verified-last-fetched";
	#lastFetched;
	#queryInProgress = false;

	#apiQueryTimeoutInMinutes = 15;
	#apiQueryTimeout = this.#apiQueryTimeoutInMinutes * 60 * 1000;
	constructor(settings) {
		this.settings = settings;
		const fetched = localStorage.getItem(this.#lastFetchedLocalStorage);
		if (fetched) {
			this.#lastFetched = new Date(fetched);
		}
	}

	async renderQuickAccess() {
		if (this.#queryInProgress) {
			return;
		}

		const queried = await this.#queryUsers();

		if (!queried && this.#quickAccessRendered()) {
			this.#updateTimer();
			return;
		}

		if (
			!this.settings.options.quickAccessEnabled.getValue() &&
			!this.settings.verifiedUsers.some((user) => user.quickAccessEnabled)
		) {
			return;
		}

		const quickAccessContainer = DOM_DOM.getOrCreate(
			"div",
			"#quick-access quick-access",
		);

		const container = DOM_DOM.create("div", "quick-access-users-wrap");

		const sectionHeader = this.#renderHeader();
		const users = this.#renderUsers();

		container.append(sectionHeader, users);

		this.#insertIntoDOM(quickAccessContainer, container);
	}

	#renderHeader() {
		const sectionHeader = document.createElement("div");
		sectionHeader.setAttribute("class", "section-header");
		const title = document.createElement("h2");
		title.append("Users");
		title.setAttribute(
			"title",
			`Last updated at ${this.#lastFetched.toLocaleTimeString()}`,
		);
		sectionHeader.append(title);

		const timer = DOM_DOM.create("span", "quick-access-timer", "");

		const refreshButton = IconButton(RefreshIcon(), () => {
			this.#queryUsers(true);
		});

		sectionHeader.append(DOM_DOM.create("div", null, [timer, refreshButton]));

		return sectionHeader;
	}

	#renderUsers() {
		const quickAccessBody = document.createElement("div");
		quickAccessBody.setAttribute("class", "void-quick-access-wrap");

		for (const user of this.#getQuickAccessUsers()) {
			quickAccessBody.append(this.#createQuickAccessLink(user));
		}

		return quickAccessBody;
	}

	#updateTimer() {
		if (!this.settings.options.quickAccessTimer.getValue()) {
			return;
		}
		const timer = DOM_DOM.get("quick-access-timer");
		const nextQuery = new Date(
			this.#lastFetched.getTime() + this.#apiQueryTimeout,
		);
		const timeLeftInSeconds = Math.floor((nextQuery - new Date()) / 1000);
		const timeLeftInMinutes = timeLeftInSeconds / 60;

		if (timeLeftInMinutes > 1) {
			timer.replaceChildren(`${Math.floor(timeLeftInSeconds / 60)}m`);
			return;
		}

		timer.replaceChildren(`${timeLeftInSeconds}s`);
	}

	async #queryUsers(ignoreLastFetched = false) {
		const currentTime = new Date();

		if (
			!this.#lastFetched ||
			currentTime - this.#lastFetched > this.#apiQueryTimeout ||
			ignoreLastFetched
		) {
			try {
				Toaster.debug("Querying Quick Access users.");
				this.#queryInProgress = true;
				const anilistAPI = new AnilistAPI(this.settings);
				await anilistAPI.queryVerifiedUsers();
				Toaster.success("Quick Access users updated.");
			} catch (error) {
				Toaster.error("Querying Quick Access failed.");
				console.error(error);
			} finally {
				this.#lastFetched = new Date();
				localStorage.setItem(
					this.#lastFetchedLocalStorage,
					this.#lastFetched,
				);
				this.#queryInProgress = false;
				return true;
			}
		} else {
			return false;
		}
	}

	clearBadge() {
		const username =
			window.location.pathname.match(/^\/user\/([^/]*)\/?/)[1];
		this.settings.updateUserOption(
			username,
			"quickAccessBadgeDisplay",
			false,
		);
	}

	#createQuickAccessLink(user) {
		const container = document.createElement("a");
		container.setAttribute("class", "void-quick-access-item");
		container.setAttribute(
			"href",
			`https://anilist.co/user/${user.username}/`,
		);

		const image = document.createElement("div");
		image.style.backgroundImage = `url(${user.avatar})`;
		image.setAttribute("class", "void-quick-access-pfp");
		container.append(image);

		const username = document.createElement("div");
		username.append(user.username);
		username.setAttribute("class", "void-quick-access-username");

		if (
			(this.settings.options.quickAccessBadge.getValue() ||
				user.quickAccessBadge) &&
			user.quickAccessBadgeDisplay
		) {
			container.classList.add("void-quick-access-badge");
		}

		container.append(username);
		return container;
	}

	#quickAccessRendered() {
		const quickAccess = DOM_DOM.get("quick-access-wrap");
		return quickAccess !== null;
	}

	#getQuickAccessUsers() {
		if (this.settings.options.quickAccessEnabled.getValue()) {
			return this.settings.verifiedUsers;
		}

		return this.settings.verifiedUsers.filter(
			(user) => user.quickAccessEnabled,
		);
	}

	#insertIntoDOM(quickAccessContainer, container) {
		if (
			quickAccessContainer.querySelector(".void-quick-access-users-wrap")
		) {
			const oldUsers = DOM_DOM.get("quick-access-users-wrap");
			quickAccessContainer.replaceChild(container, oldUsers);
		} else {
			quickAccessContainer.append(container);
		}

		if (DOM_DOM.get("#quick-access")) {
			return;
		}
		const section = document.querySelector(
			".container > .home > div:nth-child(2)",
		);
		section.insertBefore(quickAccessContainer, section.firstChild);
	}
}

;// CONCATENATED MODULE: ./src/libraries/lz-string.js
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString = (function () {
	// private property
	var f = String.fromCharCode;
	var keyStrBase64 =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	var keyStrUriSafe =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
	var baseReverseDic = {};

	function getBaseValue(alphabet, character) {
		if (!baseReverseDic[alphabet]) {
			baseReverseDic[alphabet] = {};
			for (var i = 0; i < alphabet.length; i++) {
				baseReverseDic[alphabet][alphabet.charAt(i)] = i;
			}
		}
		return baseReverseDic[alphabet][character];
	}

	var LZString = {
		compressToBase64: function (input) {
			if (input == null) return "";
			var res = LZString._compress(input, 6, function (a) {
				return keyStrBase64.charAt(a);
			});
			switch (
				res.length % 4 // To produce valid Base64
			) {
				default: // When could this happen ?
				case 0:
					return res;
				case 1:
					return res + "===";
				case 2:
					return res + "==";
				case 3:
					return res + "=";
			}
		},

		decompressFromBase64: function (input) {
			if (input == null) return "";
			if (input == "") return null;
			return LZString._decompress(input.length, 32, function (index) {
				return getBaseValue(keyStrBase64, input.charAt(index));
			});
		},

		compressToUTF16: function (input) {
			if (input == null) return "";
			return (
				LZString._compress(input, 15, function (a) {
					return f(a + 32);
				}) + " "
			);
		},

		decompressFromUTF16: function (compressed) {
			if (compressed == null) return "";
			if (compressed == "") return null;
			return LZString._decompress(
				compressed.length,
				16384,
				function (index) {
					return compressed.charCodeAt(index) - 32;
				},
			);
		},

		//compress into uint8array (UCS-2 big endian format)
		compressToUint8Array: function (uncompressed) {
			var compressed = LZString.compress(uncompressed);
			var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character

			for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) {
				var current_value = compressed.charCodeAt(i);
				buf[i * 2] = current_value >>> 8;
				buf[i * 2 + 1] = current_value % 256;
			}
			return buf;
		},

		//decompress from uint8array (UCS-2 big endian format)
		decompressFromUint8Array: function (compressed) {
			if (compressed === null || compressed === undefined) {
				return LZString.decompress(compressed);
			} else {
				var buf = new Array(compressed.length / 2); // 2 bytes per character
				for (var i = 0, TotalLen = buf.length; i < TotalLen; i++) {
					buf[i] = compressed[i * 2] * 256 + compressed[i * 2 + 1];
				}

				var result = [];
				buf.forEach(function (c) {
					result.push(f(c));
				});
				return LZString.decompress(result.join(""));
			}
		},

		//compress into a string that is already URI encoded
		compressToEncodedURIComponent: function (input) {
			if (input == null) return "";
			return LZString._compress(input, 6, function (a) {
				return keyStrUriSafe.charAt(a);
			});
		},

		//decompress from an output of compressToEncodedURIComponent
		decompressFromEncodedURIComponent: function (input) {
			if (input == null) return "";
			if (input == "") return null;
			input = input.replace(/ /g, "+");
			return LZString._decompress(input.length, 32, function (index) {
				return getBaseValue(keyStrUriSafe, input.charAt(index));
			});
		},

		compress: function (uncompressed) {
			return LZString._compress(uncompressed, 16, function (a) {
				return f(a);
			});
		},
		_compress: function (uncompressed, bitsPerChar, getCharFromInt) {
			if (uncompressed == null) return "";
			var i,
				value,
				context_dictionary = {},
				context_dictionaryToCreate = {},
				context_c = "",
				context_wc = "",
				context_w = "",
				context_enlargeIn = 2, // Compensate for the first entry which should not count
				context_dictSize = 3,
				context_numBits = 2,
				context_data = [],
				context_data_val = 0,
				context_data_position = 0,
				ii;

			for (ii = 0; ii < uncompressed.length; ii += 1) {
				context_c = uncompressed.charAt(ii);
				if (
					!Object.prototype.hasOwnProperty.call(
						context_dictionary,
						context_c,
					)
				) {
					context_dictionary[context_c] = context_dictSize++;
					context_dictionaryToCreate[context_c] = true;
				}

				context_wc = context_w + context_c;
				if (
					Object.prototype.hasOwnProperty.call(
						context_dictionary,
						context_wc,
					)
				) {
					context_w = context_wc;
				} else {
					if (
						Object.prototype.hasOwnProperty.call(
							context_dictionaryToCreate,
							context_w,
						)
					) {
						if (context_w.charCodeAt(0) < 256) {
							for (i = 0; i < context_numBits; i++) {
								context_data_val = context_data_val << 1;
								if (context_data_position == bitsPerChar - 1) {
									context_data_position = 0;
									context_data.push(
										getCharFromInt(context_data_val),
									);
									context_data_val = 0;
								} else {
									context_data_position++;
								}
							}
							value = context_w.charCodeAt(0);
							for (i = 0; i < 8; i++) {
								context_data_val =
									(context_data_val << 1) | (value & 1);
								if (context_data_position == bitsPerChar - 1) {
									context_data_position = 0;
									context_data.push(
										getCharFromInt(context_data_val),
									);
									context_data_val = 0;
								} else {
									context_data_position++;
								}
								value = value >> 1;
							}
						} else {
							value = 1;
							for (i = 0; i < context_numBits; i++) {
								context_data_val =
									(context_data_val << 1) | value;
								if (context_data_position == bitsPerChar - 1) {
									context_data_position = 0;
									context_data.push(
										getCharFromInt(context_data_val),
									);
									context_data_val = 0;
								} else {
									context_data_position++;
								}
								value = 0;
							}
							value = context_w.charCodeAt(0);
							for (i = 0; i < 16; i++) {
								context_data_val =
									(context_data_val << 1) | (value & 1);
								if (context_data_position == bitsPerChar - 1) {
									context_data_position = 0;
									context_data.push(
										getCharFromInt(context_data_val),
									);
									context_data_val = 0;
								} else {
									context_data_position++;
								}
								value = value >> 1;
							}
						}
						context_enlargeIn--;
						if (context_enlargeIn == 0) {
							context_enlargeIn = Math.pow(2, context_numBits);
							context_numBits++;
						}
						delete context_dictionaryToCreate[context_w];
					} else {
						value = context_dictionary[context_w];
						for (i = 0; i < context_numBits; i++) {
							context_data_val =
								(context_data_val << 1) | (value & 1);
							if (context_data_position == bitsPerChar - 1) {
								context_data_position = 0;
								context_data.push(
									getCharFromInt(context_data_val),
								);
								context_data_val = 0;
							} else {
								context_data_position++;
							}
							value = value >> 1;
						}
					}
					context_enlargeIn--;
					if (context_enlargeIn == 0) {
						context_enlargeIn = Math.pow(2, context_numBits);
						context_numBits++;
					}
					// Add wc to the dictionary.
					context_dictionary[context_wc] = context_dictSize++;
					context_w = String(context_c);
				}
			}

			// Output the code for w.
			if (context_w !== "") {
				if (
					Object.prototype.hasOwnProperty.call(
						context_dictionaryToCreate,
						context_w,
					)
				) {
					if (context_w.charCodeAt(0) < 256) {
						for (i = 0; i < context_numBits; i++) {
							context_data_val = context_data_val << 1;
							if (context_data_position == bitsPerChar - 1) {
								context_data_position = 0;
								context_data.push(
									getCharFromInt(context_data_val),
								);
								context_data_val = 0;
							} else {
								context_data_position++;
							}
						}
						value = context_w.charCodeAt(0);
						for (i = 0; i < 8; i++) {
							context_data_val =
								(context_data_val << 1) | (value & 1);
							if (context_data_position == bitsPerChar - 1) {
								context_data_position = 0;
								context_data.push(
									getCharFromInt(context_data_val),
								);
								context_data_val = 0;
							} else {
								context_data_position++;
							}
							value = value >> 1;
						}
					} else {
						value = 1;
						for (i = 0; i < context_numBits; i++) {
							context_data_val = (context_data_val << 1) | value;
							if (context_data_position == bitsPerChar - 1) {
								context_data_position = 0;
								context_data.push(
									getCharFromInt(context_data_val),
								);
								context_data_val = 0;
							} else {
								context_data_position++;
							}
							value = 0;
						}
						value = context_w.charCodeAt(0);
						for (i = 0; i < 16; i++) {
							context_data_val =
								(context_data_val << 1) | (value & 1);
							if (context_data_position == bitsPerChar - 1) {
								context_data_position = 0;
								context_data.push(
									getCharFromInt(context_data_val),
								);
								context_data_val = 0;
							} else {
								context_data_position++;
							}
							value = value >> 1;
						}
					}
					context_enlargeIn--;
					if (context_enlargeIn == 0) {
						context_enlargeIn = Math.pow(2, context_numBits);
						context_numBits++;
					}
					delete context_dictionaryToCreate[context_w];
				} else {
					value = context_dictionary[context_w];
					for (i = 0; i < context_numBits; i++) {
						context_data_val =
							(context_data_val << 1) | (value & 1);
						if (context_data_position == bitsPerChar - 1) {
							context_data_position = 0;
							context_data.push(getCharFromInt(context_data_val));
							context_data_val = 0;
						} else {
							context_data_position++;
						}
						value = value >> 1;
					}
				}
				context_enlargeIn--;
				if (context_enlargeIn == 0) {
					context_enlargeIn = Math.pow(2, context_numBits);
					context_numBits++;
				}
			}

			// Mark the end of the stream
			value = 2;
			for (i = 0; i < context_numBits; i++) {
				context_data_val = (context_data_val << 1) | (value & 1);
				if (context_data_position == bitsPerChar - 1) {
					context_data_position = 0;
					context_data.push(getCharFromInt(context_data_val));
					context_data_val = 0;
				} else {
					context_data_position++;
				}
				value = value >> 1;
			}

			// Flush the last char
			while (true) {
				context_data_val = context_data_val << 1;
				if (context_data_position == bitsPerChar - 1) {
					context_data.push(getCharFromInt(context_data_val));
					break;
				} else context_data_position++;
			}
			return context_data.join("");
		},

		decompress: function (compressed) {
			if (compressed == null) return "";
			if (compressed == "") return null;
			return LZString._decompress(
				compressed.length,
				32768,
				function (index) {
					return compressed.charCodeAt(index);
				},
			);
		},

		_decompress: function (length, resetValue, getNextValue) {
			var dictionary = [],
				next,
				enlargeIn = 4,
				dictSize = 4,
				numBits = 3,
				entry = "",
				result = [],
				i,
				w,
				bits,
				resb,
				maxpower,
				power,
				c,
				data = { val: getNextValue(0), position: resetValue, index: 1 };

			for (i = 0; i < 3; i += 1) {
				dictionary[i] = i;
			}

			bits = 0;
			maxpower = Math.pow(2, 2);
			power = 1;
			while (power != maxpower) {
				resb = data.val & data.position;
				data.position >>= 1;
				if (data.position == 0) {
					data.position = resetValue;
					data.val = getNextValue(data.index++);
				}
				bits |= (resb > 0 ? 1 : 0) * power;
				power <<= 1;
			}

			switch ((next = bits)) {
				case 0:
					bits = 0;
					maxpower = Math.pow(2, 8);
					power = 1;
					while (power != maxpower) {
						resb = data.val & data.position;
						data.position >>= 1;
						if (data.position == 0) {
							data.position = resetValue;
							data.val = getNextValue(data.index++);
						}
						bits |= (resb > 0 ? 1 : 0) * power;
						power <<= 1;
					}
					c = f(bits);
					break;
				case 1:
					bits = 0;
					maxpower = Math.pow(2, 16);
					power = 1;
					while (power != maxpower) {
						resb = data.val & data.position;
						data.position >>= 1;
						if (data.position == 0) {
							data.position = resetValue;
							data.val = getNextValue(data.index++);
						}
						bits |= (resb > 0 ? 1 : 0) * power;
						power <<= 1;
					}
					c = f(bits);
					break;
				case 2:
					return "";
			}
			dictionary[3] = c;
			w = c;
			result.push(c);
			while (true) {
				if (data.index > length) {
					return "";
				}

				bits = 0;
				maxpower = Math.pow(2, numBits);
				power = 1;
				while (power != maxpower) {
					resb = data.val & data.position;
					data.position >>= 1;
					if (data.position == 0) {
						data.position = resetValue;
						data.val = getNextValue(data.index++);
					}
					bits |= (resb > 0 ? 1 : 0) * power;
					power <<= 1;
				}

				switch ((c = bits)) {
					case 0:
						bits = 0;
						maxpower = Math.pow(2, 8);
						power = 1;
						while (power != maxpower) {
							resb = data.val & data.position;
							data.position >>= 1;
							if (data.position == 0) {
								data.position = resetValue;
								data.val = getNextValue(data.index++);
							}
							bits |= (resb > 0 ? 1 : 0) * power;
							power <<= 1;
						}

						dictionary[dictSize++] = f(bits);
						c = dictSize - 1;
						enlargeIn--;
						break;
					case 1:
						bits = 0;
						maxpower = Math.pow(2, 16);
						power = 1;
						while (power != maxpower) {
							resb = data.val & data.position;
							data.position >>= 1;
							if (data.position == 0) {
								data.position = resetValue;
								data.val = getNextValue(data.index++);
							}
							bits |= (resb > 0 ? 1 : 0) * power;
							power <<= 1;
						}
						dictionary[dictSize++] = f(bits);
						c = dictSize - 1;
						enlargeIn--;
						break;
					case 2:
						return result.join("");
				}

				if (enlargeIn == 0) {
					enlargeIn = Math.pow(2, numBits);
					numBits++;
				}

				if (dictionary[c]) {
					entry = dictionary[c];
				} else {
					if (c === dictSize) {
						entry = w + w.charAt(0);
					} else {
						return null;
					}
				}
				result.push(entry);

				// Add w+entry[0] to the dictionary.
				dictionary[dictSize++] = w + entry.charAt(0);
				enlargeIn--;

				w = entry;

				if (enlargeIn == 0) {
					enlargeIn = Math.pow(2, numBits);
					numBits++;
				}
			}
		},
	};
	return LZString;
})();

/* harmony default export */ const lz_string = (LZString);

;// CONCATENATED MODULE: ./src/handlers/userCSS.js










class UserCSS {
	#settings;
	#currentActivity;
	#currentUser;
	css = "";
	preview = false;
	cssInLocalStorage = "void-verified-user-css";
	broadcastChannel;
	#csspy = {
		username: "",
		css: "",
	};

	constructor(settings) {
		this.#settings = settings;
		if (
			this.#settings.auth?.token &&
			this.#settings.options.profileCssEnabled.getValue()
		) {
			const cssInLocalStorage = JSON.parse(
				localStorage.getItem(this.cssInLocalStorage),
			);
			if (cssInLocalStorage) {
				this.css = cssInLocalStorage.css;
				this.preview = cssInLocalStorage.preview;
			} else {
				this.getAuthUserCss();
			}
		}

		this.broadcastChannel = new BroadcastChannel("user-css");
		this.broadcastChannel.addEventListener("message", (event) =>
			this.#handleBroadcastMessage(event, this.#settings),
		);
	}

	async checkActivityCss() {
		if (
			!this.#settings.options.activityCssEnabled.getValue() ||
			!window.location.pathname.startsWith("/activity/")
		) {
			return;
		}

		const activityId = window.location.pathname.match(
			/^\/activity\/([^/]*)\/?/,
		)[1];

		if (this.#currentActivity === activityId) {
			return;
		}

		this.#currentActivity = activityId;
		let activity;
		try {
			Toaster.debug("Querying user activity.");
			const anilistAPI = new AnilistAPI(this.#settings);
			activity = await anilistAPI.getActivityCss(activityId);
		} catch {
			Toaster.error("Failed to get activity CSS.");
			return;
		}

		const username = activity.user?.name ?? activity.recipient?.name;

		const userColor =
			activity.user?.options.profileColor ??
			activity.recipient?.options.profileColor;
		const rgb = ColorFunctions.handleAnilistColor(userColor);

		const activityEntry = document.querySelector(
			".container > .activity-entry",
		);

		activityEntry.style.setProperty("--color-blue", rgb);
		activityEntry.style.setProperty("--color-blue-dim", rgb);

		if (username === this.#settings.anilistUser && this.preview) {
			this.#renderCss(this.css, "user-css");
			return;
		}

		if (username === this.#currentUser) {
			this.#clearGlobalCss();
			return;
		}
		new StyleHandler(this.#settings).clearStyles("user-css");

		if (!this.#shouldRenderCss(username)) {
			return;
		}

		const about = activity.user?.about ?? activity.recipient?.about;

		const css = this.#decodeAbout(about)?.customCSS;
		if (css) {
			this.#renderCss(css, "user-css");
		} else {
			Toaster.debug("User has no custom CSS.");
		}

		this.#currentUser = username;
	}

	resetCurrentActivity() {
		this.#currentActivity = null;
	}

	async checkUserCss() {
		if (
			!this.#settings.options.profileCssEnabled.getValue() ||
			!window.location.pathname.startsWith("/user/")
		) {
			return;
		}

		const username =
			window.location.pathname.match(/^\/user\/([^/]*)\/?/)[1];

		if (username === this.#currentUser) {
			return;
		}

		if (username === this.#settings.anilistUser && this.preview) {
			this.#renderCss(this.css, "user-css");
			return;
		}

		if (!this.#shouldRenderCss(username)) {
			new StyleHandler(this.#settings).clearStyles("user-css");
			return;
		}

		this.#currentUser = username;

		let about;
		try {
			Toaster.debug("Querying user CSS.");
			const anilistAPI = new AnilistAPI(this.#settings);
			about = await anilistAPI.getUserAbout(username);
		} catch (error) {
			Toaster.error("Failed to load user's CSS.");
			return;
		}

		const css = this.#decodeAbout(about)?.customCSS;
		if (!css) {
			Toaster.debug("User has no custom CSS.");
			new StyleHandler(this.#settings).clearStyles("user-css");
		}
		this.#renderCss(css, "user-css");
	}

	resetCurrentUser() {
		this.#currentUser = null;
	}

	updateCss(css) {
		this.css = css;
		if (this.preview) {
			this.broadcastChannel.postMessage({type: "css", css});
		}
		this.#saveToLocalStorage();
	}

	async publishUserCss() {
		const username = this.#settings.anilistUser;
		if (!username) {
			return;
		}

		const anilistAPI = new AnilistAPI(this.#settings);
		let about;
		try {
			Toaster.debug("Querying account user about to merge changes into.");
			about = await anilistAPI.getUserAbout(username);
		} catch (error) {
			Toaster.error("Failed to get current about for merging new CSS.");
			return;
		}
		if (!about) {
			about = "";
		}
		let aboutJson = this.#decodeAbout(about);
		aboutJson.customCSS = this.css;
		const compressedAbout = lz_string.compressToBase64(
			JSON.stringify(aboutJson),
		);

		const target = about.match(/^\[\]\(json([A-Za-z0-9+/=]+)\)/)?.[1];

		if (target) {
			about = about.replace(target, compressedAbout);
		} else {
			about = `[](json${compressedAbout})\n\n` + about;
		}
		try {
			Toaster.debug("Publishing CSS.");
			await anilistAPI.saveUserAbout(about);
			Toaster.success("CSS published.");
		} catch (error) {
			Toaster.error("Failed to publish CSS changes.");
		}
	}

	togglePreview() {
		this.preview = !this.preview;
		this.broadcastChannel.postMessage({
			type: "preview",
			preview: this.preview,
		});
		this.#saveToLocalStorage();
	}

	async getAuthUserCss() {
		const anilistAPI = new AnilistAPI(this.#settings);
		const username = this.#settings.anilistUser;
		if (!username) {
			return;
		}
		try {
			Toaster.debug("Querying account user CSS.");
			const about = await anilistAPI.getUserAbout(username);
			const css = this.#decodeAbout(about).customCSS;
			this.css = css;
			this.#saveToLocalStorage();
			return css;
		} catch (error) {
			Toaster.error("Failed to query account user CSS.");
		}
	}

	renderCSSpy(settingsUi) {
		const container = DOM_DOM.create("div");
		container.append(DOM_DOM.create("h3", null, "CSSpy"));

		const usernameInput = ActionInputField(
			"",
			(_, inputField) => {
				this.#handleSpy(inputField, settingsUi);
			},
			SearchDocumentIcon(),
		);

		container.append(usernameInput);

		if (this.#csspy.css === "") {
			return container;
		}

		const cssContainer = DOM_DOM.create("div", "#css-spy-container ace-editor");
		// cssContainer.setAttribute("readonly", true);
		const header = DOM_DOM.create("h5", "layout-header", [
			Link(
				this.#csspy.username,
				`https://anilist.co/user/${this.#csspy.username}/`,
			),
			`'s CSS`,
		]);

		setTimeout(() => {
			AceEditorInitializer.initializeEditor("void-css-spy-container", this.#csspy.css);
			ace.edit("void-css-spy-container").setOption("readOnly", true);
		}, 150);

		container.append(header);
		container.append(cssContainer);

		return container;
	}

	async #handleSpy(inputField, settingsUi) {
		const username = inputField.value;
		if (username === "") {
			return;
		}

		try {
			Toaster.debug(`Spying CSS from ${username}.`);
			const about = await new AnilistAPI(this.#settings).getUserAbout(
				username,
			);
			const css = this.#decodeAbout(about)?.customCSS;
			if (css) {
				this.#csspy.css = css;
				this.#csspy.username = username;
				settingsUi.renderSettingsUiContent();
			} else {
				this.#csspy.css = "";
				this.#csspy.username = "";
				Toaster.debug("User has no custom CSS.");
			}
		} catch (error) {
			this.#csspy.css = "";
			this.#csspy.username = "";
			Toaster.error(`There was an error getting CSS for ${username}`);
			console.error(error);
		}
	}

	#handleBroadcastMessage(event, settings) {
		switch (event.data.type) {
			case "css":
				this.#handlePreviewCssMessage(event.data.css, settings);
				break;
			case "preview":
				this.#handlePreviewToggleMessage(event.data.preview);
				break;
		}
	}

	#handlePreviewCssMessage(css, settings) {
		this.css = css;
		const hasUserCss = document.getElementById(
			"void-verified-user-css-styles",
		);
		if (hasUserCss) {
			new StyleHandler(settings).createStyleLink(css, "user-css");
		}
	}

	#handlePreviewToggleMessage(preview) {
		this.preview = preview;
		const hasUserCss = document.getElementById(
			"void-verified-user-css-styles",
		);
		if (!hasUserCss) {
			return;
		}

		this.resetCurrentUser();
		this.resetCurrentActivity();

		this.checkUserCss();
		this.checkActivityCss();
	}

	#saveToLocalStorage() {
		localStorage.setItem(
			this.cssInLocalStorage,
			JSON.stringify({
				css: this.css,
				preview: this.preview,
			}),
		);
	}

	#shouldRenderCss(username) {
		const user = this.#settings.getUser(username);
		if (
			this.#settings.options.onlyLoadCssFromVerifiedUser.getValue() &&
			!this.#settings.isVerified(username)
		) {
			return false;
		}
		if (user?.onlyLoadCssFromVerifiedUser) {
			return true;
		}
		return !this.#userSpecificRenderingExists();
	}

	#userSpecificRenderingExists() {
		return this.#settings.verifiedUsers.some(
			(user) => user.onlyLoadCssFromVerifiedUser,
		);
	}

	#renderCss(css, id) {
		if (!css) {
			return;
		}

		const styleHandler = new StyleHandler(this.#settings);
		styleHandler.createStyleLink(css, id);
		this.#clearGlobalCss();
	}

	#clearGlobalCss() {
		if (this.#settings.options.globalCssAutoDisable.getValue()) {
			new StyleHandler(this.#settings).clearStyles("global-css");
		}
	}

	#decodeAbout(about) {
		let json = (about || "").match(/^\[\]\(json([A-Za-z0-9+/=]+)\)/);
		if (!json) {
			return {
				customCss: "",
			};
		}

		let jsonData;
		try {
			jsonData = JSON.parse(atob(json[1]));
		} catch (e) {
			jsonData = JSON.parse(lz_string.decompressFromBase64(json[1]));
		}
		return jsonData;
	}
}

;// CONCATENATED MODULE: ./src/utils/markdown.js
const markdownRegex = [
	{
		regex: /^##### (.*$)/gim,
		format: "<h5>$1</h5>",
	},
	{
		regex: /^#### (.*$)/gim,
		format: "<h4>$1</h4>",
	},
	{
		regex: /^### (.*$)/gim,
		format: "<h3>$1</h3>",
	},
	{
		regex: /^## (.*$)/gim,
		format: "<h2>$1</h2>",
	},
	{
		regex: /^# (.*$)/gim,
		format: "<h1>$1</h1>",
	},
	{
		regex: /\_\_(.*)\_\_/gim,
		format: "<strong>$1</strong>",
	},
	{
		regex: /\_(.*)\_/gim,
		format: "<em>$1</em>",
	},
	{
		regex: /(?:\r\n|\r|\n)/g,
		format: "<br>",
	},
	{
		regex: /\~~~(.*)\~~~/gim,
		format: "<center>$1</center>",
	},
	{
		regex: /\[([^\]]*)\]\(([^\)]+)\)/gi,
		format: "<a href='$2' target='_blank'>$1</a>",
	},
	{
		regex: /\~\!(.*)\!\~/gi,
		format: "<span class='markdown-spoiler'><span>$1</span></span>",
	},
	{
		regex: /img([0-9]+%?)\(([^\)]+)\)/g,
		format: "<img src='$2' width='$1' >",
	},
];

class Markdown {
	static parse(markdown) {
		let html = markdown;
		for (const parser of markdownRegex) {
			html = html.replace(parser.regex, parser.format);
		}

		return html;
	}
}

;// CONCATENATED MODULE: ./src/handlers/layoutDesigner.js








class Layout {
	avatar;
	banner;
	bio;
	color;
	donatorBadge;
	name;

	constructor(layout) {
		this.avatar = layout?.avatar ?? "";
		this.banner = layout?.banner ?? "";
		this.bio = layout?.bio ?? "";
		this.color = layout?.color ?? "";
		this.donatorBadge = layout?.donatorBadge ?? "";
		this.name = layout?.name ?? "New Layout";
	}
}

class LayoutDesigner {
	#settings;
	#layoutsInLocalStorage = "void-verified-layouts";
	#originalHtml;
	#broadcastChannel;
	#donatorTier = 0;
	#anilistSettings;
	#layout;
	#layouts = {
		selectedLayout: 0,
		preview: false,
		disableCss: false,
		layoutsList: [new Layout()],
	};

	constructor(settings) {
		this.#settings = settings;

		this.#broadcastChannel = new BroadcastChannel("void-layouts");
		this.#broadcastChannel.addEventListener("message", (event) =>
			this.#handleBroadcastMessage(event),
		);

		const layouts = JSON.parse(
			localStorage.getItem(this.#layoutsInLocalStorage),
		);
		if (layouts) {
			this.#layouts = layouts;
			this.#layouts.layoutsList = layouts.layoutsList.map(
				(layout) => new Layout(layout),
			);
		}

		this.#anilistSettings = JSON.parse(localStorage.getItem("auth"));

		this.#donatorTier = this.#anilistSettings?.donatorTier;
		this.#layout = this.#getSelectedLayout();
	}

	renderLayoutPreview() {
		if (!this.#settings.options.layoutDesignerEnabled.getValue()) {
			return;
		}

		if (!window.location.pathname.startsWith("/user/")) {
			return;
		}
		const username =
			window.location.pathname.match(/^\/user\/([^/]*)\/?/)[1];

		if (username !== this.#settings.anilistUser || !this.#layouts.preview) {
			return;
		}

		this.#handleAvatar(this.#layout.avatar);
		this.#handleBanner(this.#layout.banner);
		this.#handleColor(this.#layout.color);
		this.#handleDonatorBadge(this.#layout.donatorBadge);
		this.#handleCss();
		this.#handleAbout(Markdown.parse(this.#layout.bio ?? ""));
	}

	#handleBroadcastMessage(event) {
		switch (event.data.type) {
			case "preview":
				this.#handlePreviewToggleMessage(event.data.preview);
				break;
			case "layout":
				this.#handleLayoutMessage(event.data.layout);
				break;
			case "css":
				this.#handleCssMessage(event.data.disableCss);
				break;
		}
	}

	#handlePreviewToggleMessage(preview) {
		this.#layouts.preview = preview;
		if (
			preview ||
			!window.location.pathname.startsWith(
				`/user/${this.#settings.anilistUser}/`,
			)
		) {
			return;
		}

		this.#handleAvatar(this.#anilistSettings?.avatar?.large);
		this.#handleBanner(this.#anilistSettings?.bannerImage);
		this.#handleColor(this.#anilistSettings.options.profileColor);
		this.#handleDonatorBadge(this.#anilistSettings.donatorBadge);
		this.#layouts.disableCss = false;
		this.#handleCss();
		this.#handleAbout(this.#originalHtml);
	}

	#handleLayoutMessage(layout) {
		this.#layout = layout;
	}

	#handleCssMessage(disableCss) {
		this.#layouts.disableCss = disableCss;
	}

	#handleAvatar(avatar) {
		if (avatar === "") {
			return;
		}

		const avatarElement = document.querySelector("img.avatar");
		if (avatarElement.src !== avatar) {
			avatarElement.src = avatar;
		}

		const avatarLinks = document.querySelectorAll(
			`a.avatar[href*="${this.#settings.anilistUser}"]`,
		);
		for (const avatarLink of avatarLinks) {
			if (avatarLink.style !== `background-image: url(${avatar})`) {
				avatarLink.style = `background-image: url(${avatar})`;
			}
		}
	}

	#handleBanner(banner) {
		if (banner === "") {
			return;
		}

		const bannerElement = document.querySelector(".banner");
		if (bannerElement.style !== `background-image: url(${banner})`) {
			bannerElement.style = `background-image: url(${banner})`;
		}
	}

	#handleColor(value) {
		let color;
		try {
			color = ColorFunctions.handleAnilistColor(value);
		} catch (err) {
			return;
		}

		const pageContent = document.querySelector(".page-content > .user");
		pageContent.style.setProperty("--color-blue", color);
		pageContent.style.setProperty("--color-blue-dim", color);
	}

	#handleDonatorBadge(donatorText) {
		if (this.#donatorTier < 3 || donatorText === "") {
			return;
		}

		const donatorBadge = document.querySelector(".donator-badge");
		if (donatorBadge.innerText !== donatorText) {
			donatorBadge.innerText = donatorText;
		}
	}

	#handleCss() {
		if (this.#layouts.disableCss) {
			DOM_DOM.get("#verified-user-css-styles")?.setAttribute(
				"disabled",
				true,
			);
		} else {
			DOM_DOM.get("#verified-user-css-styles")?.removeAttribute("disabled");
		}
	}

	#handleAbout(about) {
		const aboutContainer = document.querySelector(".about .markdown");

		if (!aboutContainer) {
			return;
		}

		if (!this.#originalHtml) {
			this.#originalHtml = aboutContainer.innerHTML;
		}

		aboutContainer.innerHTML = about !== "" ? about : this.#originalHtml;
	}

	renderSettings(settingsUi) {
		if (!this.#settings.options.layoutDesignerEnabled.getValue()) {
			return "";
		}
		const container = DOM_DOM.create("div", "layout-designer-container");

		const header = DOM_DOM.create("h3", null, "Layout Designer");

		const layoutSelector = this.#createLayoutSelector(settingsUi);
		const layoutInfoSection = this.#layoutInfoSection(settingsUi);

		const imageSection = DOM_DOM.create("div");

		imageSection.append(
			this.#createImageField("avatar", this.#layout.avatar, settingsUi),
		);

		imageSection.append(
			this.#createImageField("banner", this.#layout.banner, settingsUi),
		);

		const imageUploadNote = Note(
			"You can preview avatar & banner by providing a link to an image. If you have configured a image host, you can upload images by pasting them to the fields. ",
		);

		imageUploadNote.append(
			DOM_DOM.create("br"),
			"Unfortunately AniList API does not support third parties uploading new avatars or banners. You have to upload them separately.",
		);

		const colorSelection = this.#createColorSelection(settingsUi);

		const previewButton = Button(
			this.#layouts.preview ? "Disable Preview" : "Enable Preview",
			() => {
				this.#togglePreview(settingsUi);
			},
		);

		const cssButton = Button(
			this.#layouts.disableCss ? "Enable Css" : "Disable Css",
			() => {
				this.#toggleCss();
				cssButton.innerText = this.#layouts.disableCss
					? "Enable Css"
					: "Disable Css";
			},
		);

		const getAboutButton = Button(
			"Reset About",
			() => {
				this.#getUserAbout(settingsUi);
			},
			"error",
		);

		container.append(
			header,
			layoutSelector,
			layoutInfoSection,
			imageSection,
			imageUploadNote,
			colorSelection,
		);

		if (this.#donatorTier >= 3) {
			container.append(this.#createDonatorBadgeField(settingsUi));
		}

		container.append(this.#createAboutSection(settingsUi), getAboutButton);

		if (this.#settings.auth?.token) {
			const saveAboutButton = Button(
				"Publish About",
				(event) => {
					this.#publishAbout(event, settingsUi);
				},
				"success",
			);
			container.append(saveAboutButton);
		}

		container.append(previewButton);

		if (this.#layouts.preview) {
			container.append(cssButton);
		}
		return container;
	}

	#createLayoutSelector(settingsUi) {
		const container = DOM_DOM.create("div");
		container.append(
			IconButton(AddIcon(), () => {
				this.#addLayout();
				settingsUi.renderSettingsUiContent();
			}),
		);
		const options = this.#layouts.layoutsList.map((layout, index) =>
			Option(
				`${layout.name} #${index + 1}`,
				index === this.#layouts.selectedLayout,
				() => {
					this.#switchLayout(index);
					settingsUi.renderSettingsUiContent();
				},
			),
		);

		container.append(Select(options));
		return container;
	}

	#switchLayout(index) {
		this.#layout = this.#layouts.layoutsList[index];
		this.#layouts.selectedLayout = index;
		this.#saveToLocalStorage();
		this.#broadcastLayoutChange();
	}

	#addLayout() {
		const layout = new Layout();
		this.#layout = layout;
		this.#layouts.layoutsList.push(layout);
		this.#layouts.selectedLayout = Math.max(
			this.#layouts.layoutsList.length - 1,
			0,
		);
		this.#saveToLocalStorage();
		this.#broadcastLayoutChange();
	}

	#deleteLayout() {
		if (!window.confirm("Are you sure you want to delete this layout?")) {
			return;
		}
		this.#layouts.layoutsList.splice(this.#layouts.selectedLayout, 1);
		this.#layouts.selectedLayout = Math.max(
			this.#layouts.selectedLayout - 1,
			0,
		);

		if (this.#layouts.layoutsList.length === 0) {
			this.#layouts.layoutsList.push(new Layout());
		}

		this.#layout = this.#layouts.layoutsList[this.#layouts.selectedLayout];
		this.#saveToLocalStorage();
		this.#broadcastLayoutChange();
	}

	#layoutInfoSection(settingsUi) {
		const container = Label(
			"Layout name",
			InputField(this.#layout?.name, (event) => {
				this.#updateOption("name", event.target.value, settingsUi);
			}),
		);

		container.append(
			IconButton(TrashcanIcon(), () => {
				this.#deleteLayout();
				settingsUi.renderSettingsUiContent();
			}),
		);
		return container;
	}

	#createInputField(field, value, settingsUi) {
		const input = InputField(value, (event) => {
			this.#updateOption(field, event.target.value, settingsUi);
		});
		return input;
	}

	#createImageField(field, value, settingsUi) {
		const container = DOM_DOM.create("div", "layout-image-container");
		const header = DOM_DOM.create("h5", "layout-header", field);
		const display = DOM_DOM.create("div", `layout-image-display ${field}`);
		display.style.backgroundImage = `url(${value})`;
		const input = this.#createInputField(field, value, settingsUi);

		container.append(header, display, input);
		return container;
	}

	#createDonatorBadgeField(settingsUi) {
		const container = DOM_DOM.create("div", "layout-donator-badge-container");
		const donatorHeader = DOM_DOM.create(
			"h5",
			"layout-header",
			"Donator Badge",
		);
		const donatorInput = InputField(this.#layout.donatorBadge, (event) => {
			this.#updateOption("donatorBadge", event.target.value, settingsUi);
		});
		donatorInput.setAttribute("maxlength", 24);

		container.append(donatorHeader, donatorInput);

		if (
			this.#layout.donatorBadge !== this.#anilistSettings.donatorBadge &&
			this.#layout.donatorBadge !== "" &&
			this.#settings.auth?.token
		) {
			const publishButton = Button("Publish Donator Badge", (event) => {
				this.#publishDonatorText(event, settingsUi);
			});
			container.append(DOM_DOM.create("div", null, publishButton));
		}

		return container;
	}

	#createColorSelection(settingsUi) {
		const container = DOM_DOM.create("div", "layout-color-selection");

		const header = DOM_DOM.create("h5", "layout-header", "Color");
		container.append(header);

		for (const anilistColor of ColorFunctions.defaultColors) {
			container.append(this.#createColorButton(anilistColor, settingsUi));
		}

		if (this.#donatorTier >= 2) {
			const isDefaultColor = ColorFunctions.defaultColors.some(
				(color) => color === this.#layout.color,
			);

			const colorInput = ColorPicker(
				isDefaultColor ? "" : this.#layout.color,
				(event) => {
					this.#updateOption("color", event.target.value, settingsUi);
				},
			);
			if (!isDefaultColor && this.#layout.color !== "") {
				colorInput.classList.add("active");
			}
			container.append(colorInput);
		}

		if (
			this.#settings.auth?.token &&
			this.#layout.color.toLocaleLowerCase() !==
				this.#anilistSettings?.options?.profileColor?.toLocaleLowerCase() &&
			this.#layout.color !== ""
		) {
			const publishButton = Button("Publish Color", (event) => {
				this.#publishColor(event, settingsUi);
			});
			container.append(DOM_DOM.create("div", null, publishButton));
		}

		return container;
	}

	#createAboutSection(settingsUi) {
		const container = DOM_DOM.create("div");
		const aboutHeader = DOM_DOM.create("h5", "layout-header", "About");
		const aboutInput = TextArea(this.#layout.bio, (event) => {
			this.#updateOption("bio", event.target.value, settingsUi);
		});
		const note = Note(
			"Please note that VoidVerified does not have access to AniList's markdown parser. AniList specific features might not be available while previewing. Recommended to be used for smaller changes like previewing a different image for a layout.",
		);

		container.append(aboutHeader, aboutInput, note);
		return container;
	}

	async #publishAbout(event, settingsUi) {
		const button = event.target;
		button.innerText = "Publishing...";

		try {
			const anilistAPI = new AnilistAPI(this.#settings);
			let currentAbout = await anilistAPI.getUserAbout(
				this.#settings.anilistUser,
			);
			if (!currentAbout) {
				currentAbout = "";
			}
			const about = this.#transformAbout(currentAbout, this.#layout.bio);

			await anilistAPI.saveUserAbout(about);
			Toaster.success("About published.");
			settingsUi.renderSettingsUiContent();
		} catch (error) {
			console.error(error);
			Toaster.error("Failed to publish about.");
		}
	}

	#transformAbout(currentAbout, newAbout) {
		const json = currentAbout.match(/^\[\]\(json([A-Za-z0-9+/=]+)\)/)?.[1];

		if (!json) {
			return newAbout;
		}

		const about = `[](json${json})` + newAbout;
		return about;
	}

	async #publishColor(event, settingsUi) {
		const button = event.target;
		const color = this.#layout.color;
		button.innerText = "Publishing...";

		try {
			const anilistAPI = new AnilistAPI(this.#settings);
			const result = await anilistAPI.saveUserColor(color);
			const profileColor = result.UpdateUser?.options?.profileColor;
			this.#anilistSettings.options.profileColor = profileColor;
			Toaster.success("Color published.");
		} catch (error) {
			Toaster.error("Failed to publish color.");
			console.error("Failed to publish color.", error);
		} finally {
			settingsUi.renderSettingsUiContent();
		}
	}

	async #publishDonatorText(event, settingsUi) {
		const button = event.target;
		const donatorText = this.#layout.donatorBadge;
		button.innerText = "Publishing...";

		try {
			const anilistAPI = new AnilistAPI(this.#settings);
			const result = await anilistAPI.saveDonatorBadge(donatorText);
			const donatorBadge = result.UpdateUser?.donatorBadge;
			this.#anilistSettings.donatorBadge = donatorBadge;
			Toaster.success("Donator badge published.");
		} catch (error) {
			Toaster.error("Failed to publish donator badge.");
			console.error("Failed to publish donator badge.", error);
		} finally {
			settingsUi.renderSettingsUiContent();
		}
	}

	async #getUserAbout(settingsUi) {
		if (
			this.#layout.bio !== "" &&
			!window.confirm(
				"Are you sure you want to reset about? Any changes will be lost.",
			)
		) {
			return;
		}

		try {
			Toaster.debug("Querying user about.");
			const anilistAPI = new AnilistAPI(this.#settings);
			const about = await anilistAPI.getUserAbout(
				this.#settings.anilistUser,
			);
			const clearedAbout = this.#removeJson(about);

			this.#updateOption("bio", clearedAbout, settingsUi);
			Toaster.success("About reset.");
		} catch (error) {
			Toaster.error("Failed to query current about from AniList API.");
		}
	}

	#removeJson(about) {
		return about.replace(/^\[\]\(json([A-Za-z0-9+/=]+)\)/, "");
	}

	#createColorButton(anilistColor, settingsUi) {
		const button = DOM_DOM.create("div", "color-button");
		button.style.backgroundColor = `rgb(${ColorFunctions.handleAnilistColor(
			anilistColor,
		)})`;

		button.addEventListener("click", () => {
			this.#updateOption("color", anilistColor, settingsUi);
		});

		if (this.#layout.color === anilistColor) {
			button.classList.add("active");
		}

		return button;
	}

	#updateOption(field, value, settingsUi) {
		this.#layout[field] = value;
		this.#updateLayout(this.#layout);
		settingsUi.renderSettingsUiContent();
	}

	#togglePreview(settingsUi) {
		this.#layouts.preview = !this.#layouts.preview;
		if (!this.#layouts.preview) {
			this.#layouts.disableCss = false;
		}
		this.#broadcastChannel.postMessage({
			type: "preview",
			preview: this.#layouts.preview,
		});
		this.#saveToLocalStorage();
		settingsUi.renderSettingsUiContent();
	}

	#toggleCss() {
		this.#layouts.disableCss = !this.#layouts.disableCss;
		this.#broadcastChannel.postMessage({
			type: "css",
			disableCss: this.#layouts.disableCss,
		});
		this.#saveToLocalStorage();
	}

	#getSelectedLayout() {
		return this.#layouts.layoutsList[this.#layouts.selectedLayout];
	}

	#updateLayout(layout) {
		this.#layouts.layoutsList[this.#layouts.selectedLayout] = layout;
		this.#saveToLocalStorage();
		this.#broadcastLayoutChange();
	}

	#broadcastLayoutChange() {
		this.#broadcastChannel.postMessage({
			type: "layout",
			layout: this.#layout,
		});
	}

	#saveToLocalStorage() {
		localStorage.setItem(
			this.#layoutsInLocalStorage,
			JSON.stringify(this.#layouts),
		);
	}
}

;// CONCATENATED MODULE: ./src/assets/imageFormats.js
const ImageFormats = [
	"jpg",
	"png",
	"gif",
	"webp",
	"apng",
	"avif",
	"jpeg",
	"svg",
];

;// CONCATENATED MODULE: ./src/handlers/gifKeyboardHandler.js






const keyboardTabs = {
	gifs: "GIFS",
	images: "Images",
};

class GifKeyboardConfig {
	gifs;
	gifSize;
	images;
	#configInLocalStorage = "void-verified-gif-keyboard";
	constructor() {
		const config = JSON.parse(
			localStorage.getItem(this.#configInLocalStorage),
		);
		this.gifs = config?.gifs ?? [];
		this.images = config?.images ?? [];
		this.gifSize = config?.gifSize ?? 260;
	}

	save() {
		localStorage.setItem(
			"void-verified-gif-keyboard",
			JSON.stringify(this),
		);
	}
}

class GifKeyboardHandler {
	#settings;
	#activeTab = keyboardTabs.gifs;
	#paginationPage = 0;
	#pageSize = 30;
	config;
	constructor(settings) {
		this.#settings = settings;
		this.config = new GifKeyboardConfig();
	}

	handleGifKeyboard() {
		this.#addGifKeyboards();
		this.#addMediaLikeButtons();
	}

	#addMediaLikeButtons() {
		if (!this.#settings.options.gifKeyboardEnabled.getValue()) {
			return;
		}

		if (!this.#settings.options.gifKeyboardLikeButtonsEnabled.getValue()) {
			return;
		}

		const gifs = document.querySelectorAll(
			":is(.activity-markdown, .reply-markdown) .markdown img[src$='.gif']",
		);
		for (const gif of gifs) {
			this.#addMediaLikeButton(gif, keyboardTabs.gifs, this.config.gifs);
		}

		const images = ImageFormats.map((format) => {
			return [
				...document.querySelectorAll(
					`:is(.activity-markdown, .reply-markdown) .markdown img[src$='.${format}']`,
				),
			];
		}).flat(1);
		for (const image of images) {
			this.#addMediaLikeButton(
				image,
				keyboardTabs.images,
				this.config.images,
			);
		}
	}

	#addMediaLikeButton(media, mediaType, mediaList) {
		if (media.parentElement.classList.contains("void-gif-like-container")) {
			return;
		}

		const img = media.cloneNode();
		img.removeAttribute("width");

		const gifContainer = GifContainer(
			img,
			() => {
				this.#addOrRemoveMedia(media.src, mediaType);
				this.config.save();
				this.#refreshKeyboards();
			},
			mediaList,
		);

		const width = media.getAttribute("width");
		if (width) {
			gifContainer.style.maxWidth = width?.endsWith("%")
				? width
				: `${width}px`;
		} else {
			gifContainer.style.maxWidth = `${img.width}px`;
		}

		media.replaceWith(gifContainer);
	}

	#addGifKeyboards() {
		if (!this.#settings.options.gifKeyboardEnabled.getValue()) {
			return;
		}

		const markdownEditors = document.querySelectorAll(".markdown-editor");
		for (const markdownEditor of markdownEditors) {
			if (markdownEditor.querySelector(".void-gif-button")) {
				continue;
			}

			const gifKeyboard = GifKeyboard(this.#createKeyboardHeader());

			gifKeyboard.classList.add("void-hidden");
			this.#renderMediaList(gifKeyboard, markdownEditor);
			this.#renderControls(gifKeyboard, markdownEditor);

			const iconButton = IconButton(
				GifIcon(),
				() => {
					this.#toggleKeyboardVisibility(
						gifKeyboard,
						markdownEditor,
					);
				},
				"gif-button",
			);
			iconButton.setAttribute("title", "GIF Keyboard");
			markdownEditor.append(iconButton);

			markdownEditor.parentNode.insertBefore(
				gifKeyboard,
				markdownEditor.nextSibling,
			);
		}
	}

	#refreshKeyboards() {
		const keyboards = DOM_DOM.getAll("gif-keyboard-container");
		for (const keyboard of keyboards) {
			this.#refreshKeyboard(keyboard);
		}
	}

	#refreshKeyboard(keyboard) {
		const markdownEditor =
			keyboard.parentElement.querySelector(".markdown-editor");
		this.#renderControls(keyboard, markdownEditor);
		this.#renderMediaList(keyboard, markdownEditor);
	}

	#createKeyboardHeader = () => {
		const header = DOM_DOM.create("div", "gif-keyboard-header");

		const options = Object.values(keyboardTabs).map((option) =>
			Option(option, option === this.#activeTab, (event) => {
				this.#activeTab = option;
				const keyboard =
					event.target.parentElement.parentElement.parentElement; // oh god
				this.#refreshKeyboard(keyboard);
				event.target.parentElement.parentElement.replaceWith(
					this.#createKeyboardHeader(),
				);
			}),
		);
		header.append(Select(options));

		header.append(
			RangeField(
				this.config.gifSize,
				(event) => {
					this.config.gifSize = event.target.value;
					this.config.save();
				},
				600,
				10,
				10,
			),
		);
		return header;
	};

	#addOrRemoveMedia(url, mediaType) {
		let mediaList =
			mediaType === keyboardTabs.gifs
				? this.config.gifs
				: this.config.images;
		if (mediaList.includes(url)) {
			mediaList = mediaList.filter((media) => media !== url);
		} else {
			mediaList.push(url);
		}
		switch (mediaType) {
			case keyboardTabs.gifs:
				this.config.gifs = mediaList;
				break;
			case keyboardTabs.images:
				this.config.images = mediaList;
				break;
		}
	}

	#toggleKeyboardVisibility(keyboard) {
		if (keyboard.classList.contains("void-hidden")) {
			this.#refreshKeyboard(keyboard);
			keyboard.classList.remove("void-hidden");
		} else {
			keyboard.classList.add("void-hidden");
		}
	}

	#renderMediaList(keyboard, markdownEditor) {
		if (!keyboard || !markdownEditor) {
			return;
		}
		const mediaItems = keyboard.querySelector(".void-gif-keyboard-list");
		const columns = [1, 2, 3].map(() => {
			return DOM_DOM.create("div", "gif-keyboard-list-column");
		});
		mediaItems.replaceChildren(...columns);
		const textarea = markdownEditor.parentElement.querySelector("textarea");
		const mediaList =
			this.#activeTab === keyboardTabs.gifs
				? this.config.gifs
				: this.config.images;
		if (mediaList.length === 0) {
			mediaItems.replaceChildren(
				DOM_DOM.create(
					"div",
					"gif-keyboard-list-placeholder",
					this.#activeTab === keyboardTabs.gifs
						? "It's pronounced GIF."
						: "You have no funny memes :c",
				),
			);
		}
		for (const [index, media] of mediaList
			.slice(
				this.#paginationPage * this.#pageSize,
				this.#paginationPage * this.#pageSize + this.#pageSize,
			)
			.entries()) {
			mediaItems.children.item(index % 3).append(
				GifItem(
					media,
					() => {
						textarea.setRangeText(
							`img${this.config.gifSize}(${media})`,
						);
					},
					() => {
						this.#addOrRemoveMedia(media, this.#activeTab);
						this.config.save();
					},
					mediaList,
				),
			);
		}
	}

	#renderControls(keyboard, markdownEditor) {
		const container = keyboard.querySelector(
			".void-gif-keyboard-control-container",
		);
		const mediaField = this.#createMediaAddField(keyboard, markdownEditor);
		const pagination = this.#createPagination(keyboard, markdownEditor);
		container.replaceChildren(mediaField, pagination);
	}

	#createMediaAddField(keyboard, markdownEditor) {
		const actionfield = ActionInputField(
			"",
			(_, inputField) => {
				this.#handleAddMediaField(inputField, keyboard, markdownEditor);
			},
			AddIcon(),
		);
		actionfield
			.querySelector("input")
			.setAttribute("placeholder", "Add media...");

		return actionfield;
	}

	#handleAddMediaField(inputField, keyboard, markdownEditor) {
		const url = inputField.value;
		inputField.value = "";

		let format;
		if (url.toLowerCase().endsWith(".gif")) {
			format = keyboardTabs.gifs;
		} else if (
			ImageFormats.some((imgFormat) =>
				url.toLowerCase().endsWith(imgFormat.toLocaleLowerCase()),
			)
		) {
			format = keyboardTabs.images;
		}
		if (!format) {
			Toaster.error("Url was not recognized as image or GIF.");
			return;
		}

		Toaster.success(`Added media to ${format}`);
		this.#addOrRemoveMedia(url, format);
		this.config.save();
		this.#refreshKeyboard(keyboard);
	}

	#createPagination(keyboard, markdownEditor) {
		const container = DOM_DOM.create(
			"div",
			"gif-keyboard-pagination-container",
		);
		const mediaList =
			this.#activeTab === keyboardTabs.gifs
				? this.config.gifs
				: this.config.images;
		const maxPages = Math.ceil(mediaList.length / this.#pageSize) - 1;

		if (this.#paginationPage > maxPages) {
			this.#paginationPage = maxPages;
		}

		container.append(
			Pagination(this.#paginationPage, maxPages, (page) => {
				this.#paginationPage = page;
				this.#refreshKeyboards(keyboard, markdownEditor);
			}),
		);
		return container;
	}
}

;// CONCATENATED MODULE: ./src/handlers/anilistFeedFixHandler.js


class AnilistFeedFixHandler {
	#hidePrivateMessages = false;
	#settings;
	#styleHandler;
	constructor(settings) {
		this.#settings = settings;
		this.#styleHandler = new StyleHandler(settings);
	}

	handleFilters() {
		if (
			!this.#settings.options.hideMessagesFromListFeed.getValue() ||
			window.location.pathname !== `/user/${this.#settings.anilistUser}/`
		) {
			return;
		}

		const feedOptions = Array.from(
			document.querySelector(".activity-feed-wrap .section-header ul")
				.children,
		);

		if (!feedOptions) {
			return;
		}

		if (feedOptions[3].getAttribute("void-fixed")) {
			return;
		}

		for (const option of feedOptions.slice(0, 3)) {
			option.addEventListener("click", () => {
				this.#hidePrivateMessages = false;
			});
		}

		feedOptions[3].addEventListener("click", () => {
			this.#hidePrivateMessages = true;
		});
		feedOptions[3].setAttribute("void-fixed", true);
	}

	handleFix() {
		if (
			window.location.pathname !== `/user/${this.#settings.anilistUser}/`
		) {
			this.#styleHandler.clearStyles("private-message-fix");
			this.#hidePrivateMessages = false;
			return;
		}

		if (this.#hidePrivateMessages) {
			this.#styleHandler.createStyleLink(
				hidePrivateMessagesStyle,
				"private-message-fix",
			);
		} else {
			this.#styleHandler.clearStyles("private-message-fix");
		}
	}
}

const hidePrivateMessagesStyle = `
    .activity-feed-wrap .activity-message {
        display: none !important;
    }
`;

;// CONCATENATED MODULE: ./src/components/readNotifications.js
class ReadNotifications {
	static #notificationsInLocalStorage = "void-verified-read-notifications";
	static #unreadNotificationsFeed = new Set();
	static #unreadNotificationsCount = new Set();
	static getUnreadNotificationsCount(notifications) {
		const readNotifications = this.#getReadNotifications();
		let unreadNotificationsCount = 0;
		this.#unreadNotificationsCount = new Set();
		for (const notification of notifications) {
			if (!readNotifications.has(notification.id)) {
				unreadNotificationsCount++;
				this.#unreadNotificationsCount.add(notification.id);
			}
		}
		return unreadNotificationsCount;
	}

	static markAllAsRead() {
		const readNotifications = this.#getReadNotifications();
		this.#unreadNotificationsFeed.forEach((notification) => {
			readNotifications.add(notification);
		});
		this.#unreadNotificationsCount.forEach((notification) => {
			readNotifications.add(notification);
		});
		this.#unreadNotificationsCount = new Set();
		this.#unreadNotificationsFeed = new Set();
		this.#saveReadNotifications(readNotifications);
	}

	static markMultipleAsRead(notifications) {
		const readNotifications = this.#getReadNotifications();
		notifications.forEach((notification) => {
			readNotifications.add(notification);
		});
		this.#saveReadNotifications(readNotifications);
	}

	static markMultipleAsUnread(notifications) {
		const readNotifications = this.#getReadNotifications();
		notifications.forEach((notification) => {
			readNotifications.delete(notification);
		});
		this.#saveReadNotifications(readNotifications);
	}

	static isRead(notificationId) {
		const readNotifications = this.#getReadNotifications();
		const isRead = readNotifications.has(notificationId);
		if (!isRead) {
			this.#unreadNotificationsFeed.add(notificationId);
		}
		return isRead;
	}

	static resetUnreadNotificationsFeed() {
		this.#unreadNotificationsFeed = new Set();
	}

	static #getReadNotifications() {
		return new Set(
			JSON.parse(localStorage.getItem(this.#notificationsInLocalStorage)),
		);
	}

	static #saveReadNotifications(notifications) {
		let notificationsAsArray = Array.from(notifications);
		if (notificationsAsArray.length > 10000) {
			notificationsAsArray = notificationsAsArray.slice(-10000);
		}
		localStorage.setItem(
			this.#notificationsInLocalStorage,
			JSON.stringify(notificationsAsArray),
		);
	}
}

;// CONCATENATED MODULE: ./src/components/notificationWrapper.js




const NotificationWrapper = (notification, addReadListener = false) => {
	const wrapper = DOM_DOM.create("div", "notification-wrapper");
	const previewWrapper = createPreview(notification);
	const context = createContext(notification);

	const timestamp = DOM_DOM.create(
		"div",
		"notification-timestamp",
		timeAgo(notification.createdAt),
	);

	wrapper.append(previewWrapper, context, timestamp);
	if (addReadListener) {
		wrapper.addEventListener("click", () => {
			if (wrapper.classList.contains("void-unread-notification")) {
				markAsRead(notification);
				wrapper.classList.remove("void-unread-notification");
			} else {
				markAsUnread(notification);
				wrapper.classList.add("void-unread-notification");
			}
		});
	}
	return wrapper;
};

const markAsRead = (notification) => {
	const notifications = [
		notification.id,
		...notification.group?.map((item) => item.notificationId),
	];
	ReadNotifications.markMultipleAsRead(notifications);
};

const markAsUnread = (notification) => {
	const notifications = [
		notification.id,
		...notification.group?.map((item) => item.notificationId),
	];
	ReadNotifications.markMultipleAsUnread(notifications);
};

const createPreview = (notification) => {
	const previewWrapper = DOM_DOM.create("div", "notification-preview-wrapper");
	if (notification.type === "MEDIA_DELETION") {
		return previewWrapper;
	}
	const linkUrl = notification.user
		? `https://anilist.co/user/${notification.user.name}/`
		: `https://anilist.co/${notification.media?.type.toLowerCase()}/${
				notification.media?.id
			}`;
	const preview = Link("", linkUrl, "", "notification-preview");
	if (notification.media) {
		preview.classList.add("void-notification-preview-media");
	}
	const imageUrl =
		notification.user?.avatar.large ?? notification.media?.coverImage.large;
	preview.style.backgroundImage = `url(${imageUrl})`;
	previewWrapper.append(preview);

	if (notification.group?.length > 0) {
		preview.setAttribute("data-count", `+${notification.group.length}`);
		const group = createGroup(notification);
		previewWrapper.append(group);
	}

	previewWrapper.addEventListener("click", (event) => {
		event.stopPropagation();
	});

	if (notification.activity) {
		previewWrapper.append(createActivityRelation(notification.activity));
	}

	return previewWrapper;
};

const createActivityRelation = (activity) => {
	let url;
	let image;
	switch (activity.type) {
		case "ANIME_LIST":
			url = `https://anilist.co/anime/${activity.media.id}`;
			image = activity.media.coverImage.large;
			break;
		case "MANGA_LIST":
			url = `https://anilist.co/manga/${activity.media.id}`;
			image = activity.media.coverImage.large;
			break;
		case "TEXT":
			url = `https://anilist.co/user/${activity.user.name}`;
			image = activity.user.avatar.large;
			break;
		case "MESSAGE":
			url = `https://anilist.co/user/${activity.recipient.name}`;
			image = activity.recipient.avatar.large;
			break;
	}
	const activityRelation = Link("", url, "", "notification-preview-relation");
	activityRelation.style.backgroundImage = `url(${image})`;
	return activityRelation;
};

const createGroup = (notification) => {
	const group = DOM_DOM.create("div", "notification-group");
	for (const user of notification.group.slice(
		0,
		Math.min(10, notification.group.length),
	)) {
		const groupItem = Link(
			"",
			`https://anilist.co/user/${user.name}/`,
			"",
			"notification-group-item",
		);
		groupItem.style.backgroundImage = `url(${user.avatar.large})`;
		group.append(groupItem);
	}
	return group;
};

const createContext = (notification) => {
	if (
		notification.type === "AIRING" ||
		notification.type === "RELATED_MEDIA_ADDITION" ||
		notification.type === "MEDIA_DATA_CHANGE" ||
		notification.type === "MEDIA_DELETION"
	) {
		return createMediaContext(notification);
	}

	const highlight = DOM_DOM.create(
		"span",
		"notification-context-actor",
		notification.user.name,
	);

	const context = DOM_DOM.create("a", "notification-context", [
		highlight,
		`\u00A0${notification.context.trim()}`,
	]);

	if (notification.thread) {
		const thread = DOM_DOM.create(
			"span",
			"notification-context-actor",
			`\u00A0${notification.thread.title}`,
		);
		context.append(thread);
	}

	context.setAttribute("href", getNotificationUrl(notification));
	context.addEventListener("click", (event) => {
		event.stopPropagation();
		markAsRead(notification);
	});

	return context;
};

const getNotificationUrl = (notification) => {
	switch (notification.type) {
		case "THREAD_COMMENT_LIKE":
		case "THREAD_COMMENT_MENTION":
		case "THREAD_SUBSCRIBED":
		case "THREAD_COMMENT_REPLY":
			return `https://anilist.co/forum/thread/${notification.thread.id}/comment/${notification.commentId}`;
		case "THREAD_LIKE":
			return `https://anilist.co/forum/thread/${notification.threadId}/`;
		case "FOLLOWING":
			return `https://anilist.co/user/${notification.user.name}/`;
		default:
			return `https://anilist.co/activity/${notification.activityId}`;
	}
};

const createMediaContext = (notification) => {
	const highlight = DOM_DOM.create(
		"span",
		"notification-context-actor",
		notification.media?.title?.userPreferred ??
			notification.deletedMediaTitle,
	);
	let context;
	if (notification.type === "AIRING") {
		context = DOM_DOM.create("a", "notification-context", [
			notification.contexts[0],
			notification.episode,
			notification.contexts[1],
			highlight,
			notification.contexts[2],
		]);
	} else {
		context = DOM_DOM.create("a", "notification-context", [
			highlight,
			`\u00A0${notification.context.trim()}`,
		]);
	}
	if (!notification.deletedMediaTitle) {
		context.setAttribute(
			"href",
			`https://anilist.co/${notification.media.type.toLowerCase()}/${
				notification.media.id
			}`,
		);
	}

	if (notification.reason) {
		const reason = DOM_DOM.create(
			"div",
			"notification-context-reason",
			notification.reason,
		);
		context.append(reason);
	}
	context.addEventListener("click", (event) => {
		event.stopPropagation();
		markAsRead(notification);
	});
	return context;
};

const timeAgo = (timestamp) => {
	const now = new Date();
	const seconds = Math.floor((now.getTime() - timestamp * 1000) / 1000);

	let interval = Math.floor(seconds / 31536000);
	if (interval > 1) {
		return interval + " years ago";
	} else if (interval === 1) {
		return "1 year ago";
	}

	interval = Math.floor(seconds / 2592000);
	if (interval > 1) {
		return interval + " months ago";
	} else if (interval === 1) {
		return "1 month ago";
	}

	interval = Math.floor(seconds / 86400);
	if (interval > 1) {
		return interval + " days ago";
	} else if (interval === 1) {
		return "1 day ago";
	}

	interval = Math.floor(seconds / 3600);
	if (interval > 1) {
		return interval + " hours ago";
	} else if (interval === 1) {
		return "1 hour ago";
	}

	interval = Math.floor(seconds / 60);
	if (interval > 1) {
		return interval + " minutes ago";
	} else if (interval === 1) {
		return "1 minute ago";
	}

	return Math.floor(seconds) + " seconds ago";
};

;// CONCATENATED MODULE: ./src/handlers/notifications/notificationTypes.js
const notificationTypes = [
	"AIRING",
	"ACTIVITY_LIKE",
	"ACTIVITY_MENTION",
	"ACTIVITY_MESSAGE",
	"ACTIVITY_REPLY",
	"ACTIVITY_REPLY_LIKE",
	"ACTIVITY_REPLY_SUBSCRIBED",
	"FOLLOWING",
	"RELATED_MEDIA_ADDITION",
	"THREAD_COMMENT_LIKE",
	"THREAD_COMMENT_MENTION",
	"THREAD_COMMENT_REPLY",
	"THREAD_LIKE",
	"THREAD_SUBSCRIBED",
	"MEDIA_DATA_CHANGE",
	"MEDIA_DELETION",
	// not implemented
	// "MEDIA_MERGE",
];

;// CONCATENATED MODULE: ./src/handlers/notifications/notificationConfig.js


class NotificationConfig {
	groupNotifications;
	notificationTypes;
	collapsed;
	resetDefaultNotifications;
	addActivityRelation;
	#configInLocalStorage;
	constructor(locaStorageString) {
		this.#configInLocalStorage = locaStorageString;
		const config = JSON.parse(
			localStorage.getItem(this.#configInLocalStorage),
		);
		this.groupNotifications = config?.groupNotifications ?? true;
		this.notificationTypes = config?.notificationTypes ?? notificationTypes;
		this.collapsed = config?.collapsed ?? false;
		this.resetDefaultNotifications =
			config?.resetDefaultNotifications ?? true;
		this.addActivityRelation = config?.addActivityRelation ?? false;
	}

	save() {
		localStorage.setItem(this.#configInLocalStorage, JSON.stringify(this));
	}
}

;// CONCATENATED MODULE: ./src/handlers/notifications/notificationsCache.js
class NotificationsCache {
	static #notificationRelationsInSessionStorage =
		"void-verified-notification-relations";
	static #deadLinkRelations = "void-verified-notification-deadlink-relations";
	static cacheRelations(relations) {
		const relationsMap = this.#getRelations();
		for (const relation of relations) {
			relationsMap.set(relation.id, relation);
		}
		this.#setRelations(relationsMap);
	}

	static filterDeadLinks(activityIds) {
		const deadLinks =
			JSON.parse(sessionStorage.getItem(this.#deadLinkRelations)) ?? [];
		return activityIds.filter((id) => !deadLinks.includes(id));
	}

	static cacheDeadLinks(activityIds) {
		const deadLinks = new Set(
			JSON.parse(sessionStorage.getItem(this.#deadLinkRelations)),
		);
		for (const id of activityIds) {
			deadLinks.add(id);
		}
		sessionStorage.setItem(
			this.#deadLinkRelations,
			JSON.stringify(Array.from(deadLinks)),
		);
	}

	static getCachedRelations(activityIds) {
		const relations = this.#getRelations();
		const cachedIds = Array.from(relations.keys());
		const nonCachedIds = activityIds.filter(
			(id) => !cachedIds.includes(id),
		);
		return [
			Array.from(relations).map((mapEntry) => mapEntry[1]),
			nonCachedIds,
		];
	}

	static #getRelations() {
		const relations = new Map(
			JSON.parse(
				sessionStorage.getItem(
					this.#notificationRelationsInSessionStorage,
				),
			),
		);
		return relations;
	}

	static #setRelations(relations) {
		sessionStorage.setItem(
			this.#notificationRelationsInSessionStorage,
			JSON.stringify(Array.from(relations)),
		);
	}
}

;// CONCATENATED MODULE: ./src/handlers/notifications/notificationQuickAccessHandler.js











class NotificationQuickAccessHandler {
	#settings;
	#shouldQuery = true;
	#shouldRender = true;
	#notifications;
	#timeout = null;
	#config;
	#configOpen = false;
	#shouldQueryAfterConfigClose = false;
	constructor(settings) {
		this.#settings = settings;
		this.#config = new NotificationConfig(
			"void-verified-quick-access-notifications-config",
		);
	}

	async renderNotifications() {
		if (
			!this.#settings.options.quickAccessNotificationsEnabled.getValue() ||
			!this.#settings.isAuthorized()
		) {
			return;
		}
		if (this.#shouldQuery) {
			await this.#queryNotifications();
		}

		if (!this.#shouldRender) {
			return;
		}

		if (this.#config.hideDefaultNotificationDot) {
			const dot = document.body.querySelector(".user .notification-dot");
			if (dot) {
				dot.style.display = "none";
			}
		}

		if (!this.#notifications) {
			return;
		}

		const quickAccessContainer = DOM_DOM.getOrCreate(
			"div",
			"#quick-access quick-access",
		);

		const container = DOM_DOM.create("div", "quick-access-notifications");

		if (!this.#configOpen) {
			const notifications = DOM_DOM.create("div", "notifications-list");
			for (const notification of this.#handleNotifications(
				this.#notifications,
			)) {
				notifications.append(NotificationWrapper(notification));
			}
			container.append(notifications);
		} else {
			const configurationContainer = this.#createConfigurationContainer();
			container.append(configurationContainer);
		}

		const containerWrapper = DOM_DOM.create(
			"div",
			"quick-access-notifications-wrapper",
		);

		const header = DOM_DOM.create("h2", null, ["Notifications"]);
		container.setAttribute("collapsed", this.#config.collapsed);
		header.addEventListener("click", () => {
			this.#config.collapsed = !this.#config.collapsed;
			this.#config.save();
			container.setAttribute("collapsed", this.#config.collapsed);
		});

		const headerWrapper = DOM_DOM.create("div", null, header);
		headerWrapper.classList.add("section-header");

		const clearButton = this.#clearButton();
		const configButton = this.#configOpenButton();
		headerWrapper.append(
			DOM_DOM.create("span", null, [clearButton, configButton]),
		);

		containerWrapper.append(headerWrapper, container);

		this.#insertIntoDOM(quickAccessContainer, containerWrapper);
	}

	resetShouldRender() {
		this.#shouldRender = true;
		this.#shouldQuery = true;
		clearTimeout(this.#timeout);
		this.#timeout = null;
	}

	async #queryNotifications() {
		this.#shouldQuery = false;
		this.#timeout = setTimeout(
			() => {
				this.#shouldQuery = true;
			},
			3 * 60 * 1000,
		);
		let notifications = [];
		const anilistAPI = new AnilistAPI(this.#settings);
		try {
			const [notifs] = await anilistAPI.getNotifications(
				this.#config.notificationTypes.length > 0
					? this.#config.notificationTypes
					: notificationTypes,
			);
			notifications = notifs;
			this.#shouldRender = true;
		} catch (error) {
			console.error(error);
			Toaster.error(
				"There was an error querying quick access notifications.",
			);
		}

		const activityIds = new Set(
			notifications
				.filter((x) => x.activityId)
				.filter((x) => x.type !== "ACTIVITY_MESSAGE")
				.map((x) => x.activityId),
		);

		if (activityIds.size > 0 && this.#config.addActivityRelation) {
			const [relations, missingIds] =
				NotificationsCache.getCachedRelations(Array.from(activityIds));
			const nonDeadIds = NotificationsCache.filterDeadLinks(missingIds);
			if (nonDeadIds.length > 0) {
				try {
					const rels =
						await anilistAPI.getActivityNotificationRelations(
							Array.from(nonDeadIds),
						);
					relations.push(...rels);
					NotificationsCache.cacheRelations(rels);
					const foundIds = rels.map((relation) => relation.id);
					NotificationsCache.cacheDeadLinks(
						missingIds.filter((id) => !foundIds.includes(id)),
					);
				} catch (error) {
					console.error(error);
					Toaster.error(
						"Failed to get activity notification relations.",
					);
				}
			}
			notifications = notifications.map((notification) => {
				notification.activity = relations.find(
					(relation) => notification.activityId === relation.id,
				);
				return notification;
			});
		}
		this.#notifications = notifications;
	}

	#handleNotifications(notifications) {
		if (!notifications) {
			return [];
		}
		if (!this.#config.groupNotifications || notifications.length === 0) {
			return notifications.map((notification) => {
				notification.group = undefined;
				return notification;
			});
		}

		let prevNotification = notifications[0];
		prevNotification.group = [];
		let notificationsCopy = [...notifications];
		const notificationsToRemove = [];
		for (let i = 1; i < notifications.length; i++) {
			const notification = notifications[i];
			notification.group = [];
			if (
				prevNotification.type === notification.type &&
				prevNotification.activityId === notification.activityId &&
				notification.user
			) {
				prevNotification.group.push(notification.user);
				notificationsToRemove.push(i);
			} else {
				prevNotification = { ...notification };
			}
		}
		return notificationsCopy.filter(
			(_, index) => !notificationsToRemove.includes(index),
		);
	}

	#clearButton = () => {
		const clearButton = IconButton(CheckBadgeIcon(), async () => {
			if (this.#settings.options.replaceNotifications.getValue()) {
				ReadNotifications.markAllAsRead();
				document.querySelector(".void-notification-dot")?.remove();
			}
			try {
				await new AnilistAPI(this.#settings).resetNotificationCount();
				document.body
					.querySelector(".user .notification-dot")
					?.remove();
			} catch (error) {
				Toaster.error(
					"There was an error resetting notification count.",
				);
				console.error(error);
			}
		});
		clearButton.setAttribute("title", "Mark all as read");
		return clearButton;
	};

	#configOpenButton = () => {
		const openConfigurationButton = IconButton(CogIcon(), () => {
			this.#configOpen = !this.#configOpen;
			this.#shouldRender = true;
			if (this.#shouldQueryAfterConfigClose) {
				this.#shouldQuery = true;
				this.#shouldQueryAfterConfigClose = false;
			}
			this.renderNotifications();
		});
		return openConfigurationButton;
	};

	#createConfigurationContainer() {
		const configWrapper = DOM_DOM.create("div", "notifications-config-wrapper");
		const header = DOM_DOM.create(
			"h2",
			"notification-settings-header",
			"Notification Settings",
		);

		const basicOptions = this.#createbasicOptions();
		const notificationTypes = this.#createNotificationTypeOptions();
		configWrapper.append(header, basicOptions, notificationTypes);

		return configWrapper;
	}

	#createbasicOptions = () => {
		const container = DOM_DOM.create("div");
		const groupOption = SettingLabel(
			"Group similar notifications.",
			Checkbox(this.#config.groupNotifications, () => {
				this.#config.groupNotifications =
					!this.#config.groupNotifications;
				this.#config.save();
			}),
		);
		const relationOption = SettingLabel(
			"Add relation to activity notifications.",
			Checkbox(this.#config.addActivityRelation, () => {
				this.#config.addActivityRelation =
					!this.#config.addActivityRelation;
				this.#config.save();
			}),
		);
		container.append(groupOption, relationOption);
		return container;
	};

	#createNotificationTypeOptions = () => {
		const container = DOM_DOM.create("div");
		const header = DOM_DOM.create(
			"h3",
			"notification-type-list-header",
			"Notification Types",
		);
		container.append(header);

		for (const type of notificationTypes) {
			const t = SettingLabel(
				type,
				Checkbox(this.#config.notificationTypes.includes(type), () => {
					if (this.#config.notificationTypes.includes(type)) {
						this.#config.notificationTypes =
							this.#config.notificationTypes.filter(
								(x) => x !== type,
							);
					} else {
						this.#config.notificationTypes.push(type);
					}
					this.#config.save();
					this.#shouldQueryAfterConfigClose = true;
				}),
			);
			container.append(t);
		}

		return container;
	};

	#insertIntoDOM(quickAccessContainer, container) {
		if (
			quickAccessContainer.querySelector(
				".void-quick-access-notifications",
			)
		) {
			const oldNotifications = DOM_DOM.get(
				"quick-access-notifications-wrapper",
			);
			quickAccessContainer.replaceChild(container, oldNotifications);
		} else {
			quickAccessContainer.append(container);
		}
		this.#shouldRender = false;

		if (DOM_DOM.get("#quick-access")) {
			return;
		}
		const section = document.querySelector(
			".container > .home > div:nth-child(2)",
		);
		section.insertBefore(quickAccessContainer, section.firstChild);
	}
}

;// CONCATENATED MODULE: ./src/components/loader.js
// MIT License
// Copyright (c) 2020 Vineeth.TR

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.



const Loader = () => {
	const loader = DOM_DOM.create("span", "loader");
	return loader;
};

;// CONCATENATED MODULE: ./src/handlers/notifications/notificationFeedHandler.js












class NotificationFeedHandler {
	#settings;
	#config;
	#filter = "custom";
	#pageInfo = {
		currentPage: 0,
		hasNextPage: false,
	};
	constructor(settings) {
		this.#settings = settings;
		this.#config = new NotificationConfig(
			"void-verified-notifications-config",
		);

		if (
			this.#settings.options.replaceNotifications.getValue() &&
			this.#settings.isAuthorized()
		) {
			new StyleHandler().createStyleLink(
				notificationReplacementStyles,
				"notifications",
			);
			this.#handleUnreadNotificationsCount(this);
			setInterval(
				() => {
					this.#handleUnreadNotificationsCount(this);
				},
				3 * 60 * 1000,
			);
		}
	}

	renderNotificationsFeed() {
		if (
			!this.#settings.options.replaceNotifications.getValue() ||
			!this.#settings.isAuthorized()
		) {
			return;
		}

		if (!window.location.pathname.startsWith("/notifications")) {
			this.#pageInfo = { currentPage: 0, hasNextPage: false };
			this.#filter = "custom";
			return;
		}

		if (document.querySelector("#void-notifications-feed-container")) {
			return;
		}

		const container = DOM_DOM.create("div", "#notifications-feed-container");
		container.classList.add("container");
		container.append(this.#createSideBar());
		document
			.querySelector(".notifications-feed.container")
			.append(container);

		const notifications = DOM_DOM.create("div", "notifications-feed-list");
		container.append(notifications);

		this.#createNotifications();
	}

	async #handleUnreadNotificationsCount(notificationFeedHandler) {
		if (
			!this.#settings.options.replaceNotifications.getValue() ||
			!this.#settings.isAuthorized()
		) {
			return;
		}

		try {
			let [notifications] = await new AnilistAPI(
				notificationFeedHandler.#settings,
			).getNotifications(
				notificationFeedHandler.#config.notificationTypes,
				1,
				this.#config.resetDefaultNotifications,
			);

			const unreadNotificationsCount =
				ReadNotifications.getUnreadNotificationsCount(notifications);
			document
				.querySelector(".nav .user .void-notification-dot")
				?.remove();
			if (unreadNotificationsCount === 0) {
				return;
			}
			const notificationDot = DOM_DOM.create(
				"a",
				"notification-dot",
				unreadNotificationsCount,
			);
			notificationDot.setAttribute(
				"href",
				"https://anilist.co/notifications",
			);

			document.querySelector(".nav .user")?.append(notificationDot);
		} catch (error) {
			console.error(error);
			Toaster.error("There was an error querying unread notifications");
		}
	}

	#createSideBar() {
		const container = DOM_DOM.create("div", "notifications-feed-sidebar");
		container.append(this.#createFilters());
		container.append(this.#createReadAllButton());
		container.append(this.#createConfigurationContainer());
		return container;
	}

	#createFilters() {
		const container = DOM_DOM.create("div", "notifications-feed-filters");
		container.append(
			...filters.map((filter) => {
				const filterButton = DOM_DOM.create(
					"div",
					"notifications-feed-filter",
					filter,
				);
				filterButton.setAttribute("void-filter", filter);
				if (filter === this.#filter) {
					filterButton.classList.add("void-active");
				}
				filterButton.addEventListener("click", () => {
					this.#filter = filter;
					ReadNotifications.resetUnreadNotificationsFeed();
					document
						.querySelector(
							".void-notifications-feed-filter.void-active",
						)
						?.classList.remove("void-active");
					document
						.querySelector(
							`.void-notifications-feed-filter[void-filter="${filter}"]`,
						)
						?.classList.add("void-active");
					this.#pageInfo = { currentPage: 0, hasNextPage: false };
					document
						.querySelector(".void-notifications-feed-list")
						.replaceChildren([]);
					this.#createNotifications();
				});
				return filterButton;
			}),
		);
		return container;
	}

	#createReadAllButton = () => {
		const button = Button(
			"Mark all as read",
			async () => {
				ReadNotifications.markAllAsRead();
				document
					.querySelectorAll(".void-unread-notification")
					.forEach((notification) => {
						notification.classList.remove(
							"void-unread-notification",
						);
					});
				document.querySelector(".void-notification-dot")?.remove();
				if (!this.#config.resetDefaultNotifications) {
					try {
						Toaster.debug("Resetting notification count.");
						await new AnilistAPI(
							this.#settings,
						).resetNotificationCount();
						document.body
							.querySelector(".user .notification-dot")
							?.remove();
						Toaster.success("Notifications count reset.");
					} catch (error) {
						Toaster.error(
							"There was an error resetting notification count.",
						);
						console.error(error);
					}
				}
			},
			"notification-all-read-button",
		);
		return button;
	};

	#createConfigurationContainer() {
		const container = DOM_DOM.create("div", "notifications-feed-settings");
		const header = DOM_DOM.create(
			"h2",
			"notification-settings-header",
			"Notification Settings",
		);

		const configWrapper = DOM_DOM.create("div", "notifications-config-wrapper");
		configWrapper.setAttribute("collapsed", "true");

		const basicOptions = this.#createbasicOptions();
		const notificationTypes = this.#createNotificationTypeOptions();
		configWrapper.append(basicOptions, notificationTypes);

		header.addEventListener("click", () => {
			configWrapper.setAttribute(
				"collapsed",
				!(configWrapper.getAttribute("collapsed") === "true"),
			);
		});

		container.append(header, configWrapper);

		return container;
	}

	#createbasicOptions = () => {
		const container = DOM_DOM.create("div");
		const groupOption = SettingLabel(
			"Group similar notifications.",
			Checkbox(this.#config.groupNotifications, () => {
				this.#config.groupNotifications =
					!this.#config.groupNotifications;
				this.#config.save();
			}),
		);
		const relationOption = SettingLabel(
			"Add relation to activity notifications.",
			Checkbox(this.#config.addActivityRelation, () => {
				this.#config.addActivityRelation =
					!this.#config.addActivityRelation;
				this.#config.save();
			}),
		);
		const resetOption = SettingLabel(
			"Reset AniList's notification count when querying notifications.",
			Checkbox(this.#config.resetDefaultNotifications, () => {
				this.#config.resetDefaultNotifications =
					!this.#config.resetDefaultNotifications;
				this.#config.save();
			}),
		);
		container.append(groupOption, relationOption, resetOption);
		return container;
	};

	#createNotificationTypeOptions = () => {
		const container = DOM_DOM.create("div");
		const header = DOM_DOM.create(
			"h3",
			"notification-type-list-header",
			"Notification Types",
		);
		container.append(header);

		for (const type of notificationTypes) {
			const t = SettingLabel(
				type,
				Checkbox(this.#config.notificationTypes.includes(type), () => {
					if (this.#config.notificationTypes.includes(type)) {
						this.#config.notificationTypes =
							this.#config.notificationTypes.filter(
								(x) => x !== type,
							);
					} else {
						this.#config.notificationTypes.push(type);
					}
					this.#config.save();
				}),
			);
			container.append(t);
		}

		return container;
	};

	async #createNotifications() {
		this.#showLoader();
		document
			.querySelector(".void-notifications-load-more-button")
			?.remove();
		const notificationElements = [];
		let notifications = [];

		const anilistAPI = new AnilistAPI(this.#settings);

		try {
			Toaster.debug("Querying notification feed.");
			const [notifs, pageInfo] = await anilistAPI.getNotifications(
				this.#getNotificationTypes(),
				this.#pageInfo.currentPage + 1,
				this.#config.resetDefaultNotifications,
			);
			notifications = notifs;
			this.#pageInfo = pageInfo;
		} catch (error) {
			console.error(error);
			Toaster.error("There was an error querying notification feed.");
		}

		const activityIds = new Set(
			notifications
				.filter((x) => x.activityId)
				.filter((x) => x.type !== "ACTIVITY_MESSAGE")
				.map((x) => x.activityId),
		);

		if (activityIds.size > 0 && this.#config.addActivityRelation) {
			const [relations, missingIds] =
				NotificationsCache.getCachedRelations(Array.from(activityIds));
			const nonDeadIds = NotificationsCache.filterDeadLinks(missingIds);
			if (nonDeadIds.length > 0) {
				try {
					const rels =
						await anilistAPI.getActivityNotificationRelations(
							Array.from(nonDeadIds),
						);
					relations.push(...rels);
					NotificationsCache.cacheRelations(rels);
					const foundIds = rels.map((relation) => relation.id);
					NotificationsCache.cacheDeadLinks(
						missingIds.filter((id) => !foundIds.includes(id)),
					);
				} catch (error) {
					console.error(error);
					Toaster.error(
						"Failed to get activity notification relations.",
					);
				}
			}
			notifications = notifications.map((notification) => {
				notification.activity = relations.find(
					(relation) => notification.activityId === relation.id,
				);
				return notification;
			});
		}

		for (const notification of this.#groupNotifications(notifications)) {
			const notificationElement = NotificationWrapper(notification, true);
			if (!ReadNotifications.isRead(notification.id)) {
				notificationElement.classList.add("void-unread-notification");
			}
			notificationElements.push(notificationElement);
		}

		if (notifications.length === 0) {
			notificationElements.push(
				DOM_DOM.create(
					"div",
					"notifications-feed-empty-notice",
					"Couldn't load notifications.",
				),
			);
		}

		document.querySelector(".void-loader")?.remove();

		document
			.querySelector(".void-notifications-feed-list")
			.append(...notificationElements);

		this.#createLoadMoreButton();
	}

	#createLoadMoreButton() {
		if (!this.#pageInfo.hasNextPage) {
			return;
		}
		const button = Button(
			"Load More",
			() => {
				this.#createNotifications(this.#pageInfo.currentPage + 1);
			},
			"notifications-load-more-button",
		);
		document.querySelector(".void-notifications-feed-list").append(button);
	}

	#showLoader() {
		document
			.querySelector(".void-notifications-feed-list")
			.append(Loader());
	}

	#getNotificationTypes() {
		switch (this.#filter) {
			case "custom":
				return this.#config.notificationTypes;
			case "all":
				return notificationTypes;
			case "airing":
				return ["AIRING"];
			case "activity":
				return [
					"ACTIVITY_MENTION",
					"ACTIVITY_MESSAGE",
					"ACTIVITY_REPLY",
					"ACTIVITY_REPLY_SUBSCRIBED",
				];
			case "likes":
				return ["ACTIVITY_LIKE", "ACTIVITY_REPLY_LIKE"];
			case "forum":
				return [
					"THREAD_COMMENT_LIKE",
					"THREAD_COMMENT_MENTION",
					"THREAD_COMMENT_REPLY",
					"THREAD_LIKE",
					"THREAD_SUBSCRIBED",
				];
			case "follows":
				return ["FOLLOWING"];
			case "media":
				return [
					"RELATED_MEDIA_ADDITION",
					"MEDIA_DATA_CHANGE",
					"MEDIA_DELETION",
				];
		}
	}

	#groupNotifications(notifications) {
		if (!notifications) {
			return [];
		}

		if (
			!this.#config.groupNotifications ||
			!notifications ||
			notifications.length === 0
		) {
			return notifications.map((notification) => {
				notification.group = undefined;
				return notification;
			});
		}

		let prevNotification = notifications[0];
		prevNotification.group = [];
		let notificationsCopy = [...notifications];
		const notificationsToRemove = [];
		for (let i = 1; i < notifications.length; i++) {
			const notification = notifications[i];
			notification.group = [];
			if (
				prevNotification.type === notification.type &&
				prevNotification.activityId === notification.activityId &&
				notification.user
			) {
				const groupItem = {
					...notification.user,
					notificationId: notification.id,
				};
				prevNotification.group.push(groupItem);
				notificationsToRemove.push(i);
			} else {
				prevNotification = { ...notification };
			}
		}
		return notificationsCopy.filter(
			(_, index) => !notificationsToRemove.includes(index),
		);
	}
}

const filters = [
	"custom",
	"all",
	"airing",
	"activity",
	"forum",
	"follows",
	"likes",
	"media",
];

const notificationReplacementStyles = `
    .nav .user .notification-dot {
        display: none;
    }

    .notifications-feed.container .filters,
    .notifications-feed.container .notifications {
        display: none;
    }
`;

;// CONCATENATED MODULE: ./src/handlers/activityPostHandler.ts





var MediaStatus;
(function (MediaStatus) {
    MediaStatus["Current"] = "CURRENT";
    MediaStatus["Planning"] = "PLANNING";
    MediaStatus["Completed"] = "COMPLETED";
    MediaStatus["Dropped"] = "DROPPED";
    MediaStatus["Paused"] = "PAUSED";
    MediaStatus["Repeating"] = "REPEATING";
})(MediaStatus || (MediaStatus = {}));
class ActivityPostHandler {
    settings;
    #timeout;
    selectedSearchResult;
    mediaActivity;
    constructor(settings) {
        this.settings = settings;
    }
    render() {
        if (!this.settings.options.replyActivityUpdate.getValue() || !this.settings.isAuthorized()) {
            return;
        }
        const activityEditContainer = document.querySelector(".home > .activity-feed-wrap > .activity-edit");
        if (!activityEditContainer) {
            return;
        }
        if (DOM_DOM.get("activity-reply-controls-container")) {
            return;
        }
        const markdownInput = activityEditContainer.querySelector(":scope > .input");
        const markdownEditor = activityEditContainer.querySelector(".markdown-editor");
        if (!markdownEditor) {
            return;
        }
        if (!markdownEditor.querySelector(".void-activity-reply-toggle-button")) {
            markdownEditor.append(this.#createToggleButton());
        }
        if (!markdownInput) {
            return;
        }
        activityEditContainer.insertBefore(this.#createControls(), markdownInput);
    }
    #createControls() {
        const container = DOM_DOM.create("div", "activity-reply-controls-container");
        container.append(this.#createHeader());
        container.append(DOM_DOM.create("div", "media-status-controls", DOM_DOM.create("div", "gif-keyboard-list-placeholder", "Select a media...")));
        container.setAttribute("closed", true);
        return container;
    }
    #createToggleButton() {
        const button = IconButton(FilmIcon(), () => {
            const container = document.querySelector(".void-activity-reply-controls-container");
            const currentValue = container.getAttribute("closed");
            container.setAttribute("closed", currentValue === "true" ? "false" : "true");
        }, "gif-button");
        button.setAttribute("title", "Reply to Activity");
        return button;
    }
    #createStatusAndProgressControl = () => {
        const container = document.querySelector(".void-media-status-controls");
        container.replaceChildren();
        container.append(Image(this.selectedSearchResult.coverImage.large, "status-poster"));
        const progressContainer = DOM_DOM.create("div", "activity-reply-progress-container");
        const mediaTitleContainer = DOM_DOM.create("div");
        mediaTitleContainer.append(DOM_DOM.create("div", "media-search-title", Link(this.selectedSearchResult.title.userPreferred, `https://anilist.co/${this.selectedSearchResult.type === "ANIME" ? "anime" : "manga"}/${this.selectedSearchResult.id}`, "_blank")));
        mediaTitleContainer.append(DOM_DOM.create("div", "media-search-type", `${this.selectedSearchResult.startDate.year ?? "Unreleased"} ${this.selectedSearchResult.type}`));
        progressContainer.append(mediaTitleContainer);
        progressContainer.append(DOM_DOM.create("h5", "layout-header", "Status"));
        const options = Object.keys(MediaStatus)
            .filter(status => !(MediaStatus[status] === MediaStatus.Repeating && this.mediaActivity.maxProgress === 1))
            .map((status) => Option(status, MediaStatus[status] === this.mediaActivity.status, () => {
            this.mediaActivity.status = MediaStatus[status];
            this.#createStatusAndProgressControl();
        }));
        const select = Select(options);
        progressContainer.append(select);
        container.append(progressContainer);
        if (this.mediaActivity.status !== MediaStatus.Current && this.mediaActivity.status !== MediaStatus.Repeating) {
            return;
        }
        progressContainer.append(DOM_DOM.create("h5", "layout-header", "Progress"));
        const progressInput = InputField(this.mediaActivity.progress, (event) => {
            this.mediaActivity.progress = Number(event.target.value);
        });
        progressInput.setAttribute("type", "number");
        progressInput.setAttribute("max", this.mediaActivity.maxProgress);
        progressInput.setAttribute("min", 0);
        progressContainer.append(DOM_DOM.create("div", null, progressInput));
    };
    #createHeader() {
        const container = DOM_DOM.create("div", "activity-reply-search-container");
        const searchInput = DOM_DOM.create("input", "input");
        searchInput.setAttribute("placeholder", "Search media...");
        searchInput.addEventListener("keyup", function (event) {
            this.#handleSearchInput(event.target.value);
        }.bind(this));
        searchInput.addEventListener("focusout", () => {
            // set timeout so clicking on a result works
            setTimeout(() => {
                DOM_DOM.get("#media-search-list")?.remove();
            }, 150);
        });
        container.append(searchInput);
        const headerContainer = DOM_DOM.create("div", "activity-reply-header", container);
        const replyButton = Button("Reply", function () {
            this.#handleReply();
        }.bind(this), "slim");
        headerContainer.append(replyButton);
        return headerContainer;
    }
    async #handleReply() {
        if (!this.selectedSearchResult) {
            Toaster.notify("Please search for a media before replying.");
            return;
        }
        if (this.#validateNotificationOptions()) {
            Toaster.notify(`You have disabled ${this.mediaActivity.status} list activity type. Enable it in settings to create this activity.`);
            return;
        }
        const anilistAPI = new AnilistAPI(this.settings);
        try {
            await anilistAPI.updateMediaProgress(this.mediaActivity.mediaListId, this.selectedSearchResult.id, this.mediaActivity.status, this.mediaActivity.progress);
        }
        catch (error) {
            Toaster.error("Failed to update media progress");
            console.error(error);
            return;
        }
        try {
            const response = await anilistAPI.getCreatedMediaActivity(this.selectedSearchResult.id);
            const textarea = document.querySelector(".home > .activity-feed-wrap > .activity-edit > .input > textarea");
            const replyResponse = await anilistAPI.replyToActivity(response.id, textarea.value);
            window.location.replace(`https://anilist.co/activity/${response.id}`);
        }
        catch (error) {
            Toaster.error("Failed to reply to activity");
        }
    }
    #validateNotificationOptions() {
        const disabledListActivity = JSON.parse(localStorage.getItem("auth"))?.options?.disabledListActivity;
        return disabledListActivity.some(disabledListActivity => disabledListActivity.type === this.mediaActivity.status && disabledListActivity.disabled);
    }
    #handleSearchInput(value) {
        clearTimeout(this.#timeout);
        if (value === "" || value.length < 3) {
            return;
        }
        this.#timeout = setTimeout(async () => {
            const anilistAPI = new AnilistAPI(this.settings);
            try {
                Toaster.debug(`Querying media with search word ${value}`);
                const response = await anilistAPI.searchMedia(value);
                this.renderSearchResults(response);
                console.log(response);
            }
            catch (error) {
                console.error(error);
                Toaster.error(`Failed to query media with search word ${value}`);
            }
        }, 800);
    }
    renderSearchResults(results) {
        const container = DOM_DOM.getOrCreate("div", "#media-search-list");
        container.replaceChildren([]);
        for (const result of results) {
            const resultContainer = DOM_DOM.create("div", "media-search-result");
            resultContainer.addEventListener("click", function () {
                this.setSelectedSearchResult(result);
            }.bind(this));
            resultContainer.append(DOM_DOM.create("div", null, Image(result.coverImage.large, "media-search-poster")));
            const infoContainer = DOM_DOM.create("div", "media-search-info");
            infoContainer.append(DOM_DOM.create("div", "media-search-title", result.title.userPreferred));
            infoContainer.append(DOM_DOM.create("div", "media-search-type", `${result.type} ${result.startDate.year}`));
            resultContainer.append(infoContainer);
            container.append(resultContainer);
        }
        DOM_DOM.get("activity-reply-search-container").append(container);
    }
    async setSelectedSearchResult(result) {
        this.selectedSearchResult = result;
        DOM_DOM.get("#media-search-list")?.remove();
        console.log(this.selectedSearchResult);
        const anilistAPI = new AnilistAPI(this.settings);
        try {
            const mediaProgress = await anilistAPI.getMediaProgress(result.id);
            if (mediaProgress) {
                this.mediaActivity = {
                    status: mediaProgress.status,
                    progress: mediaProgress.progress,
                    maxProgress: mediaProgress.media.episodes ?? mediaProgress.media.chapters,
                    mediaListId: mediaProgress.id
                };
            }
            else {
                this.mediaActivity = {
                    status: MediaStatus.Planning,
                    progress: 0,
                    maxProgress: this.selectedSearchResult.episodes ?? this.selectedSearchResult.chapters,
                    mediaListId: undefined
                };
            }
            this.#createStatusAndProgressControl();
        }
        catch (error) {
            Toaster.error("Failed to query media progress");
            console.error(error);
        }
    }
}

;// CONCATENATED MODULE: ./src/handlers/intervalScriptHandler.ts














class IntervalScriptHandler {
    styleHandler;
    settingsUi;
    activityHandler;
    settings;
    globalCSS;
    quickAccess;
    userCSS;
    layoutDesigner;
    gifKeyboard;
    anilistFeedFixHandler;
    notificationQuickAccessHandler;
    notificationFeedHandler;
    activityPostHandler;
    constructor(settings) {
        this.settings = settings;
        this.styleHandler = new StyleHandler(settings);
        this.globalCSS = new GlobalCSS(settings);
        this.userCSS = new UserCSS(settings);
        this.layoutDesigner = new LayoutDesigner(settings);
        this.gifKeyboard = new GifKeyboardHandler(settings);
        this.settingsUi = new SettingsUserInterface(settings, this.styleHandler, this.globalCSS, this.userCSS, this.layoutDesigner);
        this.activityHandler = new ActivityHandler(settings);
        this.quickAccess = new QuickAccess(settings);
        this.anilistFeedFixHandler = new AnilistFeedFixHandler(settings);
        this.notificationQuickAccessHandler =
            new NotificationQuickAccessHandler(settings);
        this.notificationFeedHandler = new NotificationFeedHandler(settings);
        this.activityPostHandler = new ActivityPostHandler(settings);
    }
    currentPath = "";
    evaluationIntervalInSeconds = 1;
    hasPathChanged(path) {
        if (path === this.currentPath) {
            return false;
        }
        this.currentPath = path;
        return true;
    }
    handleIntervalScripts(intervalScriptHandler) {
        const path = window.location.pathname;
        intervalScriptHandler.activityHandler.moveAndDisplaySubscribeButton();
        intervalScriptHandler.activityHandler.addSelfMessageButton();
        intervalScriptHandler.activityHandler.removeBlankFromAnilistLinks();
        intervalScriptHandler.gifKeyboard.handleGifKeyboard();
        intervalScriptHandler.globalCSS.clearCssForProfile();
        intervalScriptHandler.layoutDesigner.renderLayoutPreview();
        intervalScriptHandler.anilistFeedFixHandler.handleFix();
        intervalScriptHandler.notificationFeedHandler.renderNotificationsFeed();
        // intervalScriptHandler.voidRouter.handleRouting();
        if (path === "/home") {
            intervalScriptHandler.styleHandler.refreshHomePage();
            intervalScriptHandler.quickAccess.renderQuickAccess();
            intervalScriptHandler.notificationQuickAccessHandler.renderNotifications();
            intervalScriptHandler.activityPostHandler.render();
        }
        else {
            intervalScriptHandler.notificationQuickAccessHandler.resetShouldRender();
        }
        if (!path.startsWith("/settings/developer")) {
            intervalScriptHandler.settingsUi.removeSettingsUi();
        }
        if (!intervalScriptHandler.hasPathChanged(path)) {
            return;
        }
        if (path.startsWith("/user/")) {
            intervalScriptHandler.userCSS.checkUserCss();
            intervalScriptHandler.quickAccess.clearBadge();
            intervalScriptHandler.styleHandler.verifyProfile();
            intervalScriptHandler.anilistFeedFixHandler.handleFilters();
        }
        else {
            intervalScriptHandler.styleHandler.clearStyles("profile");
        }
        if (path.startsWith("/activity/")) {
            intervalScriptHandler.userCSS.checkActivityCss();
        }
        if (!path.startsWith("/activity/") && !path.startsWith("/user/")) {
            intervalScriptHandler.userCSS.resetCurrentActivity();
            intervalScriptHandler.userCSS.resetCurrentUser();
            intervalScriptHandler.styleHandler.clearStyles("user-css");
        }
        intervalScriptHandler.globalCSS.createCss();
        if (path.startsWith("/settings/developer")) {
            intervalScriptHandler.settingsUi.renderSettingsUi();
        }
    }
    enableScriptIntervalHandling() {
        const interval = setInterval(() => {
            try {
                this.handleIntervalScripts(this);
            }
            catch (error) {
                Toaster.critical([
                    "A critical error has occured running interval script loop. VoidVerified is not working correctly. Please check developer console and contact ",
                    Link("voidnyan", "https://anilist.co/user/voidnyan/", "_blank"),
                    ".",
                ]);
                clearInterval(interval);
                console.error(error);
            }
        }, this.evaluationIntervalInSeconds * 1000);
    }
}

;// CONCATENATED MODULE: ./src/handlers/pasteHandler.js



class PasteHandler {
	settings;
	#uploadInProgress = false;
	constructor(settings) {
		this.settings = settings;
	}

	setup() {
		window.addEventListener("paste", (event) => {
			this.#handlePaste(event);
		});
	}

	async #handlePaste(event) {
		if (
			event.target.tagName !== "TEXTAREA" &&
			event.target.tagName !== "INPUT"
		) {
			return;
		}

		const clipboard = event.clipboardData.getData("text/plain").trim();

		const file = event.clipboardData.items[0]?.getAsFile();
		if (file && this.settings.options.pasteImagesToHostService.getValue()) {
			event.preventDefault();
			const result = await this.#handleImages(event);
			const transformedClipboard = result.join("\n\n");
			window.document.execCommand(
				"insertText",
				false,
				transformedClipboard,
			);
		} else if (this.settings.options.pasteEnabled.getValue()) {
			event.preventDefault();
			const regex = new RegExp(
				`(?<!\\()\\b(https?:\/\/\\S+\\.(?:${ImageFormats.join(
					"|",
				)}))\\b(?!.*?\\))`,
				"gi",
			);
			const result = clipboard.replace(
				regex,
				(match) =>
					`img${this.settings.options.pasteImageWidth.getValue()}(${match})`,
			);
			window.document.execCommand("insertText", false, result);
			return;
		}
	}

	async #handleImages(event) {
		const _files = event.clipboardData.items;
		if (this.#uploadInProgress) {
			return;
		}
		this.#uploadInProgress = true;
		document.body.classList.add("void-upload-in-progress");

		const imageApi = new ImageApiFactory().getImageHostInstance();

		const files = Object.values(_files).map((file) => file.getAsFile());
		const images = files.filter((file) => file.type.startsWith("image/"));

		try {
			const results = await Promise.all(
				images.map((image) => imageApi.uploadImage(image)),
			);
			return results
				.filter((url) => url !== null)
				.map((url) => this.#handleRow(url, event));
		} catch (error) {
			console.error(error);
			return [];
		} finally {
			this.#uploadInProgress = false;
			document.body.classList.remove("void-upload-in-progress");
		}
	}

	#handleRow(row, event) {
		if (
			event.target.parentElement.classList.contains("void-css-editor") ||
			event.target.tagName === "INPUT"
		) {
			return row;
		}

		row = row.trim();
		if (ImageFormats.some((format) => row.toLowerCase().endsWith(format))) {
			return this.#handleImg(row);
		} else if (row.toLowerCase().startsWith("http")) {
			return `[](${row})`;
		} else {
			return row;
		}
	}

	#handleImg(row) {
		const img = `img${this.settings.options.pasteImageWidth.getValue()}(${row})`;
		let result = img;
		if (this.settings.options.pasteWrapImagesWithLink.getValue()) {
			result = `[ ${img} ](${row})`;
		}
		return result;
	}
}

;// CONCATENATED MODULE: ./src/assets/styles.js
const styles = /* css */ `
    :root {
        --void-info: 46, 149, 179;
        --void-error: 188, 53, 46;
        --void-success: 80, 162, 80;
        --void-warning: 232, 180, 2;
    }


    a[href="/settings/developer" i]::after{content: " & Void"}
    .void-settings .void-nav ol {
        display: flex;
        margin: 8px 0px;
        padding: 0;
    }

    .void-nav {
        margin-top: 3rem;
    }

    .void-settings .void-nav li {
        list-style: none;
        display: block;
        color: rgb(var(--color-text));
        padding: 4px 8px;
        text-transform: capitalize;
        background: rgb(var(--color-foreground-blue));
        cursor: pointer;
        min-width: 50px;
        text-align: center;
        font-size: 1.4rem;
    }

    .void-settings .void-nav li.void-active,
    .void-settings .void-nav li:hover {
        background: rgb(var(--color-blue));
        color: rgb(var(--color-text-bright));
    }

    .void-settings .void-nav li:first-child {
        border-radius: 4px 0px 0px 4px;
    }

    .void-settings .void-nav li:last-child {
        border-radius: 0px 4px 4px 0px;
    }

    .void-settings .void-settings-header {
        margin-top: 30px;
    }

    .void-settings .void-table table {
        border-collapse: collapse;
    }

    .void-settings .void-table :is(th, td) {
        padding: 2px 6px !important;
    }

    .void-settings .void-table :is(th, td):first-child {
        border-radius: 4px 0px 0px 4px;
    }

    .void-settings .void-table :is(th, td):last-child {
        border-radius: 0px 4px 4px 0px;
    }

    .void-settings .void-table tbody tr:hover {
        background-color: rgba(var(--color-foreground-blue), .7);
    }

    .void-settings .void-table input[type="color"] {
        border: 0;
        height: 24px;
        width: 40px;
        padding: 0;
        background-color: unset;
        cursor: pointer;
    }

    .void-settings .void-table button {
        background: unset;
        border: none;
        cursor: pointer;
        padding: 0;
    }

    .void-settings .void-table form {
        padding: 8px;
        display: flex;
        align-items: center;
        gap: 8px;
    }

    .void-settings .void-settings-header span {
        color: rgb(var(--color-blue));
    }

    .void-settings .void-settings-list {
        display: flex;
        flex-direction: column;
        gap: 5px;
    }

    .void-setting-label {
        padding-left: 6px;
        vertical-align: middle;
        cursor: pointer;
    }

    .void-setting-label-container.void-auth-required .void-setting-label {
        color: rgb(var(--color-peach));
    }

    .void-setting-label-container .void-checkbox {
        vertical-align: middle;
    }

    .void-checkbox {
        cursor: pointer;
    }


    .void-settings .void-settings-list input.void-input {
        width: 50px;
        text-align: center;
        height: 20px;
        font-size: 12px;
    }

    .void-settings .void-settings-list label {
        padding-left: 8px;
    }

    .void-settings .void-css-editor label {
        margin-top: 20px;
        fontSize: 2rem;
        display: inline-block;
    }

    .void-textarea {
        width: 100%;
        height: 300px;
        min-height: 200px;
        resize: vertical;
        background: rgb(var(--color-foreground-blue));
        color: rgb(var(--color-text));
        padding: 4px;
        border-radius: 4px;
        border: 2px solid transparent;
        outline: none !important;
    }

    .void-textarea:focus {
        border: 2px solid rgb(var(--color-blue)) !important;
    }

    .void-layout-image-container {
        padding: 4px;
        display: inline-block;
    }

    .void-layout-image-container:first-child {
        width: 35%;
    }

    .void-layout-image-container:last-child {
        width: 65%;
    }

    .void-layout-header {
        text-transform: uppercase;
        margin-top: 2.2em;
        margin-bottom: .8em;
    }

    .void-layout-image-display {
        height: 140px;
        background-repeat: no-repeat;
        margin: auto;
        margin-bottom: 6px;
        border-radius: 4px;
    }

    .void-layout-image-display.void-banner {
        width: 100%;
        background-size: cover;
        background-position: 50% 50%;
    }

    .void-layout-image-display.void-avatar {
        background-size: contain;
        width: 140px;
    }

    .void-layout-image-container input {
        width: 100%;
    }

    .void-layout-color-selection {
        margin-top: 10px;
        margin-bottom: 10px;
    }

    .void-layout-color-selection .void-color-button {
        width: 50px;
        height: 50px;
        display: inline-flex;
        border-radius: 4px;
        margin-right: 10px;
    }

    .void-layout-color-selection .void-color-button.active {
        border: 4px solid rgb(var(--color-text));
    }

    .void-layout-color-selection .void-color-picker-container.active {
        border: 2px solid rgb(var(--color-text));
    }

    .void-color-picker-container {
        display: inline-block;
        vertical-align: top;
        width: 75px;
        height: 50px;
        border: 2px solid transparent;
        border-radius: 4px;
        box-sizing: border-box;
    }

    .void-color-picker-container:has(:focus) {
        border: 2px solid rgb(var(--color-text));
    }

    .void-color-picker-input {
        width: 100%;
        height: 20px;
        background-color: rgba(var(--color-background), .6);
        padding: 1px;
        font-size: 11px;
        color: rgb(var(--color-text));
        outline: none;
        appearance: none;
        -webkit-appearance: none;
        text-align: center;
        border: unset;
        border-radius: 0px 0px 4px 4px;
    }

    .void-color-picker {
        /* width: 100%;;
        height: 50px; */
        block-size: 30px;
        border-width: 0px;
        padding: 0px;
        background-color: unset;
        inline-size: 100%;
        border-radius: 4px;
        appearance: none;
        vertical-align: top;
        padding-block: 0px;
        padding-inline: 0px;
        outline: none;
    }

    .void-color-picker::-webkit-color-swatch,
    .void-color-picker::-moz-color-swatch {
        border: none;
        border-radius: 4px;
    }

    .void-color-picker::-webkit-color-swatch-wrapper,
    .void-color-picker::-webkit-color-swatch-wrapper {
        padding: 0px;
        border-radius: 4px;
    }

    .void-input {
        background-color: rgba(var(--color-background), .6);
        padding: 4px 6px;
        color: rgb(var(--color-text));
        outline: none;
        appearance: none;
        -webkit-appearance: none;
        border: 2px solid transparent;
        border-radius: 4px;
        box-sizing: border-box;
    }

    .void-action-container {
        display: inline-block;
        width: fit-content;
    }

    .void-action-container .void-icon-button {
        padding: 6px 8px;
        margin: 0px;
        background: rgb(var(--color-foreground-blue-dark));
        color: rgb(var(--color-text));
        border-radius: 0px 4px 4px 0px;
        height: 100%;
        display: inline-flex;
        flex-direction: column;
        justify-content: center;
        border: 2px solid transparent;
        border-left: 0px !important;
        line-height: 1.15;
        box-sizing: border-box;
        vertical-align: top;
    }

    .void-action-container .void-icon-button:hover {
        color: rgb(var(--color-blue));
    }

    .void-action-container .void-icon-button svg {
        height: 14px;
    }

    .void-action-container .void-input {
        border-radius: 4px 0px 0px 4px;
    }

    a.void-link {
        color: rgb(var(--color-blue)) !important;
    }

    .void-input.void-sign {
        width: 75px;
        text-align: center;
        height: 20px;
        font-size: 14px;
    }

    .void-input:focus {
        border: 2px solid rgb(var(--color-blue));
    }

    .void-button {
        align-items: center;
        background: rgb(var(--color-blue));
        border-radius: 4px;
        color: rgb(var(--color-text-bright));
        cursor: pointer;
        display: inline-flex;
        font-size: 1.3rem;
        padding: 10px 15px;
        outline: none;
        appearance: none;
        -webkit-appearance: none;
        border: 0px solid rgb(var(--color-background));
        vertical-align: top;
        margin-top: 15px;
        margin-right: 10px;
    }

    .void-icon-button {
        display: inline-block;
        cursor: pointer;
        margin-left: 4px;
        margin-right: 4px;
        vertical-align: middle;
    }

    .void-icon-button svg {
        height: 12px;
        vertical-align: middle;
        display: inline-block;
        pointer-events: none;
    }

    .void-range-container {
        display: inline-flex;
    }

    .void-range-display {
        margin-left: 5px;
        user-select: none;
        font-size: 14px;
        font-weight: bold;
        display: inline-flex;
        justify-content: center;
        align-items: center;
        min-width: 25px;
    }

    .void-gif-button svg {
        height: 18px;
        vertical-align: top;
        color: rgb(var(--color-red));
    }

    .void-gif-button:hover svg {
        color: rgb(var(--color-blue));
    }

    .void-gif-button {
        margin: 0px;
    }

    .markdown-editor[style*="display: none;"] + .void-gif-keyboard-container,
    .home > .activity-feed-wrap > .activity-edit:has(.markdown-editor[style*="display: none;"]) .void-activity-reply-controls-container
     {
        display: none;
    }

    .void-gif-keyboard-container {
        width: 100%;
        background: rgb(var(--color-foreground));
        margin-bottom: 12px;
        border-radius: 4px;
    }

    .void-gif-keyboard-header {
        background: rgb(var(--color-foreground-grey-dark));
        padding: 12px 20px;
        border-radius: 4px 4px 0px 0px;
        display: flex;
        justify-content: space-between;
    }

    .void-gif-keyboard-control-container {
        display: flex;
        justify-content: space-between;
        padding: 12px 12px 0px 12px;
    }

    .void-gif-keyboard-list-container {
        height: 300px;
        min-height: 200px;
        max-height: 500px;
        overflow-y: scroll;
        resize: vertical;
        user-select: none;
    }

    .void-gif-keyboard-list {
        padding: 12px 20px;
        width: 100%;
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        gap: 8px;
    }

    .void-gif-keyboard-list-column {
        width: calc(100% / 3);
        display: flex;
        flex-direction: column;
        gap: 8px;
    }

    .void-gif-keyboard-list-placeholder {
        font-size: 20px;
        color: rgb(var(--color-text));
        display: flex;
        height: 220px;
        width: 100%;
        justify-content: center;
        align-items: center;
        user-select: none;
    }

    .void-gif-keyboard-item img {
        width: 100%;
        background-size: contain;
        background-repeat: no-repeat;
        border-radius: 4px;
        cursor: pointer;
        display: block;

    }

    .void-gif-like-container {
        position: relative;
        width: 100%;
        display: inline-block;
    }

    .void-gif-like-container img {
        width: 100%;
    }

    .void-gif-like-container .void-gif-like {
        position: absolute;
        top: 6px;
        right: 6px;
        height: 20px;
        opacity: 0;
        transition: 0.2s ease-in all;
        color: rgb(var(--color-text-bright));
    }

    .void-gif-like-container:hover .void-gif-like {
        opacity: 1;
    }

    .void-gif-like-container .void-gif-like svg {
        height: 24px;
    }

    .void-gif-like-container .void-gif-like.void-liked,
    .void-liked {
        color: rgb(var(--color-red));
    }

    .void-hidden {
        display: none;
    }

    .void-pagination-container .void-icon-button.void-active,
    .void-pagination-container .void-icon-button:hover {
        color: rgb(var(--color-blue));
    }

    .void-pagination-container .void-icon-button.void-pagination-skip {
        vertical-align: top;
    }

    .void-quick-access {
        display: flex;
        flex-direction: column;
    }

    .void-quick-access .void-quick-access-wrap {
        background: rgb(var(--color-foreground));
        display: grid;
        grid-template-columns: repeat(auto-fill, 60px);
        grid-template-rows: repeat(auto-fill, 80px);
        gap: 15px;
        padding: 15px;
        margin-bottom: 25px;
        border-radius: 4px;
    }

    .void-quick-access .void-quick-access-notifications-wrapper {
        order: 2;
        margin-bottom: 25px;
    }

    .void-quick-access .void-quick-access-notifications {
        background: rgb(var(--color-foreground));
        overflow-y: auto;
        max-height: 300px;
        scrollbar-width: thin;
        scrollbar-color: rgb(var(--color-blue)) rgba(0, 0, 0, 0);
        border-radius: 4px;
        transition: max-height 0.5s;
    }

    .void-notifications-config-wrapper {
        padding: 15px;
    }

    .void-notifications-list {
        padding: 15px;
        display: flex;
        flex-direction: column;
        gap: 8px;
    }

    .void-quick-access-notifications[collapsed="true"] {
        max-height: 0px;
    }

    .void-quick-access-notifications-wrapper .section-header h2 {
        cursor: pointer;
    }

    .void-quick-access .section-header {
        display: flex;
        justify-content: space-between;
    }

    .void-quick-access-timer {
        font-size: 12px;
        color: rgb(var(--color-text));
    }

    .void-quick-access-item {
        display: inline-block;
    }

    .void-quick-access-pfp {
        background-size: contain;
        background-repeat: no-repeat;
        height: 60px;
        width: 60px;
        border-radius: 4px;
    }

    .void-quick-access-username {
        display: inline-block;
        text-align: center;
        bottom: -20px;
        width: 100%;
        word-break: break-all;
        font-size: 1.2rem;
    }

    .void-quick-access-badge {
        position: relative;
    }

    .void-quick-access-badge::after {
        content: "New";
        background: rgb(var(--color-blue));
        border: 3px solid rgb(var(--color-foreground));
        border-radius: 10px;
        padding: 2px 4px;
        font-size: 9px;
        position: absolute;
        top: 2px;
        right: -10px;
        color: white;
    }

    .void-notification-wrapper {
        display: flex;
        gap: 6px;
    }

    .void-notification-wrapper .void-notification-preview {
        width: 30px;
        height: 30px;
        display: inline-block;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: 50% 50%;
        margin-right: 6px;
        border-radius: 4px;
        position: relative;
    }

    .void-notification-wrapper .void-notification-preview.void-notification-preview-media {
        width: auto;
        height: 30px;
        aspect-ratio: 85 / 115;
        background-size: cover;
    }

    .void-notification-wrapper .void-notification-preview[data-count]::after {
        content: attr(data-count);
        background: rgb(var(--color-blue));
        border: 3px solid rgb(var(--color-foreground));
        border-radius: 10px;
        padding: 2px 4px;
        font-size: 9px;
        position: absolute;
        top: -6px;
        right: -10px;
        color: white;
    }

    .void-notification-preview-wrapper {
        position: relative;
    }

    .void-notification-preview-wrapper:hover .void-notification-group,
    .void-notification-group:hover {
        display: flex;
    }

    .void-notification-group {
        display: none;
        position: absolute;
        top: -30px;
        z-index: 20;
        background: rgb(var(--color-foreground));
        border-radius: 4px;
        overflow: hidden;
    }

    .void-notification-wrapper:first-child .void-notification-group {
        top: unset;
        bottom: -30px;
    }

    .void-notification-group .void-notification-group-item {
        display: block;
        width: 30px;
        height: 30px;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: 50% 50%;
        z-index: 20;
    }

    .void-notification-wrapper {
        display: flex;
        align-items: center;
    }

    .void-notification-wrapper .void-notification-context .void-notification-context-actor {
        color: rgb(var(--color-blue));
    }

    .void-notification-context-reason {
        font-size: 12px;
        padding-top: 2px;
    }

    .void-notification-wrapper .void-notification-timestamp {
        font-size: 10px;
        margin-left: auto;
        align-self: start;
        min-width: fit-content;
    }

    .void-unread-notification {
        margin-left: -5px;
        border-left: 5px solid rgb(var(--color-blue));
    }

    .void-notification-settings-header {
        margin-bottom: 8px;
    }

    .void-notification-type-list-header {
        font-size: 16px;
        margin: 8px 0 4px 0;
    }

    #void-notifications-feed-container {
        display: grid;
        grid-template-columns: fit-content(200px) auto;
        gap: 20px;
    }

    @media screen and (max-width: 800px) {
        #void-notifications-feed-container {
            grid-template-columns: 1fr;
        }
    }


    .void-notifications-feed-settings {
        margin-top: 25px;
    }

    .void-notifications-feed-sidebar .void-notifications-config-wrapper {
        padding: 0;
        max-height: max-content;
        transition: max-height 0.5s;
        overflow: hidden;
        font-size: 12px;
    }

    .void-notifications-feed-sidebar .void-notification-settings-header {
        cursor: pointer;
        user-select: none;
    }

    .void-notifications-feed-sidebar .void-notifications-config-wrapper[collapsed="true"] {
        max-height: 0px;
    }

    .notifications-feed.container:has(#void-notifications-feed-container) {
        display: block;
    }

    .void-notifications-feed-filters {
        display: flex;
        flex-direction: column;
        gap: 5px;
    }

    .void-notifications-feed-filter {
        padding: 6px 8px;
        border-radius: 4px;
        cursor: pointer;
        text-transform: capitalize;
    }

    .void-notifications-feed-filter.void-active,
    .void-notifications-feed-filter:hover {
        background-color: rgb(var(--color-foreground));
    }

    .void-notifications-feed-list {
        display: flex;
        flex-direction: column;
        gap: 12px;
    }

    .void-notifications-feed-list .void-notification-wrapper {
        background: rgb(var(--color-foreground));
        border-radius: 4px;
        padding: 8px;
    }

    .void-notifications-feed-list .void-notification-preview{
        width: 50px;
        height: 50px;
    }

    .void-notifications-feed-list .void-notification-group-item {
        width: 40px;
        height: 40px;
    }

    .void-notifications-feed-list .void-notification-preview.void-notification-preview-media {
        height: 50px;
    }

    .void-notifications-feed-list .void-notification-group,
    .void-notifications-feed-list .void-notification-wrapper:first-child .void-notification-group {
        top: -40px;
        bottom: unset;
    }


    .void-notification-preview-relation {
        height: 30px;
        aspect-ratio: 1 / 1;
        background-size: cover;
        background-repeat: no-repeat;
        background-position: 50% 50%;
        border-radius: 4px;
        position: absolute;
        bottom: -3px;
        right: -3px;
    }

    .void-quick-access-notifications-wrapper .void-notification-preview-relation {
        height: 18px;
    }

    .void-notifications-feed-empty-notice {
        text-align: center;
    }

    .void-notifications-load-more-button,
    .void-notification-all-read-button {
        display: block;
        width: 100%;
        font-weight: 650;
    }

    .void-notification-dot {
        background:rgb(var(--color-peach));
        border-radius:50%;
        bottom:0;
        color:#fff2f2;
        display:block;
        font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
        font-size:1.2rem;
        font-weight:500;
        height:20px;
        left:10px;
        line-height:20px;
        overflow:hidden;
        padding-right:1px;
        position:relative;
        text-align:center;
        transition:.2s;
        width:20px
    }

    .void-notification-dot:visited,
    .void-notification-dot:hover {
        color:#fff2f2;
    }

    .void-chip {
        background: rgb(var(--color-blue));
        border-radius: 9999px;
        display: inline-block;
        padding: 2px 4px;
        font-size: 9px;
        color: white;
        margin: 0px 4px;
        vertical-align: top;
        user-select: none;
    }

    .void-loader {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        display: block;
        margin:15px auto;
        position: relative;
        color: rgb(var(--color-blue));
        left: -100px;
        box-sizing: border-box;
        animation: shadowRolling 2s linear infinite;
    }

        @keyframes shadowRolling {
        0% {
            box-shadow: 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
        }
        12% {
            box-shadow: 100px 0 rgb(var(--color-blue)), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
        }
        25% {
            box-shadow: 110px 0 rgb(var(--color-blue)), 100px 0 rgb(var(--color-blue)), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
        }
        36% {
            box-shadow: 120px 0 rgb(var(--color-blue)), 110px 0 rgb(var(--color-blue)), 100px 0 rgb(var(--color-blue)), 0px 0 rgba(255, 255, 255, 0);
        }
        50% {
            box-shadow: 130px 0 rgb(var(--color-blue)), 120px 0 rgb(var(--color-blue)), 110px 0 rgb(var(--color-blue)), 100px 0 rgb(var(--color-blue));
        }
        62% {
            box-shadow: 200px 0 rgba(255, 255, 255, 0), 130px 0 rgb(var(--color-blue)), 120px 0 rgb(var(--color-blue)), 110px 0 rgb(var(--color-blue));
        }
        75% {
            box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 130px 0 rgb(var(--color-blue)), 120px 0 rgb(var(--color-blue));
        }
        87% {
            box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 130px 0 rgb(var(--color-blue));
        }
        100% {
            box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0);
        }
        }

    .void-notice {
        font-size: 11px;
        margin-top: 5px;
    }

    .void-select {
        display: inline-flex;
        flex-wrap: wrap;
    }

    .void-select .void-option {
        padding: 3px 8px;
        background: rgb(var(--color-foreground-blue));
        font-size: 12px;
        cursor: pointer;
        user-select: none;
    }

    .void-select .void-option:hover {
        background: rgb(var(--color-foreground-blue-dark));
        color: rgb(var(--color-text));
    }

    .void-select .void-option:first-child {
        border-radius: 4px 0px 0px 4px;
    }

    .void-select .void-option:last-child {
        border-radius: 0px 4px 4px 0px;
    }

    .void-select .void-option.active {
        background: rgb(var(--color-blue));
        color: rgb(var(--color-text-bright));
    }

    .void-label-container {
        margin-top: 6px;
        margin-bottom: 6px;
    }

    .void-label-span {
        margin-right: 10px;
        min-width: 200px;
        display: inline-block;
    }

    .void-upload-in-progress {
        cursor: wait !important;
    }

    body:has(.void-modal-background[open]) {
        overflow: hidden;
    }

    .void-modal-background[open] {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        z-index: 9999;
        backdrop-filter: brightness(50%);
        background: transparent;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .void-modal {
        background: rgb(var(--color-foreground));
        color: rgb(var(--color-text));
        border-radius: 4px;
        min-width: 500px;
        padding: 0;
        margin: 0;
        border-width: 0px;
    }

    .void-modal-header {
        width: 100%;
        background: rgb(var(--color-background));
        padding: 12px;
        border-radius: 4px 4px 0px 0px;
        display: flex;
        justify-content: space-between;
        font-weight: 700;
    }

    .void-modal-header .void-icon-button {
        color: rgb(var(--color-red));
        height: 20px;
    }

    .void-modal-content {
        padding: 12px;
        max-height: 500px;
        overflow-y: scroll;
    }

    .void-change-log-header {
        margin: 4px 0px;
    }

    .void-change-log-note {
        margin-bottom: 16px;
    }

    .void-change-log-list {
        list-style: none;
        gap: 5px;
        padding-left: 20px;
    }

    .void-change-log-list li:not(:last-child) {
        margin-bottom: 4px;
    }

    .void-change-log-list-item span:first-child {
        text-align: center;
        width: 13px;
        display: inline-block;
    }

    .void-change-log-list-item span:last-child {
        margin-left: 6px;
    }

    .void-tooltip-container:has(input) {
        display: inline-block;
    }

    .void-tooltip-container {
        position: relative;
    }

    .void-tooltip {
        position: absolute;
        text-align: center;
        top: -8px;
        left: 50%;
        transform: translate(-50%, -100%);
        font-size: 12px;
        padding: 4px 6px;
        background: rgb(var(--color-foreground-blue));
        border-radius: 4px;
        width: max-content;
        max-width: 200px;
        visibility: hidden;
        z-index: 3000;
    }

    .void-tooltip-container:hover .void-tooltip {
        visibility: visible;
    }

    .void-tooltip-container:hover .void-tooltip:after {
        content: "";
        position: absolute;
        top: 100%;
        left: 50%;
        margin-left: -5px;
        border-width: 5px;
        border-style: solid;
        border-color: rgb(var(--color-foreground-blue)) transparent transparent transparent;
    }

    .void-button.void-slim,
    .void-button.void-self-message {
        margin-top: 0;
        margin-right: 0;
        margin-left: 18px;
        font-family: inherit;
        font-weight: 900;
    }

    .activity-edit .rules-notice {
        display: block;
    }

    #void-toast-container {
        position: fixed;
        display: flex;
        flex-direction: column;
        gap: 10px;
    }

    #void-toast-container.void-bottom-left {
        bottom: 10px;
        left: 10px;
        flex-direction: column-reverse;
    }

    #void-toast-container.void-bottom-right {
        bottom: 10px;
        right: 10px;
        flex-direction: column-reverse;
    }

    #void-toast-container.void-top-left {
        top: 70px;
        left: 10px;
    }

    #void-toast-container.void-top-right {
        top: 70px;
        right: 10px;
    }

    .void-toast {
        font-size: 14px;
        color: rgb(var(--color-text-bright));
        min-width: 150px;
        max-width: 300px;
        min-heigth: 50px;
        padding: 10px 8px;
        border-radius: 4px;
    }

    .void-info {
        background: rgb(var(--void-info));
    }

    .void-success {
        background: rgb(var(--void-success));
    }

    .void-error {
        background: rgb(var(--void-error));
    }

    .void-warning {
        background: rgb(var(--void-warning));
    }

    .void-activity-reply-controls-container {
    	background: rgb(var(--color-foreground));
    	border-radius: 4px;
        justify-self: flex-start;
        margin: 10px 0px;
        display: flex;
        flex-direction: column;
        gap: 8px;
    }

    .void-activity-reply-controls-container[closed="true"] {
    	display: none;
    }

    .void-media-status-controls {
    	display: flex;
    	gap: 10px;
    	padding: 12px;
    	height: 168px;
    }

    .void-media-status-controls .void-gif-keyboard-list-placeholder {
    	height: unset;
    }

    .void-activity-reply-progress-container {
    	display: flex;
    	flex-direction: column;
    	max-width: 100%;
    	flex-shrink: 1;
    	overflow: hidden;
    }

    .void-activity-reply-progress-container .void-media-search-title {
		font-size: 16px;
    }

    .void-activity-reply-progress-container .void-media-search-type {
    	font-size: 12px;
    	margin-top: 2px;
    }

    .void-activity-reply-progress-container .void-layout-header {
    	margin-top: 10px;
    	margin-bottom: 5px;
    }

    .void-reply-button-container {
    	display: flex;
    	justify-content: flex-end;
    }

    .void-activity-reply-toggle-button {
    	display: flex;
    	align-items: center;
    	background-color: rgb(var(--color-blue));
    	padding: 10px 15px;
    	border-radius: 4px;
    }

    .void-media-status-controls .void-status-poster {
    	height: 144px;
    	aspect-ratio: 75 / 115;
    	object-fit: cover;
    }

	.void-activity-reply-toggle-button svg {
	    height: 18px;
	}

	.void-activity-reply-header {
		width: 100%;
		background: rgb(var(--color-foreground-grey-dark));
		display: flex;
		justify-content: space-between;
		padding: 12px;
		border-radius: 4px 4px 0px 0px;
	}

    .void-activity-reply-search-container {
        position: relative;
        width: fit-content;
    }

    #void-media-search-list {
        position: absolute;
        z-index: 999;
        background: rgb(var(--color-foreground));
        border: 2px solid rgb(var(--color-blue));
        border-top: 0px solid transparent;
        width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        border-radius: 0px 0px 4px 4px;
    }

    .void-ace-editor {
    	width: 100%;
    	height: 600px;
    	max-height: 800px;
    	resize: vertical;
    	min-height: 150px;
    }

    .void-status-poster {
        height: 85px;
        border-radius: 4px;
        user-select: none;
    }

    .void-media-search-result {
        cursor: pointer;
        padding: 4px 6px;
        display: flex;
        gap: 5px;
        text-overflow: ellipsis;
        align-items: center;
    }

    .void-media-search-poster {
        width: 30px;
        height: 30px;
        aspect-ratio: 1 / 1;
        object-fit: cover;
        border-radius: 4px;
    }

    .void-media-search-info {
        max-width: calc(100% - 30px - 4px - 4px);
    }

    .void-media-search-title {
        font-size: 12px;
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
    }

    .void-media-search-type {
        font-size: 10px;
        font-style: italic;
    }

    .void-media-search-result:hover {
        background: rgb(var(--color-foreground-grey));
    }
    .
`;

;// CONCATENATED MODULE: ./src/handlers/libraryLoader.ts
class LibraryLoader {
    static loadScript(url, callback) {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;
        script.onload = callback;
        document.head.appendChild(script);
    }
    static loadAceEditor() {
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ace.min.js", () => {
            // @ts-ignore
            ace.config.set("packaged", true);
            // @ts-ignore
            ace.config.set("basePath", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ace.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/worker-css.js'", () => { });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/mode-css.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("ace/mode/css", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/mode-css.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ext-inline_autocomplete.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ext-inline_autocomplete.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ext-beautify.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ext-beautify.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.9.6/theme-monokai.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("https://cdnjs.cloudflare.com/ajax/libs/ace/1.9.6/theme-monokai.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.9.6/theme-dawn.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("https://cdnjs.cloudflare.com/ajax/libs/ace/1.9.6/theme-dawn.min.js");
        });
        this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/keybinding-vscode.min.js", () => {
            // @ts-ignore
            ace.config.setModuleUrl("https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/keybinding-vscode.min.js");
        });
    }
}

;// CONCATENATED MODULE: ./src/voidverified.user.js











LibraryLoader.loadAceEditor();

const settings = new Settings();
Toaster.initializeToaster(settings);
const styleHandler = new StyleHandler(settings);
styleHandler.refreshStyles();
styleHandler.createStyleLink(styles, "script");

try {
	const intervalScriptHandler = new IntervalScriptHandler(settings);
	intervalScriptHandler.enableScriptIntervalHandling();
} catch (error) {
	Toaster.critical(
		"A critical error has occured setting up intervalScriptHandler. Please check developer console and contact voidnyan.",
	);
	console.error(error);
}

try {
	const pasteHandler = new PasteHandler(settings);
	pasteHandler.setup();
} catch (error) {
	Toaster.critical(
		"A critical error has occured setting up pasteHandler. Please check developer console and contact voidnyan.",
	);
}

new ChangeLog(settings).renderChangeLog();

new ImgurAPI(
	new ImageHostService().getImageHostConfiguration(imageHosts.imgur),
).refreshAuthToken();

console.log(`VoidVerified ${settings.version} loaded.`);

/******/ })()
;