Chaturbate Enhancer

Plibonigas Chaturbate aldonante plurajn novajn funkciojn.

Ekde 2023/05/10. Vidu La ĝisdata versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name             Chaturbate Enhancer
// @name:de          Chaturbate Enhancer
// @name:es          Chaturbate Enhancer
// @name:es-CO       Chaturbate Enhancer
// @name:it          Chaturbate Enhancer
// @name:fr          Chaturbate Enhancer
// @name:fr-CA       Chaturbate Enhancer
// @name:ru          Chaturbate Enhancer
// @name:tr          Chaturbate Enhancer
// @name:ro          Chaturbate Enhancer
// @name:no          Chaturbate Enhancer
// @name:nl          Chaturbate Enhancer
// @name:pl          Chaturbate Enhancer
// @name:ja          Chaturbate Enhancer
// @name:el          Chaturbate Enhancer
// @name:hu          Chaturbate Enhancer
// @name:fi          Chaturbate Enhancer
// @name:ar          Chaturbate Enhancer
// @name:hi          Chaturbate Enhancer
// @name:id          Chaturbate Enhancer
// @name:ko          Chaturbate Enhancer
// @name:pt-PT       Chaturbate Enhancer
// @name:pt-BR       Chaturbate Enhancer
// @name:zh          Chaturbate Enhancer
// @name:zh-CN       Chaturbate Enhancer
// @name:zh-TW       Chaturbate Enhancer
// @name:cs          Chaturbate Enhancer
// @name:sk          Chaturbate Enhancer
// @name:sl          Chaturbate Enhancer
// @name:sv          Chaturbate Enhancer
// @name:sr          Chaturbate Enhancer
// @name:af          Chaturbate Enhancer
// @name:sq          Chaturbate Enhancer
// @name:hy          Chaturbate Enhancer
// @name:be          Chaturbate Enhancer
// @name:bg          Chaturbate Enhancer
// @name:da          Chaturbate Enhancer
// @name:et          Chaturbate Enhancer
// @name:he          Chaturbate Enhancer
// @name:hr          Chaturbate Enhancer
// @name:fa          Chaturbate Enhancer
// @name:ur          Chaturbate Enhancer
// @name:bn          Chaturbate Enhancer
// @name:th          Chaturbate Enhancer
// @name:eo          Chaturbate Enhancer
// @name:ug          Chaturbate Enhancer
// @name:vi          Chaturbate Enhancer
// @description      Enhances Chaturbate by adding multiple new features.
// @description:de   Verbessert Chaturbate durch Hinzufügen mehrerer neuer Funktionen.
// @description:es   Mejora Chaturbate al agregar múltiples funciones nuevas.
// @description:es-CO Mejora Chaturbate al agregar múltiples funciones nuevas.
// @description:it   Migliora Chaturbate aggiungendo più nuove funzionalità.
// @description:fr   Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
// @description:fr-CA Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
// @description:ru   Улучшает Chaturbate, добавляя несколько новых функций.
// @description:tr   Birden çok yeni özellik ekleyerek Chaturbate'i geliştirir.
// @description:ro   Îmbunătățește Chaturbate prin adăugarea de mai multe funcții noi.
// @description:no   Forbedrer Chaturbate ved å legge til flere nye funksjoner.
// @description:nl   Verbetert Chaturbate door meerdere nieuwe functies toe te voegen.
// @description:pl   Ulepsza Chaturbate, dodając wiele nowych funkcji.
// @description:ja   複数の新機能を追加して Chaturbate を強化します。
// @description:el   Βελτιώνει το Chaturbate προσθέτοντας πολλές νέες δυνατότητες.
// @description:hu   Több új funkció hozzáadásával továbbfejleszti a Chaturbate szolgáltatást.
// @description:fi   Parantaa Chaturbatea lisäämällä useita uusia ominaisuuksia.
// @description:ar   يعزز Chaturbate عن طريق إضافة ميزات جديدة متعددة.
// @description:hi   कई नई सुविधाओं को जोड़कर Chaturbate को बेहतर बनाता है।
// @description:id   Meningkatkan Chaturbate dengan menambahkan beberapa fitur baru.
// @description:ko   여러 새로운 기능을 추가하여 Chaturbate를 향상시킵니다.
// @description:pt-PT Aprimora o Chaturbate adicionando vários novos recursos.
// @description:pt-BR Aprimora o Chaturbate adicionando vários novos recursos.
// @description:zh   通过添加多个新功能来增强 Chaturbate。
// @description:zh-CN 通过添加多个新功能来增强 Chaturbate。
// @description:zh-TW 通过添加多个新功能来增强 Chaturbate。
// @description:cs   Vylepšuje Chaturbate přidáním několika nových funkcí.
// @description:sk   Vylepšuje Chaturbate pridaním viacerých nových funkcií.
// @description:sl   Izboljša Chaturbate z dodajanjem več novih funkcij.
// @description:sv   Förbättrar Chaturbate genom att lägga till flera nya funktioner.
// @description:sr   Побољшава Цхатурбате додавањем више нових функција.
// @description:af   Verbeter Chaturbate deur verskeie nuwe kenmerke by te voeg.
// @description:sq   Përmirëson Chaturbate duke shtuar veçori të shumta të reja.
// @description:hy   Ընդլայնում է Chaturbate-ը՝ ավելացնելով բազմաթիվ նոր հնարավորություններ:
// @description:be   Паляпшае Chaturbate шляхам дадання некалькіх новых функцый.
// @description:bg   Подобрява Chaturbate чрез добавяне на множество нови функции.
// @description:da   Forbedrer Chaturbate ved at tilføje flere nye funktioner.
// @description:et   Täiustab Chaturbate'i, lisades mitu uut funktsiooni.
// @description:he   משפר את Chaturbate על ידי הוספת תכונות חדשות מרובות.
// @description:hr   Poboljšava Chaturbate dodavanjem više novih značajki.
// @description:fa   Chaturbate را با افزودن چندین ویژگی جدید تقویت می کند.
// @description:ur   متعدد نئی خصوصیات شامل کرکے Chaturbate کو بہتر بناتا ہے۔
// @description:bn   একাধিক নতুন বৈশিষ্ট্য যোগ করে Chaturbate উন্নত করে।
// @description:th   ปรับปรุง Chaturbate ด้วยการเพิ่มคุณสมบัติใหม่หลายอย่าง
// @description:eo   Plibonigas Chaturbate aldonante plurajn novajn funkciojn.
// @description:ug   كۆپ خىل يېڭى ئىقتىدارلارنى قوشۇش ئارقىلىق Chaturbate نى كۈچەيتىدۇ.
// @description:vi   Cải thiện Chaturbate bằng cách thêm nhiều tính năng mới.
// @version          3.1.0
// @author           improper.dev
// @license          CC-BY-ND-4.0
// @copyright        MoonDivision (https://sleazyfork.org/en/users/884016-improper-dev)
// @namespace        https://sleazyfork.org/en/users/884016-improper-dev
// @homepage         https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer
// @supportURL       https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer/feedback
// @contributionURL  https://cb-enh.improper.dev/contribute
// @icon             https://www.google.com/s2/favicons?sz=32&domain=chaturbate.com
// @icon64           https://www.google.com/s2/favicons?sz=64&domain=chaturbate.com
// @match            https://chaturbate.com/*
// @match            https://*.chaturbate.com/*
// @connect          camschedule.com
// @connect          onechance.onelove.workers.dev
// @connect          cb-enh.improper.dev
// @connect          cb-enh-thumb.improper.dev
// @grant            GM_addStyle
// @grant            GM_addElement
// @grant            GM_xmlhttpRequest
// @grant            GM_registerMenuCommand
// @grant            GM_unregisterMenuCommand
// @grant            GM_setClipboard
// @require          https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require          https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js
// @run-at           document-start
// @noframes
// ==/UserScript==

(function() {
'use strict';

// webext compat start
if(typeof window.unsafeWindow === 'undefined') {
	window.unsafeWindow = window;
}

if(unsafeWindow.___cbEnhancer___) {
	return;
}
unsafeWindow.___cbEnhancer___ = true;

function addStyle(style) {
	if(typeof GM_addStyle !== 'undefined') {
		return GM_addStyle(style);
	}

	let styleEl = document.createElement('style');
	styleEl.textContent = style;
	document.head.appendChild(styleEl);
}

function addElement(parent_node, tag_name, attributes) {
	if(typeof GM_addElement !== 'undefined') {
		return GM_addElement(parent_node, tag_name, attributes)
	}

	let id = Date.now();
	let msgData = {
		'msg-name': 'cb-enh-add-element',
		'data': {
			'parent-node': id,
			'tag-name': tag_name,
			'attributes': attributes
		}
	};
	$(parent_node).addClass('cb-enh-add-element-id-' + id);
	window.postMessage(JSON.stringify(msgData), '*');
}

let xhrTasks = [];
function xmlhttpRequest(details) {
	if(typeof GM_xmlhttpRequest !== 'undefined') {
		return GM_xmlhttpRequest(details);
	}

	let id = xhrTasks.length;
	xhrTasks[id] = details;

	let msgData = {
		'msg-name': 'cb-enh-xhr',
		'data': {
			'xhr-id': id,
			'details': details
		}
	};

	window.postMessage(JSON.stringify(msgData), '*');
}

window.addEventListener('message', function(e) {
	let msgData;
	try {
		msgData = JSON.parse(e.data);
	}
	catch(e) {
		return;
	}

	if(!'msg-name' in msgData || msgData['msg-name'] != 'cb-enh-xhr-event') {
		return;
	}

	if(!'data' in msgData || !'data' in msgData['data']) {
		return;
	}

	let id = msgData['data']['xhr-id'];
	if(!id in xhrTasks) {
		return;
	}

	let event_name = msgData['data']['event-name'];
	let event = msgData['data']['event'];
	event.responseText = msgData['data']['responseText'];

	if(!xhrTasks[id][event_name]) {
		return;
	}

	xhrTasks[id][event_name](event);
});

function registerMenuCommand(name, callback, accessKey) {
	if(typeof GM_registerMenuCommand !== 'undefined') {
		return GM_registerMenuCommand(name, callback, accessKey);
	}

	// @todo
}

function unregisterMenuCommand(menuCmdId) {
	if(typeof GM_unregisterMenuCommand !== 'undefined') {
		return GM_unregisterMenuCommand(menuCmdId);
	}

	// @todo
}

function setClipboard(data, info) {
	if(typeof GM_setClipboard !== 'undefined') {
		return GM_setClipboard(data, info);
	}

	// @todo
}
// webext compat end

let intvWaitBody = null;
let intvWaitVideo = null;
let intvUpdateAvatarInPrivBoard = null;
let intvUpdateFollowedList = null;
let gIntvCheckIfRoomIsOnline = null;

let gSettings = {};
let gLocales = {};
let currentHoverInterval = null;
let lastLoadedThumbReqTime = 0;
let gCurrentBroadcaster = null;

let gVideoControlsModalShown = false;

let gCapturingScreenshot = false;

let gRecording = false;
let gRecordingStream = null;
let gRecordingCanceledByUser = false;

let gIsInMultiView = false;

function getCookie(name) {
	let nameEQ = encodeURIComponent(name) + "=";
	let ca = document.cookie.split(';');
	for(let i = 0; i < ca.length; i++) {
		let c = ca[i];
		while(c.charAt(0) === ' ') {
			c = c.substring(1, c.length);
		}

		if(c.indexOf(nameEQ) === 0) {
			return decodeURIComponent(c.substring(nameEQ.length, c.length));
		}
	}
	return null;
}

function doNeedDarkMode() {
	return getCookie('theme_name') === null && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}

function enableDarkMode() {
	// Automatically enable site dark mode if system has dark mode enabled
	if(document.body && doNeedDarkMode()) {
		$('body').addClass('darkmode');
		document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
		document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
	}
}

if(doNeedDarkMode()) {
	if(document.body) {
		enableDarkMode();
	}
	else {
		intvWaitBody = setInterval(function() {
			if(document.body) {
				enableDarkMode();
				clearIntervalEx(intvWaitBody);
			}
		}, 10);
	}
}

function isMultiViewPage() {
	return window.location.pathname === '/' && (window.location.hash === '#multicam' || window.location.hash.startsWith('#multicam?rooms='));
}

if(isMultiViewPage()) {
	addStyle(`
		.content {
			display: none !important;
		}
	`);
}

let style = `
/* Hide media overlays */
.photoVideoDetailSection img {
	filter: unset !important;
}

.userUpload div {
	background: none !important;
}

.psContainer .lockOverlayBg, .smContainer .lockOverlayBg {
	display: none !important;
}

.userUpload img[src$="lock.svg"] {
	display: none !important;
}

/* Hide ads */
.ad, .vote-banner {
	display: none !important;
}

.cb-enh-avatar {
	margin-left: 10px;
	border: 1px solid #bfbfbf;
	width: 150px;
	height: 150px;
	background-color: #ebebeb;
	margin-bottom: 5px;
	background-size: 100% 100%;
	position: relative;
}

.darkmode .cb-enh-avatar {
	border-color: #2d3e50;
	background-color: #202c39;
}

.cb-enh-avatar, .cb-enh-avatar img {
	border-radius: 150px;
}

.cb-enh-avatar img {
	width: 100%;
	height: 100%;
	opacity: 0;

	position: absolute;
	left: 0;
	top: 0;

	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	user-drag: none;
	app-region: no-drag;

	pointer-events: none;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.cb-enh-offline-snapshot {
	display: block;
	border-radius: 4px;
	margin-top: 10px;
}

.cb-enh-footer {
	font-size: 14px;
	color: #341b00;
	font-weight: bold;
}

.darkmode .cb-enh-footer {
	color: #efefef;
}

.cb-enh-footer a {
	color: inherit !important;
	text-decoration: underline;
}

/* Enlarge media in bio */
tr:not(.smContainer) .contentText .previewBorder {
	width: 190px;
	height: 135px;
}

tr:not(.smContainer) .contentText .tokenText {
	top: 118px !important;
	right: 5px !important;
}

/* Detach floaters in "about" */
tr:not(.smContainer):not(.psContainer) .contentText img, tr:not(.smContainer):not(.psContainer) .contentText li, tr:not(.smContainer):not(.psContainer) .contentText a, tr:not(.smContainer):not(.psContainer) .contentText p, tr:not(.smContainer):not(.psContainer) .contentText span {
	position: unset !important;
}

/* Disable custom backgrounds in bio */
tr:not(.smContainer):not(.psContainer) .contentText * {
	background: unset !important;
}

/* Disable custom cursor in bio */
tr:not(.smContainer):not(.psContainer) .contentText * {
	cursor: auto !important;
}

tr:not(.smContainer):not(.psContainer) .contentText a {
	cursor: pointer !important;
}

tr:not(.smContainer):not(.psContainer) .contentText a * {
	cursor: pointer !important;
}
/**/

.cb-enh-video {
	max-width: 900px;
	margin: 0px;
	padding: 0px;
	width: 100%;
	height: 100%;
	object-fit: contain;
	background-color: rgba(0, 0, 0, 0);
	display: inline;
	border: 0;
	outline: 0;
	border-radius: 4px;
}

.cb-enh-video::-webkit-media-controls-play-button {
	display: none;
}

.cb-enh-video::-webkit-media-controls-timeline {
	display: none;
}

.cb-enh-video::-webkit-media-controls-current-time-display {
	display: none;
}

.cb-enh-video::-webkit-media-controls-timeline-container {
	display: none;
}

.cb-enh-video::-webkit-media-controls-time-remaining-display {
	display: none;
}

.cb-enh-schedule-frame {
	width: 100%;
	height: 350px;
	border: 0;
}

.cb-enh-chat-frame {
	width: 100%;
	max-width: 1400px;
	height: 700px;
	border: 0;
	border-radius: 4px;
}

#cb-enh-inac-load-chat {
	cursor: pointer;
}

.noselect {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.cb-enh-video-bar-btn {
	height: 15px;
	width: auto;
	position: relative;
	overflow: hidden;
	-webkit-tap-highlight-color: transparent;
	font-family: UbuntuMedium, Helvetica, Arial, sans-serif;
	font-size: 12px;
	padding: 3px 8px 2px;
	top: -4px;right: 1px;
	float: right;
	border-radius: 3px;
	cursor: pointer;
	margin-right: 5px;
	background-color: #880471;
	color: white;
	text-transform: uppercase;
}

.cb-enh-tab-bar-modal {
	width: 500px;
	border-width: 1px;
	position: absolute;
	border-style: solid;
	border-radius: 4px;
	font-size: 14px;
	padding: 8px 0px 8px 8px;
	display: none;
	z-index: 5;
	line-height: 22px;
	box-shadow: rgba(0, 0, 0, 0.08) 0px 4px 16px;
}

.cb-enh-tab-bar-modal-arrow-down {
	position: absolute;
	width: 0;
    height: 0;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
    border-top: 10px solid #0554a3;
}

.videoPlayerDiv video.cb-enh-video-mirrored {
	transform: scale(-1, 1) !important;
}

.videoPlayerDiv video.cb-enh-video-inverted {
	transform: scale(1, -1) !important;
}

.videoPlayerDiv video.cb-enh-video-mirrored.cb-enh-video-inverted {
	transform: scale(-1, -1) !important;
}

/* fix mirroring/inverting video doesn't work when video is fullscreen */
.videoPlayerDiv:not([style*='height: 100%; width: 100%;']) video.cb-enh-video-mirrored, .videoPlayerDiv:not([style*='height: 100%; width: 100%;']) video.cb-enh-video-inverted {
	width: calc(100% - 10px) !important;
}

.vjs-fullscreen video.cb-enh-video-mirrored, .vjs-fullscreen video.cb-enh-video-inverted {
	width: calc(100% - 10px) !important;
}

.cb-enh-video-controls-modal-btns {
	margin-top: 5px;
}

.cb-enh-vid-control-slider {
	width: 50%;
}

#cb-enh-video-controls-record {
	background-color: #090;
}

#cb-enh-video-controls-record.cb-enh-active {
	background-color: #ff0000;
}

/* Do not display entrance terms overlay */
.entrance-terms--shown {
	position: inherit !important;
	top: inherit !important;
	left: inherit !important;
	right: inherit !important;
	bottom: inherit !important;
	overflow: inherit !important;
	background-color: #fff;
	visibility: inherit !important;
}

#entrance_terms_overlay {
	display: none !important;
}

/* Acc info box */
#cb-enh-acc-info {
	width: 500px;
	height: 69px;
	box-sizing: border-box;
	font-size: 15px;
	overflow: hidden;
	display: inline-block;
	vertical-align: top;
	margin: 0px;
	float: right;

	color: #292929;
}

.darkmode #cb-enh-acc-info {
    color: #f2f2f2;
}

.cb-enh-acc-info-outer {
	position: relative;
	height: 100%;
}

.cb-enh-acc-info-inner {
	margin: 0;
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	right: 0%;
	margin-right: 10px;
	cursor: pointer;
}

.blurred-login-overlay > div > span, .blurred-login-overlay > div > hr {
	display: none;
}

.thumbnail_label {
	pointer-events: none;
	text-transform: uppercase;
}

/* add outline to text in interactive fullscreen/theater mode */
/* and noselect */
div[data-paction="TheaterOverlayTabs"] span {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* add outline to text in interactive fullscreen/theater mode (selected quality text) */
/* and noselect */
span[title="Video Quality"] {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* add outline to text in interactive fullscreen/theater mode (quality selector qualities) */
div#TheaterModePlayer div[ts="r"] div:not([style*="color: rgb(51, 51, 51);"]) {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

div.slider[title="Volume Slider"] div:nth-child(1) {
	border-top: 1px solid;
	border-right: 1px solid;
	border-bottom: 1px solid;
	border-color: #1c1c1c;
}

div.slider[title="Volume Slider"] div:nth-child(2) {
	border-top: 1px solid;
	border-left: 1px solid;
	border-bottom: 1px solid;
	border-color: #1c1c1c;
}

div.slider[title="Volume Slider"] div:nth-child(3) {
	border: 1px solid #1c1c1c;
}

/* and noselect */
div.slider[title="Volume Slider"] div {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* styling for hover are for video controls in theater/interactive fullscreen mode */
#cb-enh-theater-controls-area {
	position: absolute;
	width: 100%;
	heigth: 50px;
}

/* make video controls theater/interactive fullscreen mode appear when bottom of video is hovered */
div[data-paction="VideoControl"] {
	display: block !important;
	opacity: 1;
}

div[data-paction="VideoControl"][style*="display: none;"] {
	opacity: 0;
}

/* make quality selector slighty taller so scrollbar won't appear */
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
	max-height: 20em !important;
}

/* make quality selector nicely rounded */
div.vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button .vjs-menu-content {
	border-radius: 8px 8px 0 0;
}

#TheaterModePlayer #volume-high, #TheaterModePlayer #volume-mute {
	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	user-drag: none;
	app-region: no-drag;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* styling for grid size selector */
ul.sub-nav.genderTabs {
	float: left;
}

.cb-enh-grid-size-selector-img {
	float: right;
	position: relative;
	margin: 0;
	display: block;
	width: 30px;
	margin-left: 0px;
	top: -8px;
	
	cursor: pointer;
}

.darkmode .cb-enh-grid-size-selector-img path {
	fill: #002e43;
}

.darkmode .cb-enh-grid-size-selector-img path {
	fill: #68b4ef;
}

.cb-enh-grid-size-selector-img.cb-enh-first {
	margin-right: 25px;
}
/* end of styling for grid size selector */

/* styling for "more rooms" tab grid size selector */
.MoreRooms .list .roomCard a img {
	width: 100%;
	height: 100%;
}

.cb-enh-grid-size-selector-more-img {
	margin-top: -20px;
	margin-right: -10px !important;
	margin-left: 8px;
}
/* end of styling for "more rooms" tab grid size selector */

#cb-enh-add-multi-link {
	text-decoration: none;
}

.cb-enh-add-cam-icon {
	float: right;
	cursor: pointer;
}

.darkmode .cb-enh-add-cam-icon svg path, .darkmode #cb-enh-multi-tip-add-icon svg path {
	fill: #fbfbfb;
}

`;

addStyle(style);

let videoControlsContentHTML = `
<b class="ce-loc" data-ce-loc="vid_controls">Video Controls</b>:<br>

<div>
<input type="checkbox" checked id="cb-enh-video-controls-modal-show-logo"></input>
<label for="cb-enh-video-controls-modal-show-logo" class="ce-loc" data-ce-loc="show_site_logo">Show site logo</label><br>
</div>

<div>
<input type="checkbox" checked id="cb-enh-video-controls-use-intrf-def"></input>
<label for="cb-enh-video-controls-use-intrf-def" class="ce-loc" data-ce-loc="use_intrf_def">Use interactive fullscreen as default fullscreen mode</label><br>
</div>

<div>
<input type="checkbox" id="cb-enh-video-controls-modal-mirror-vid"></input>
<label for="cb-enh-video-controls-modal-mirror-vid" class="ce-loc" data-ce-loc="mirror_video">Mirror video</label><br>
</div>

<div>
<input type="checkbox" id="cb-enh-video-controls-modal-invert-vid"></input>
<label for="cb-enh-video-controls-modal-invert-vid" class="ce-loc" data-ce-loc="invert_video">Invert video</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-brightness" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-brightness" class="ce-loc" data-ce-loc="brightness">Brightness</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-contrast" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-contrast" class="ce-loc" data-ce-loc="contrast">Contrast</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-saturation" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-saturation" class="ce-loc" data-ce-loc="saturation">Saturation</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-sepia" data-default="0" value="0" min="0" max="100" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-sepia" class="ce-loc" data-ce-loc="sepia">Sepia</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-hue" data-default="0" value="0" min="0" max="360" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-hue" class="ce-loc" data-ce-loc="hue">Hue</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-blur" data-default="0" value="0" min="0" max="100" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-blur" class="ce-loc" data-ce-loc="blur">Blur</label>
</div>

<div class="cb-enh-video-controls-modal-btns">
<input type="button" id="cb-enh-video-controls-modal-reset" value="Reset" class="ce-loc" data-ce-loc="reset">
</div>
`;

function getSiteLang() {
	return $('html').attr('lang');
}

let lang = getSiteLang();
if(lang !== 'en') {
	loadLocales(lang);
}

addStyle(style);

let regRedirPreventClick = false;
$(document).ready(function() {
	enableDarkMode();
	clearIntervalEx(intvWaitBody);

	if(getSetting('reg-redir') === 2) {
		setSetting('reg-redir', null);
		setSetting('reg-redir-room', null);
		
		$('.logo-zone a').click();
		regRedirPreventClick = true;
	}

	if(isMultiViewPage()) {
		initMultiView();
	}
	else if(window.location.pathname.startsWith('/roomlogin/')) {
		enhancePasswordedRoom();
	}
	else if('initialRoomDossier' in unsafeWindow) {
		enhanceRoom();
	}

	if(window.location.pathname.startsWith('/followed-cams/')) {
		enhanceFollowedList();
	}

	initAnimatedThumbs();
	initGridSizeSelector();
	initMultiViewUINavigation();
});

function loadLocales(lang) {
	xmlhttpRequest({
		method: 'GET',
		url: 'https://cb-enh.improper.dev/locale/' + lang + '.json',
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			gLocales = data['locales'];
		}
	});
}

function localizeStrings() {
	if(!gLocales) {
		return;
	}

	$('.ce-loc').each(function() {
		let v = $(this).data('ce-loc');
		if(gLocales[v]) {
			if($(this)[0].nodeName.toLowerCase() === 'input') {
				$(this).val(gLocales[v]);
			}
			else {
				$(this).text(gLocales[v]);
			}
		}
	});
}

function localizeStringsNextTick() {
	setTimeout(function() {
		localizeStrings();
	}, 1);
}

function localizeStringsFuture() {
	setTimeout(function() {
		localizeStrings();
	}, 50);
}

function getLocale(name, failsafe) {
	if(gLocales && gLocales[name]) {
		return gLocales[name];
	}

	if(failsafe) {
		return failsafe;
	}
}

function initAnimatedThumbs() {
	let $checkboxComponent = $("#animate_thumbnails_form .checkboxComponent");
	$('#animate_thumbnails_form label').removeAttr("style");
	$("#animate_thumbnails_form .disabledTooltipColor").remove();
	$checkboxComponent.removeClass("disabled");
	$checkboxComponent.css("cursor", "pointer");
	$("#animate_thumbnails_form input").removeAttr("disabled");
	$("#animate_thumbnails_form input").removeAttr("readonly");
	$("#id_animate_thumbnails").css("cursor", "inherit");

	let animateThumbnails = getSetting("animate_thumbnails");
	if(animateThumbnails === null) {
		animateThumbnails = true;
		setSetting("animate_thumbnails", animateThumbnails);
	}
	$checkboxComponent.toggleClass("checked", animateThumbnails);

	$(document).on("click", "#animate_thumbnails_form", function(e) {
		$checkboxComponent.toggleClass("checked");
		setSetting("animate_thumbnails", $checkboxComponent.hasClass("checked"));

		e.preventDefault();
		e.stopPropagation();
	});

	$(document).on('mouseenter', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
		e.preventDefault();
		e.stopImmediatePropagation();

		if(!getSetting("animate_thumbnails")) {
			return;
		}

		clearIntervalEx(currentHoverInterval);
		updateRoomThumb($(this));
		currentHoverInterval = setInterval(() => {
			updateRoomThumb($(this));
		}, 100);
	});

	$(document).on('mouseleave', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
		e.preventDefault();
		e.stopImmediatePropagation();
		clearIntervalEx(currentHoverInterval);
	});
}

function updateRoomThumb($el) {
	// Stop CB script from executing something on image load
	$el[0].onload = null;

	let uname = $el.parent().data('room');
	//$el.attr('src', 'https://roomimg.stream.highwebmedia.com/minifwap/' + uname + '.jpg?' + Math.random());

	let reqTime = Date.now();
	let req = new XMLHttpRequest();
	req.timeout = 2000;
	req.responseType = 'arraybuffer';
	req.addEventListener('load', function() {
		if(reqTime < lastLoadedThumbReqTime) {
			return;
		}

		lastLoadedThumbReqTime = reqTime;
		$el.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(req.response))));
	});

	req.open('GET', 'https://roomimg.stream.highwebmedia.com/minifwap/' + uname + '.jpg?' + Math.random());
	req.send();
}

document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';

document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';

function enhanceRoom(ajaxTransition=false) {
	unregisterMenuCommand(getLocale('get_vsurl', 'Get video source URL'));
	$('.cb-enh-row').remove();
	clearIntervalEx(gIntvCheckIfRoomIsOnline);

	if(!ajaxTransition) {
		let cFunc = function() {
			if(!gCurrentBroadcaster) {
				return;
			}

			xmlhttpRequest({
				method: 'GET',
				url: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/api/chatvideocontext/' + gCurrentBroadcaster + '/',
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
					'Referer': 'https://chaturbate.com/' + gCurrentBroadcaster + '/',
				},
				timeout: 60*1*1000,
				onload: function(resp) {
					let data;
					try {
						data = JSON.parse(resp.responseText);
					}
					catch(SyntaxError) {
						return;
					}

					if(!('hls_source' in data) || data['hls_source'] === '') {
						alert(getLocale('err_vurl', 'ERROR: No video URL.'));
						return;
					}

					setClipboard(data['hls_source'], 'text');
					alert(data['hls_source'] + '\n\n(' + getLocale('copied_to_clipboard', 'copied to clipboard') + ')');
				}
			});
		}
		registerMenuCommand(getLocale('get_vsurl', 'Get video source URL'), cFunc, 'g');

		if(getSetting('hide-vid-logo')) {
			addStyle('#VideoPanel .cbLogo { display: none; }');
		}

		clearIntervalEx(intvWaitVideo);
		intvWaitVideo = setInterval(function() {
			let $vid = getVideo();
			if($vid.length === 0) {
				return;
			}

			clearIntervalEx(intvWaitVideo);

			// Add Picture in Picture button to the player
			if(unsafeWindow.videoJsPlayer && typeof $vid[0].requestPictureInPicture !== 'undefined') {
				let PictureInPictureToggle = videojs.getComponent('pictureInPictureToggle');
				if(PictureInPictureToggle) {
					let pictureInPictureToggle = new PictureInPictureToggle(unsafeWindow.videoJsPlayer, {});
					unsafeWindow.videoJsPlayer.getChild('ControlBar').addChild(pictureInPictureToggle);
				}
			}

			// Add video resolution to the player
			$vid[0].addEventListener('resize', function(e) {
				$(".vjs-live-display").text('LIVE - ' + e.target.videoWidth + ' x ' + e.target.videoHeight);
			});
		});

		initSupportInfo();
	}

	if(unsafeWindow.initialRoomDossier === '') {
		// initialRoomDossier is set but is empty
		// room might be banned or blocked for user
		enhanceInaccessibleRoom();
		return;
	}

	let intv = setInterval(function() {
		if($('video.vjs-tech').length === 0) {
			return;
		}

		// Make clicking on live video feed don't pause it anymore
		$('video.vjs-tech').on('pause', function() {
			makeVideoPlay($('video.vjs-tech')[0]);
		});
		clearIntervalEx(intv);

		// Watch for AJAX page transition
		let currentUsername = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
		let pageTransitionIntv = setInterval(function() {
			let uname = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
			if(currentUsername != uname) {
				clearIntervalEx(pageTransitionIntv);
				enhanceRoom(true);
				currentUsername = uname;
			}
		}, 25);
	}, 25);

	let userData;
	let broadcasterName;
	if(!ajaxTransition) {
		userData = JSON.parse(unsafeWindow.initialRoomDossier);
		broadcasterName = userData.broadcaster_username;

		if(userData['room_status'] !== 'offline') {
			initBelowVideoButtons();
		}

		// Auto refresh page if offline room became online
		if(userData['room_status'] === 'offline') {
			gIntvCheckIfRoomIsOnline = setInterval(
			function() {
				$.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
					if(data['room_status'] !== 'offline') {
						clearIntervalEx(gIntvCheckIfRoomIsOnline);
						window.location.reload();
						return;
					}
				});
			}, 2000);
		}
	}
	else {
		broadcasterName = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
		stopRecording();
	}
	gCurrentBroadcaster = broadcasterName;

	let lang = getSiteLang();
	let intervalId = setInterval(() => {
		let $table = $('.BioContents > div > table');
		if($table.length === 0) {
			return;
		}
		clearIntervalEx(intervalId);

		let $offlineNotice = $('.offlineRoomNotice');
		if($offlineNotice.length > 0) {
			// Add offline avatar
			insertRoomAv($offlineNotice, broadcasterName);

			// Add last snapshot if possible
			fetchRoomSnapshot(broadcasterName, function(resp) {
				let $img = $('<img>');
				$img.addClass('cb-enh-offline-snapshot');
				$img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
				$offlineNotice.append($img);
			});
		}

		let $divSchedule = addBioRow('Schedule', false, '<div id="cb-enh-iframe"></div>');
		if(userData && userData.room_status === 'offline') {
			addBioRow('Last Subject', true, userData.room_title);
		}
		let $divRegion = addBioRow('Region', false, '<a href=""></a>');
		let $divOnlineFor = addBioRow('Online For', false);

		xmlhttpRequest({
			method: 'GET',
			url: 'https://camschedule.com/api/room/' + broadcasterName + '?lang=' + lang,
			timeout: 60*2*1000,
			onload: function(resp) {
				let data;
				try {
					data = JSON.parse(resp.responseText);
				}
				catch(SyntaxError) {
					return;
				}

				// Populate "region" row
				if(data['region'] !== '') {
					let href = '';
					if(data['region_id'] == 0) {
						href = '/asian-cams/';
					}
					else if(data['region_id'] == 1) {
						href = '/euro-russian-cams/';
					}
					else if(data['region_id'] == 2) {
						href = '/north-american-cams/';
					}
					else if(data['region_id'] == 3) {
						href = '/south-american-cams/';
					}
					else if(data['region_id'] == 4) {
						href = '/other-region-cams/';
					}

					let elA = $divRegion.children('.cb-enh-row-value').children('a')[0];
					elA.innerHTML = data['region'];
					elA.href = href;
					$divRegion.show();
				}

				// Populate "online for" row
				if(data['online_for'] && data['online_for'] !== '') {
					$divOnlineFor.children('.cb-enh-row-value')[0].innerHTML = data['online_for'];
					$divOnlineFor.show();
				}

				// Add schedule
				if(data['has_schedule']) {
					let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
					let iframeWrapper = document.getElementById('cb-enh-iframe');
					addElement(iframeWrapper, 'iframe', {
						src: 'https://camschedule.com/embed/schedule/' + broadcasterName + '?dark=' + darkMode + '&lang=' + lang,
						class: 'cb-enh-schedule-frame'
					});
					$divSchedule.show();
				}

				localizeStrings();
			}
		});

		// Add fanclub cost
		let $fanclubBtn = $('.fanclubButton');
		if($fanclubBtn.length > 0) {
			$.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
				if(!data['performer_has_fanclub'] || data['fan_club_cost'] === null || $fanclubBtn.length === 0) {
					return;
				}

				let $a = $fanclubBtn.find('span a');
				if($a.length === 0) {
					return;
				}

				$a.text( $a.text() + ' (' + data['fan_club_cost'] + '/m)' );
			});
		}

		initMultiViewUIRoom();
	}, 500);

	// Insert model avatar on private etc. video board
	clearIntervalEx(intvUpdateAvatarInPrivBoard);
	intvUpdateAvatarInPrivBoard = setInterval(() => {
		let $el = $('#VideoPanel div[ts]').eq(0);
		if($el.data('cb-enh-av')) {
			return;
		}

		let $div = $el.find('div:first-child').eq(0);
		if($div.length === 0) {
			return;
		}

		$el.data('cb-enh-av', true);
		let $avDiv = insertRoomAv($div, broadcasterName);
		$avDiv.css('margin', '0 auto');
		$avDiv.css('margin-bottom', '10px');
	}, 500);
}

function initBelowVideoButtons() {
	// Implement live video stream controls
	// Add video controls button
	let intvAddVideoControlsBtn = setInterval(() => {
		// Wait until interface loads
		if($('#satisfactionScore').length === 0) {
			return;
		}
		clearIntervalEx(intvAddVideoControlsBtn);

		let $videoControlsModal = $('<div id="cb-enh-video-controls-modal" class="whiteModal cb-enh-tab-bar-modal noselect"><div class="cb-enh-tab-bar-modal-arrow-down"></div><span id="cb-enh-video-controls-modal-content"></span></div>');
		$(".tabBar").prepend($videoControlsModal);
		$("#cb-enh-video-controls-modal-content").html(videoControlsContentHTML);
		localizeStringsNextTick();

		// Also add screenshot button
		$('<div id="cb-enh-video-controls-screenshot" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="screenshot">Screenshot</div>').insertAfter("#satisfactionScore");

		// Also add recording button
		$('<div id="cb-enh-video-controls-record" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="start_rec">Start recording</div>').insertAfter("#satisfactionScore");

		// Add video controls button
		$('<div id="cb-enh-video-controls-btn" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="vid_controls">Video Controls</div>').insertAfter("#satisfactionScore");
	}, 100);

	$(document).on("click", "#cb-enh-video-controls-btn", function(e) {
		e.preventDefault();
		e.stopPropagation();

		if(gVideoControlsModalShown) {
			setVideoControlsVisible(false);
			return;
		}

		setVideoControlsVisible(true);

		$("#cb-enh-video-controls-modal-show-logo")[0].checked = !getSetting('hide-vid-logo');
		$("#cb-enh-video-controls-use-intrf-def")[0].checked = getSetting('use-intrf-def');

		let $btn = $(this);
		let $modal = $("#cb-enh-video-controls-modal");

		let off = $btn.offset();
		off.top -= $btn.outerHeight() + $modal.outerHeight();
		off.left -= $($modal).outerWidth() / 2;
		$modal.offset(off);

		let height = $modal.outerHeight();
		let $arrow = $("#cb-enh-video-controls-modal .cb-enh-tab-bar-modal-arrow-down");
		$arrow.offset({
			left: $btn.offset().left + $btn.outerWidth() / 2 - $arrow.outerWidth() / 2,
			top: off.top + height
		});
	});

	$(window).click(function(e) {
		if(gVideoControlsModalShown && $(e.target).closest('#cb-enh-video-controls-modal').length === 0) {
			setVideoControlsVisible(false);
		}
	});

	$(document).on("click", "#cb-enh-video-controls-modal-show-logo", function() {
		let show = $(this)[0].checked;
		$("#VideoPanel .cbLogo").toggle(show);
		setSetting('hide-vid-logo', !show);
	});

	$(document).on("click", "#cb-enh-video-controls-use-intrf-def", function() {
		let enabled = $(this)[0].checked;
		setSetting('use-intrf-def', enabled);
	});

	$(document).on("click", "#cb-enh-video-controls-modal-mirror-vid", function() {
		$(".videoPlayerDiv video").toggleClass("cb-enh-video-mirrored", $(this)[0].checked);
	});

	$(document).on("click", "#cb-enh-video-controls-modal-invert-vid", function() {
		$(".videoPlayerDiv video").toggleClass("cb-enh-video-inverted", $(this)[0].checked);
	});

	$(document).on("input", "#cb-enh-video-controls-modal-brightness", function() {
		vidFilters[0] = "brightness(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-contrast", function() {
		vidFilters[1] = "contrast(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-saturation", function() {
		vidFilters[2] = "saturate(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-sepia", function() {
		vidFilters[3] = "sepia(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-hue", function() {
		vidFilters[4] = "hue-rotate(" + $(this).val() + "deg)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-blur", function() {
		vidFilters[5] = "blur(" + $(this).val() + "px)";
		updateVideoFilters();
	});

	$(document).on("click", "#cb-enh-video-controls-modal-reset", function() {
		vidFilters = [];
		updateVideoFilters();

		$(".videoPlayerDiv video").removeClass("cb-enh-video-mirrored");
		$(".videoPlayerDiv video").removeClass("cb-enh-video-inverted");

		$("#cb-enh-video-controls-modal-mirror-vid")[0].checked = false;
		$("#cb-enh-video-controls-modal-invert-vid")[0].checked = false;

		$(".cb-enh-vid-control-slider").each(function() {
			$(this).val($(this).attr("data-default"));
		});
	});

	// Screenshot button
	$(document).on("click", "#cb-enh-video-controls-screenshot", function(e) {
		e.preventDefault();
		e.stopPropagation();
		captureScreenshot();
	});

	// Record button
	$(document).on("click", "#cb-enh-video-controls-record", function(e) {
		e.preventDefault();
		e.stopPropagation();

		if(!gRecording) {
			startRecording();
		}
		else {
			stopRecording();
		}
	});

	// Fix for chat window not closing when mouse leaves it
	let insideChat = false;
	document.addEventListener('mouseover',
		function(e) {
			if($('.draggableCanvasChatWindow').find(e.target).length > 0) {
				insideChat = true;
			}
		}
	);

	document.addEventListener('mouseout',
		function(e) {
			let $input = $('.fullvideoInputFieldChat');
			if(insideChat && $input.length > 0 && document.activeElement !== $input[0] && $('.draggableCanvasChatWindow > div[ts="n"]').find(e.target).length > 0) {
				insideChat = false;
				resetTheaterModeChatVisibilityState();
			}
		}
	);

	document.addEventListener('click',
		function(e) {
			let $chatBtn = $('#TheaterModePlayer #chat-btn');
			if($chatBtn.length > 0 && e.target === $chatBtn[0]) {
				resetTheaterModeChatVisibilityState();
			}
		}
	, true);
}

function resetTheaterModeChatVisibilityState() {
	setTimeout(
		function() {
			if($('#TheaterModeRoomContents').length > 0) {
				$('#TheaterModeRoomContents')[0].click();
			}
		},
		1
	);
}

function setVideoControlsVisible(visible) {
	$("#cb-enh-video-controls-modal").toggle(visible);
	gVideoControlsModalShown = visible;
	localizeStringsNextTick()
}

// Support info - start
function initSupportInfo() {
	$(document).on('click', '.cb-enh-acc-info-inner', function() {
		loutrreg(gCurrentBroadcaster);
	});

	let intvWaitBuyBox = setInterval(() => {
		let $bBox = $('div[data-paction=CurrentShowBuyBox]').parent();
		if($bBox.length === 0) {
			return;
		}

		clearIntervalEx(intvWaitBuyBox);
		if($('#cb-enh-acc-info').length > 0) {
			return;
		}

		let userType = getUserType();
		let msg = getSupportMessage(userType);

		if(msg === null) {
			return;
		}

		let bBoxHtml = `
<div id="cb-enh-acc-info" class="noselect">
	<div class="cb-enh-acc-info-outer">
		<div class="cb-enh-acc-info-inner" data-utype="${userType}">
			${msg}
		</div>
	</div>
</div>
		`;
		$bBox.append(bBoxHtml);
	}, 50);

	let intvUpdateAccInfoSize = setInterval(function() {
		if($('#VideoPanel').length === 0) {
			return;
		}

		let $accInfo = $('#cb-enh-acc-info');
		if($accInfo.length === 0) {
			return;
		}

		if($('#VideoPanel').innerWidth() >= 1000) {
			$accInfo.css('font-size', '15px');
			$accInfo.css('width', '500px');
			$accInfo.show();
			return;
		}

		let $parent = $accInfo.parent();
		let w1 = $parent.innerWidth();
		let chw1 = $parent.children().eq(0).innerWidth();
		let chw2 = $parent.children().eq(1).innerWidth();
		let w2 = w1 - chw1 - chw2;

		if(w2 <= 30) {
			$accInfo.hide();
			return;
		}

		$accInfo.css('width', (w2 - 10) + 'px');
		$accInfo.css('font-size', '12px');
		$accInfo.show();
	}, 200);
}

function isLogged() {
	return $('.user_information_header_username').length !== 0;
}

function getUserType() {
	let userType = 0;
	if(isLogged()) {
		userType = 2;
		let gaq = $("#gaq").html();
		let p = atob(acre1 + acre2);
		let re = new RegExp(p);
		let m = gaq.match(re);
		if(m) {
			let afn = m[1];
			if(afn !== atob(rev('gYtZWMwN3N'))) {
				userType = 1;
			}
		}
	}

	return userType;
}

function getSupportMessage(userType) {
	if(userType === 0) {
		return '<b>Chaturbate Enhancer <span class="ce-loc" data-ce-loc="msg">message</msg></b>: <span class="ce-loc" data-ce-loc="please_support">Please support</span> Chaturbate Enhancer <span class="ce-loc" data-ce-loc="ac_msg_dev">development by</span> <u class="ce-loc" data-ce-loc="ac_msg_0">creating free Chaturbate account</u>. <span class="ce-loc" data-ce-loc="thanks">Thank you</span> ❤️.';
	}
	else if(userType === 1) {
		return '<b>Chaturbate Enhancer <span class="ce-loc" data-ce-loc="msg">message</span></b>: <span class="ce-loc" data-ce-loc="please_support">Please support</span> Chaturbate Enhancer <span class="ce-loc" data-ce-loc="ac_msg_dev">development by</span> <u class="ce-loc" data-ce-loc="ac_msg_1">creating new Chaturbate account</u>. <span class="ce-loc" data-ce-loc="thanks">Thank you</span> ❤️.';
	}
	
	return null;
}

function loutrreg() {
	setSetting('reg-redir', 1);
	setSetting('reg-redir-room', gCurrentBroadcaster);

	if(isLogged()) {
		$('a[href="/auth/logout/"]').click();
		$(".modalAlert .dialog .accept").click();
	}
	else {
		regRedir(gCurrentBroadcaster);
	}
}

function regRedir(room) {
	let rurl = '/accounts/register/';
	if(room) {
		// rurl += '?room=' + room;
		rurl = '/' + room + '/?join_overlay=1&disable_sound=1';
	}
	setSetting('reg-redir', 2);
	setSetting('reg-redir-room', room);
	window.location.href = rurl;
}
// Support info - end

function getVideo() {
	return $(".videoPlayerDiv video");
}

function isVideoPlaying(vid) {
	return !vid.paused && !vid.ended && vid.readyState > 2;
}

function playIgnoreErrors(vid) {
	try {
		vid.play();
	} catch(err) {};
}

function makeVideoPlay(vid) {
	playIgnoreErrors(vid);
	setTimeout(function() { playIgnoreErrors(vid) }, 1);
	setTimeout(function() { playIgnoreErrors(vid) }, 10);
	setTimeout(function() { playIgnoreErrors(vid) }, 25);
	setTimeout(function() { playIgnoreErrors(vid) }, 50);
	setTimeout(function() { playIgnoreErrors(vid) }, 100);
}

function setSetting(name, value) {
	gSettings[name] = value;
	localStorage.setItem('cb-enh-settings', JSON.stringify(gSettings));
}

function getSetting(name, value) {
	if(typeof gSettings[name] !== 'undefined') {
		return gSettings[name];
	}
	return null;
}

function loadSettings() {
	let settings = localStorage.getItem('cb-enh-settings');
	if(settings === null) {
		settings = {};
	}
	else {
		settings = JSON.parse(settings);
	}

	gSettings = settings;
}

let vidFilters = [];
function updateVideoFilters() {
	getVideo().css("filter", vidFilters.join(" "));
}

let acre1 = 'Z2FcKCdzZXQnLCAnZGltZW5zaW';

function enhanceInaccessibleRoom() {
	let lang = getSiteLang();

	// Display video of inaccessible room
	let $baseRoomContentDiv =  $("div.BaseRoomContents div")
	if($baseRoomContentDiv.length === 0) {
		return;
	}

	$baseRoomContentDiv = $baseRoomContentDiv.eq(0);
	if($baseRoomContentDiv.text().indexOf("Access denied") !== 0) {
		return;
	}

	$baseRoomContentDiv.append('<br><span class="ce-loc" data-ce-loc="try_load">Chaturbate Enhancer will try to load video and bio of this room.</span><br><br>');

	let $langForm = $("form[action='/set_language/'] input[name='next']");
	if($langForm.length === 0) {
		return;
	}
	let username = $("form[action='/set_language/'] input[name='next']")[0].value.slice(1, -1);
	gCurrentBroadcaster = username;

	addStyle(`
		.BaseRoomContents div {
			font-size: 14px !important;
			font-family: UbuntuMedium, Arial, Helvetica, sans-serif;
			font-weight: normal;
		}

		.darkmode .BaseRoomContents {
			border-color: transparent !important;
			background-color: #202c39 !important;
		}

		.ce-row-1 {
			color: #0a5a83;
		}

		.darkmode .ce-row-1 {
			color: white;
		}
	`);

	let $upperHolder = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder);

	let $videoHolder = $('<div></div>');
	$baseRoomContentDiv.append($videoHolder);

	let $upperHolder2 = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder2);

	let $upperHolder3 = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder3);

	let $infoHolder = $('<div></div>');
	$baseRoomContentDiv.append($infoHolder);

	let $infoHolder2 = $('<div></div>');
	$baseRoomContentDiv.append($infoHolder2);

	let $scheduleHolder = $('<div></div>');
	$baseRoomContentDiv.append($scheduleHolder);

	let isOnline = false;

	// video type
	xmlhttpRequest({
		method: 'GET',
		url: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/api/chatvideocontext/' + username + '/',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
			'Referer': 'https://chaturbate.com/' + username + '/',
		},
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			if($baseRoomContentDiv.length === 0) {
				return;
			}

			let playVideo = true;
			if(data['room_status'] !== 'public') {
				let $avDiv = $("<div></div>");
				$upperHolder.append($avDiv);
				insertRoomAv($avDiv, username);
				$upperHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="room_status">Room status is</span>: <span class="ce-loc" data-ce-loc="status_' + data['room_status'] + '">' + data['room_status'] + '</span><br>');
				playVideo = false;
			}
			else {
				isOnline = true;
				$("#cb-enh-inac-load-chat").show()
			}

			if(data['hls_source'] === '') {
				playVideo = false;
			}

			if(data['room_title']) {
				let $span;
				if(data['room_status'] !== 'offline') {
					$span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="subject">Subject</span>: <span></span></span>');
				}
				else {
					$span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="last_subject">Last Subject</span>: <span></span></span>');
				}
				$span.find('span').eq(1).text(data['room_title']);
				$upperHolder.append($span);
				$upperHolder.append('<br><br>');
			}

			if(playVideo) {
				let $video = $('<video controls autoplay muted data-listener-count-webkitendfullscreen="1" class="vjs-tech cb-enh-video" id="vjs_video_3_html5_api" tabindex="-1" role="application" poster="https://cbjpeg.stream.highwebmedia.com/stream?room=' + username + '&f=' + Math.random() + '"></video>');
				$videoHolder.append($video);

				$video.on('pause', function() {
					makeVideoPlay($video[0]);
				});

				$video.on('click', function() {
					makeVideoPlay($video[0]);
				});

				let hls = new Hls();
				hls.loadSource(data['hls_source']);
				hls.attachMedia($video[0]);
			}

			if(data['age']) {
				$upperHolder3.append('<br><br><span class="ce-loc ce-row-1" data-ce-loc="age">Age</span>: ' + data['age'] + '<br>');
			}

			if(data['broadcaster_gender']) {
				$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="gender">Gender</span>: <span class="ce-loc" data-ce-loc="gender_' + data['broadcaster_gender'][0] + '">'  + data['broadcaster_gender'] + '</span><br>');
			}

			if(data['num_viewers']) {
				if(data['room_status'] !== 'offline') {
					$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="viewers">Viewers</span>: ' + data['num_viewers'] + '<br>');
				}
				else {
					$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="last_viewers">Last Viewers</span>: ' + data['num_viewers'] + '<br>');
				}
			}

			if(data['performer_has_fanclub']) {
				$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="has_fanclub">Has Fanclub</span>: <span class="ce-loc" data-ce-loc="yes">Yes</span><br>');
			}
			else {
				$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="has_fanclub">Has Fanclub</span>: <span class="ce-loc" data-ce-loc="no">No</span><br>');
			}

			if('satisfaction_score' in data) {
				let sc = data['satisfaction_score'];
				if('percent' in sc && 'up_votes' in sc && 'down_votes' in sc) {
					$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="satisfaction_score">Satisfaction Score</span>: ' + sc['percent'] + '% (' + sc['up_votes'] + ' <span class="ce-loc" data-ce-loc="up">up</span>, ' + sc['down_votes'] + ' <span class="ce-loc" data-ce-loc="down">down</span>)<br>');
				}
			}

			localizeStrings();
		},
		onerror: function() {
			$upperHolder.append('<br>' + getLocale('err_vid', 'ERROR: Unable to load video.'));
		}
	});

	// Fetch info about room
	xmlhttpRequest({
		method: 'GET',
		url: 'https://camschedule.com/api/room/' + username + '?lang=' + lang,
		timeout: 60*2*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			let $loadChatHref = $('<a id="cb-enh-inac-load-chat" style="display:none;" class="ce-loc" data-ce-loc="try_load_chat">Click here to try to load chat.</a><br>');
			$upperHolder2.append($loadChatHref);
			if(isOnline) {
				$loadChatHref.show();
			}

			$loadChatHref.on('click', function(e) {
				$loadChatHref.hide();

				e.preventDefault();
				e.stopPropagation();

				$videoHolder.empty();
				addElement($videoHolder[0], 'iframe', {
					src: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/embed/' + username + '/',
					class: 'cb-enh-chat-frame'
				});
			});

			// Populate "region" row
			if(data['region'] !== '') {
				let href = '';
				if(data['region_id'] == 0) {
					href = '/asian-cams/';
				}
				else if(data['region_id'] == 1) {
					href = '/euro-russian-cams/';
				}
				else if(data['region_id'] == 2) {
					href = '/north-american-cams/';
				}
				else if(data['region_id'] == 3) {
					href = '/south-american-cams/';
				}
				else if(data['region_id'] == 4) {
					href = '/other-region-cams/';
				}
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="region">Region</span>: <a href="' + href + '">' + data['region'] + '</a><br>');
			}

			// Populate "online for" row
			if(data['online_for'] && data['online_for'] !== '') {
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="online_for">Online For</span>: ' + data['online_for'] + '<br>');
			}
			else if(data['last_online_f'] && data['last_online_f'] !== '') {
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="last_online">Last Online</span>: ' + data['last_online_f'] + '<br>');
			}

			let info = {
				'real_name': 'Real Name',
				'birthday': 'Birthday',
				'followers_f': 'Followers',
				'location': 'Location',
				'languages': 'Languages',
				'smoke_drink': 'Smoke / Drink',
				'body_type': 'Body Type',
				'body_decorations': 'Body Decorations',
			};

			Object.keys(info).forEach(function(k) {
				let v = info[k];
				if(data[k]) {
					let $span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="' + k + '">' + v + '</span>: <span></span></span>');
					$span.find('span').eq(1).text(data[k]);
					$infoHolder.append($span);
					$infoHolder.append('<br>');
				}
			});

			// Add schedule
			if(data['has_schedule']) {
				$scheduleHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="schedule">Schedule</span>: <br>');
				let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
				addElement($scheduleHolder[0], 'iframe', {
					src: 'https://camschedule.com/embed/schedule/' + username + '?dark=' + darkMode + '&lang=' + lang,
					class: 'cb-enh-schedule-frame'
				});
			}

			localizeStrings();
		}
	});
}

function enhancePasswordedRoom() {
	// @todo
}

function addBioRow(name, visible = true, value = '') {
	let loc = name.replace(' ', '_').toLowerCase();
	let $el = $('<tr class="cb-enh-row" style="' + (visible ? '' : 'display: none; ') + 'font-size: 14px; font-weight: normal; line-height: 15px; vertical-align: top; text-align: left;"><td class="label" style="padding-bottom: 9px; font-family: UbuntuMedium, Arial, Helvetica, sans-serif; height: 16px;"><span><span class="ce-loc" data-ce-loc="' + loc + '">' + name + '</span>:</span></td><td class="contentText cb-enh-row-value" style="font-size: 14px; line-height: 16px; font-family: UbuntuRegular, Arial, Helvetica, sans-serif;">' + value + '</td></tr>');

	let $psContainers = $('.BioContents > div > table > .psContainer');
	let $smContainers = $('.BioContents > div > table > .smContainer');

	if($psContainers.length > 0) {
		$psContainers.last().after($el);
	}
	else if($smContainers.length > 0) {
		$smContainers.last().after($el);
	}
	else {
		$('.BioContents > div > table > tr').slice(-2).first().after($el);
	}

	return $el;
}

let acre2 = '9uNCcsICcoW2EtekEtWjAtOS1dKyknXCk7';

function insertRoomAv($div, username) {
	let $avDiv = $('<div class="cb-enh-avatar"></div>');
	$div.prepend($avDiv);
	addElement($avDiv[0], 'img', {
		src: 'https://camschedule.com/assets/img/avatar.png',
		alt: '',
		onload: 'this.style.opacity=1'
	});

	addElement($avDiv[0], 'img', {
		src: 'https://cb-enh-thumb.improper.dev/av/' + username + '.jpg',
		alt: '',
		onload: 'this.style.opacity=1'
	});
	return $avDiv;
}

document.addEventListener("keydown",
function(e) {
	if(`${e.code}` === 'KeyX' && e.ctrlKey) {
		if(document.activeElement) {
			if(document.activeElement.type === 'input' || document.activeElement.type === 'textarea' || document.activeElement.hasAttribute('contenteditable')) {
				return;
			}
		}

		captureScreenshot();
	}
}
);

// Screenshoting start
function captureScreenshot() {
	if(gCapturingScreenshot) {
		return;
	}

	let $vid = getVideo();
	if($vid.length === 0) {
		return;
	}

	gCapturingScreenshot = true;
	let canvas = captureVideoFrame($vid[0]);
	gCapturingScreenshot = false;
	if(!canvas) {
		alert(getLocale('err_ss', 'ERROR: Failed to capture screenshot!'));
		return;
	}

	let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
	let link = document.createElement('a');
	let date = new Date();
	link.download = getFileName(username, '.png', date);
	link.href = canvas.toDataURL();
	link.click();
}

function captureVideoFrame(video) {
	let canvas = document.createElement("canvas");
	canvas.width = video.videoWidth;
	canvas.height = video.videoHeight;
	canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
	return canvas;
}
// Screenshoting end

// Recording start
function startRecording() {
	if(gRecording) {
		return;
	}

	let $vid = getVideo();
	if($vid.length === 0) {
		alert(getLocale('err_no_rec_video', 'ERROR: There is no video to record!'));
		return;
	}

	$("#cb-enh-video-controls-record").find("span").text(getLocale("stop_rec", "Stop recording"));
	$("#cb-enh-video-controls-record").find("span").data("ce-loc", "stop_rec");
	$("#cb-enh-video-controls-record").addClass("cb-enh-active");
	gRecording = true;
	gRecordingCanceledByUser = false;

	let lengthInMS = 1000*60*10;
	let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
	console.log('Starting recording', username);

	let stream = captureStream($vid[0]);
	if(stream === 'err-no-func') {
		gRecording = false;
		alert(getLocale('err_no_rec_support', 'ERROR: Your browser does not seem to support recording. Please install latest version of modern browser. If you think that\'s mistake, please fill an issue report. Thank you!'));
		return;
	}

	$vid[0].play();

	if($vid[0].muted) {
		stream.getAudioTracks().forEach( t => stream.removeTrack( t ) );
	}

	let date = new Date();
	startRecordingStream(stream, lengthInMS).then((recordedChunks) => {
		let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
		console.log(
			`Successfully recorded ${recordedBlob.size} bytes of ${recordedBlob.type} media.`
		);

		let link = document.createElement('a');
		link.download = getFileName(username, '.webm', date);
		link.href = URL.createObjectURL(recordedBlob);
		link.click();

		gRecordingStream = null;
		gRecording = false;

		if(!gRecordingCanceledByUser) {
			// Start next recording
			startRecording();
			return;
		}

		gRecordingCanceledByUser = false;

		// UI
		$("#cb-enh-video-controls-record").find("span").text(getLocale("start_rec", "Start recording"));
		$("#cb-enh-video-controls-record").find("span").data("ce-loc", "start_rec");
		$("#cb-enh-video-controls-record").removeClass("cb-enh-active");
	});
}

function wait(delayInMS) {
	return new Promise((resolve) => setTimeout(resolve, delayInMS));
}

function startRecordingStream(stream, lengthInMS) {
	let options = {
		mimeType: 'video/webm'
	};

	let recorder = new MediaRecorder(stream, options);
	let data = [];

	recorder.ondataavailable = (event) => { data.push(event.data); }
	recorder.start(250);
	gRecordingStream = stream;

	let stopped = new Promise((resolve, reject) => {
		recorder.onstop = (event) => { resolve(event.name); }
		recorder.onerror = (event) => { reject(event.name); }
	});

	let recorded = wait(lengthInMS).then(() => {
		if(recorder.state === "recording") {
			recorder.stop();
		}
	});

	return Promise.any([stopped, recorded]).then(() => data);
}

function stopRecordingStream(stream) {
	stream.getTracks().forEach((track) => track.stop());
}

function stopRecording() {
	if(!gRecording || !gRecordingStream) {
		return;
	}

	gRecordingCanceledByUser = true;
	stopRecordingStream(gRecordingStream);
}
// Recording end

// enhanceFollowedList
function updateRoomListStatuses() {
	$('.room_list_room').each(function() {
		// Room name
		let room = $(this).find('a').eq(0).data('room');

		let _this = this;
		$.getJSON('https://chaturbate.com/api/biocontext/' + room + '/', function(data) {
			if(data['room_status'] === 'public') {
				let $label = $(_this).find('.thumbnail_label');
				if($label.length === 0) {
					$label = $(_this).find('.thumbnail_label_featured');
				}

				if(!$label.hasClass('thumbnail_label_c_new') && !$label.hasClass('thumbnail_label_c_gaming')) {
					$label.remove();
				}
				return;
			}

			$(_this).find('.thumbnail_label').remove();
			$(_this).find('.thumbnail_label_featured').remove();

			let $label = $('<div class="thumbnail_label ce-loc"></div>');
			$label.text( getLocale('status_' + data['room_status'], data['room_status']) );

			if(data['room_status'] === 'private' || data['room_status'] === 'group' || data['room_status'] === 'hidden') {
				$label.addClass('thumbnail_label_c_private_show');
			}
			else if(data['room_status'] === 'offline') {
				$label.addClass('thumbnail_label_offline');
			}
			else {
				$label.addClass('thumbnail_label_c');
			}
			$(_this).append($label);
		});
	});
}

function enhanceFollowedList() {
	clearIntervalEx(intvUpdateFollowedList);
	updateRoomListStatuses();
	intvUpdateFollowedList = setInterval(function() {
		updateRoomListStatuses();
	}, 1000 * 30);
}
//

function setGridSize(width) {
	$('#room_list').css('grid-template-columns', 'repeat(auto-fill,minmax(' + width + 'px,max-content))');
}

// Set grid style by injecting new CSS style (so it can be done before page render)
function setGridSize2(width) {
	let gridStyle = `
	#room_list {
		grid-template-columns: repeat(auto-fill,minmax(` + width + `px,max-content));
	}
	`;
	addStyle(gridStyle);
}

function setMoreRoomsGridSize(width) {
	let gridStyle = `
	.MoreRooms .list .roomCard {
		width: ` + width + `px;
	}
	`;
	addStyle(gridStyle);
}

// grid-big.svg
let gGridIconSvgBig = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M140.001 543.307V236.001h307.306v307.306H140.001Zm0 372.692V608.693h307.306v307.306H140.001Zm372.692-372.692V236.001h307.306v307.306H512.693Zm0 372.692V608.693h307.306v307.306H512.693ZM185.385 497.924h216.539V281.385H185.385v216.539Zm372.691 0h216.539V281.385H558.076v216.539Zm0 372.691h216.539V654.076H558.076v216.539Zm-372.691 0h216.539V654.076H185.385v216.539Zm372.691-372.691Zm0 156.152Zm-156.152 0Zm0-156.152Z"/></svg>';

// grid-medium.svg
let gGridIconSvgMedium = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M197.694 915.999q-23.529 0-40.611-17.082-17.082-17.082-17.082-40.611V293.694q0-23.529 17.082-40.611 17.082-17.082 40.611-17.082h564.612q23.529 0 40.611 17.082 17.082 17.082 17.082 40.611v564.612q0 23.529-17.082 40.611-17.082 17.082-40.611 17.082H197.694Zm0-45.384h153.845V704.461H185.385v153.845q0 5.385 3.462 8.847 3.462 3.462 8.847 3.462Zm199.229 0h166.154V704.461H396.923v166.154Zm211.538 0h153.845q5.385 0 8.847-3.462 3.462-3.462 3.462-8.847V704.461H608.461v166.154ZM185.385 659.077h166.154V492.923H185.385v166.154Zm211.538 0h166.154V492.923H396.923v166.154Zm211.538 0h166.154V492.923H608.461v166.154ZM185.385 447.539h166.154V281.385H197.694q-5.385 0-8.847 3.462-3.462 3.462-3.462 8.847v153.845Zm211.538 0h166.154V281.385H396.923v166.154Zm211.538 0h166.154V293.694q0-5.385-3.462-8.847-3.462-3.462-8.847-3.462H608.461v166.154Z"/></svg>';

// grid-small.svg
let gGridIconSvgSmall = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M99.232 869.076V282.924h761.536v586.152H99.232Zm45.384-405.691h133.847V328.308H144.616v135.077Zm179.23 0h133.462V328.308H323.846v135.077Zm178.846 0h133.847V328.308H502.692v135.077Zm179.23 0h133.462V328.308H681.922v135.077Zm0 180.461h133.462V508.769H681.922v135.077Zm-179.23 0h133.847V508.769H502.692v135.077Zm-178.846 0h133.462V508.769H323.846v135.077Zm-45.383-135.077H144.616v135.077h133.847V508.769Zm403.459 314.923h133.462V689.23H681.922v134.462Zm-179.23 0h133.847V689.23H502.692v134.462Zm-178.846 0h133.462V689.23H323.846v134.462Zm-179.23 0h133.847V689.23H144.616v134.462Z"/></svg>';

// grid-small-x.svg
let gGridIconSvgSmallX = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M180 876h105V771H180v105Zm165 0h105V771H345v105Zm165 0h105V771H510v105Zm165 0h105V771H675v105ZM180 381h105V276H180v105Zm0 165h105V441H180v105Zm0 165h105V606H180v105Zm165-330h105V276H345v105Zm0 165h105V441H345v105Zm0 165h105V606H345v105Zm165-330h105V276H510v105Zm0 165h105V441H510v105Zm0 165h105V606H510v105Zm165-330h105V276H675v105Zm0 165h105V441H675v105Zm0 165h105V606H675v105ZM180 936q-24 0-42-18t-18-42V276q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Z"/></svg>';

function initGridSizeSelector() {
	if(gIsInMultiView) {
		return;
	}

	// Add grid size selector buttons
	let $topSelection = $('.top-section');
	if($topSelection.length === 0) {
		// return;
	}

	let $svg1 = $(gGridIconSvgBig);
	$svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
	$topSelection.append($svg1);
	$svg1.on('click', function() {
		setGridSize(280);
		setSetting('grid-size', 280);
	});

	let $svg2 = $(gGridIconSvgMedium);
	$svg2.addClass('cb-enh-grid-size-selector-img');
	$svg2.on('click', function() {
		setGridSize(250);
		setSetting('grid-size', 250);
	});
	$topSelection.append($svg2);

	let $svg3 = $(gGridIconSvgSmall);
	$svg3.addClass('cb-enh-grid-size-selector-img');
	$topSelection.append($svg3);
	$svg3.on('click', function() {
		setGridSize(220);
		setSetting('grid-size', 220);
	});

	let $svg4 = $(gGridIconSvgSmallX);
	$svg4.addClass('cb-enh-grid-size-selector-img');
	$topSelection.append($svg4);
	$svg4.on('click', function() {
		setGridSize(180);
		setSetting('grid-size', 180);
	});
	
	// Init "more rooms" grid size selector buttons
	let intvWaitMoreRooms = setInterval(function() {
		let $moreRooms = $('.MoreRooms');
		if($moreRooms.length === 0) {
			return;
		}

		clearIntervalEx(intvWaitMoreRooms);
		let $msvg4 = $(gGridIconSvgSmallX);
		$msvg4.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg4);
		$msvg4.on('click', function() {
			setMoreRoomsGridSize(180);
			setSetting('grid-more-size', 180);
		});

		let $msvg3 = $(gGridIconSvgSmall);
		$msvg3.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg3);
		$msvg3.on('click', function() {
			setMoreRoomsGridSize(210);
			setSetting('grid-more-size', 210);
		});

		let $msvg2 = $(gGridIconSvgMedium);
		$msvg2.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg2);
		$msvg2.on('click', function() {
			setMoreRoomsGridSize(260);
			setSetting('grid-more-size', 260);
		});

		let $msvg1 = $(gGridIconSvgBig);
		$msvg1.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img cb-enh-first');
		$moreRooms.prepend($msvg1);
		$msvg1.on('click', function() {
			setMoreRoomsGridSize(300);
			setSetting('grid-more-size', 300);
		});
	});
}

// MULTI VIEW START
let multiLinkHTML = `
<li style="display: block;">
<a href="/#multicam" target="_blank" rel="noopener" style="color: rgb(255, 255, 255); font: 400 13.999px ubuntumedium, Arial, Helvetica, sans-serif;">
MULTI CAM VIEWER
</a>
</li>
`;

let multiAddSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 96 960 960" width="20"><path d="M516 661.999h51.999v-132h132v-51.998h-132v-132H516v132H384v51.998h132v132Zm-201.692 134q-27.008 0-45.657-18.65-18.65-18.65-18.65-45.658V276.309q0-27.008 18.65-45.658 18.649-18.65 45.657-18.65h455.383q27.007 0 45.657 18.65 18.65 18.65 18.65 45.658v455.382q0 27.008-18.65 45.658-18.65 18.65-45.657 18.65H314.308Zm0-51.999h455.383q4.615 0 8.462-3.846 3.846-3.847 3.846-8.463V276.309q0-4.616-3.846-8.463-3.847-3.846-8.462-3.846H314.308q-4.616 0-8.462 3.846-3.847 3.847-3.847 8.463v455.382q0 4.616 3.847 8.463 3.846 3.846 8.462 3.846ZM190.309 919.997q-27.007 0-45.657-18.65-18.65-18.65-18.65-45.657V348.309h51.999V855.69q0 4.616 3.846 8.462 3.847 3.847 8.462 3.847h507.382v51.998H190.309ZM301.999 264v480-480Z"/></svg>';

function initMultiViewUIRoom() {
	let $linkContainer = $('.BaseRoomContents div.defaultColor.styledDiv');
	if($linkContainer.length === 0) {
		return;
	}

	$linkContainer = $linkContainer.eq(0);

	let $link = $('<span><span class="defaultColor" style="margin: 0px 4px;">|</span><span id="cb-enh-add-multi-link" class="link ce-loc" data-ce-loc="multi_add">Add to multi cam viewer<span></span>');
	$linkContainer.append($link);

	$(document).on('click', '#cb-enh-add-multi-link', function() {
		multiAddRoomToList(gCurrentBroadcaster);
	});
}

function initMultiViewUINavigation() {
	let $nav = $('#nav');
	if($nav.length === 0) {
		return;
	}

	let $multiLink = $(multiLinkHTML);
	$nav.append($multiLink);

	let $span = $('<span class="cb-enh-add-cam-icon"></span>');
	$span.addClass('cb-enh-add-cam-icon');
	// $span.addClass('ce-loc');
	// $span.data('ce-loc', 'multi_add');
	$span.prop('title', getLocale('multi_add', 'Add current room to multi cam viewer'));

	let $addIcon = $(multiAddSVG);
	$span.append($addIcon);
	$('#room_list .room_list_room .sub-info .cams').append($span);

	$(document).on('click', '.cb-enh-add-cam-icon', function() {
		let username = $(this).closest('.room_list_room').find('a').eq(0).data('room');
		multiAddRoomToList(username);
	});
}

let multiViewStyle = `
#cb-enh-multi-content h1 {
	font-size: 26px;
	margin-left: 10px;
}

.cb-enh-multi-cam {
	float: left;
	position: relative;

	width: 25%;
	overflow: hidden;
}

.cb-enh-multi-cam video {
	border-radius: unset;
	display: block;

	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	float: left;

	position: absolute;
	max-width: unset;
}

.cb-enh-multi-cam img {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	float: left;
}

.cb-enh-multi-cam .cb-enh-avatar {
	position: absolute;
	top: calc(50% - 150px/2);
	left: calc(50% - 150px/2);
	float: left;
}

.cb-enh-multi-drag-outline {
	opacity: 0;
	outline: 3px dashed #eb3400;
	outline-offset: -3px;
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	z-index: 10;
	pointer-events: none;
	transition: 0.2s opacity;
}

.cb-enh-multi-cam.cb-enh-multi-cam-ondragover .cb-enh-multi-drag-outline {
	opacity: 1;
}

.cb-enh-multi-offline img, cb-enh-multi-offline video {
	filter: grayscale(1);
}

#cb-enh-multi-content input[type="button"] {
	text-transform: uppercase;
	cursor: pointer;
}

#cb-enh-multi-content input[type="checkbox"], #cb-enh-multi-content label {
	cursor: pointer;
}

.cb-enh-multi-info {
	clear: both;
	margin: 10px;
}

.cb-enh-multi-info p {
	line-height: 4px;
}

#cb-enh-multi-support {
    clear: both;
    margin: 10px;
	margin-top: 15px;
    font-size: 18px;
    color: white;
    border: 1px solid #496b91;
    padding: 5px;
    border-radius: 4px;
    background: #012247;
	max-width: 750px;
    line-height: 26px;
	cursor: pointer;
}

#cb-enh-multi-share {
	width: 600px;
}

.cb-enh-multi-link {
	cursor: pointer;
}

.cb-enh-multi-username {
	position: absolute;

	bottom: 8px;
	left: 5px;
	font-size: 24px;

	opacity: 0;
	transition: opacity 0.25s;

	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

.cb-enh-multi-username a {
	color: white;
	text-decoration: none;
}

.cb-enh-multi-status {
	position: absolute;
	top: 4px;
	left: 4px;

	opacity: 0;
	transition: opacity 0.25s;

	pointer-events: none;

	color: white;
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

.cb-enh-multi-close {
	position: absolute;
	top: 0;
	right: 0;
	opacity: 0;
	transition: opacity 0.25s;
	cursor: pointer;
}

.cb-enh-multi-close svg path {
	fill: #e80000;
}

.cb-enh-multi-cam:hover .cb-enh-multi-username, .cb-enh-multi-cam:hover .cb-enh-multi-status, .cb-enh-multi-cam:hover .cb-enh-multi-close {
	opacity: 1;
}

.cb-enh-multi-cam-wrap2 {
	padding-top: 56.25%;
	background-color: #090909;
}

#cb-enh-multi-content h1 {
	float: left;
}

#cb-enh-multi-size-selector {
	float: right;
}

.cb-enh-multi-fullscreen {
	overflow: auto;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector {
	position: fixed;
	top: 2;
	right: 50px;
	z-index: 10;
}

.cb-enh-multi-fullscreen h1 {
	display: none;
}

/* hide scrollbar in fullscreen but make it working */
.cb-enh-multi-fullscreen::-webkit-scrollbar {
	display: none;
	overflow: hidden;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector {
	opacity: 0;
	transition: 0.25s opacity;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector:hover {
	opacity: 1;
}

.cb-enh-multi-fullscreen .cb-enh-grid-size-selector-img.cb-enh-first {
	margin-right: 0;
}
`;

let multiViewHTML = `
<div id="cb-enh-multi-content">
	<h1>Chaturbate Enhancer Multi Cam Viewer</h1>
	<div id="cb-enh-multi-size-selector"></div>
	<div style="clear:both;"></div>

	<div id="cb-enh-multi-cams">
	</div>

	<div class="cb-enh-multi-info">
		<br>

		<input type="button" class="ce-loc" id="cb-enh-multi-btn-enter-fullscreen" value="ENTER FULLSCREEN" data-ce-loc="multi_btn_enter_fscreen">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-enter-fullscreen-mini" value="ENTER SEMI FULLSCREEN (WITH ADDRESS BAR)" data-ce-loc="multi_btn_enter_fscreen_mini">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-mute-all" value="MUTE ALL" data-ce-loc="multi_btn_mute_all">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-remove-offline" value="REMOVE OFFLINE CAMS" data-ce-loc="multi_btn_remove_offline">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-remove-all" value="REMOVE ALL CAMS" data-ce-loc="multi_btn_remove_all">
		
		<br>
		<br>

		<input type="checkbox" id="cb-enh-multi-btn-auto-hide">
		<label for="cb-enh-multi-btn-auto-hide" class="noselect ce-loc" data-ce-loc="multi_auto_hide_offline">Hide offline, private etc. cams</label>

		<br>

		<input type="checkbox" id="cb-enh-multi-btn-auto-remove">
		<label for="cb-enh-multi-btn-auto-remove" class="noselect ce-loc" data-ce-loc="multi_auto_remove_offline">Automatically remove offline cams</label>

		<br>
		<span class="ce-loc" data-ce-loc="max_quality">Max quality</span>:
		<select id="cb-enh-multi-max-quality">
			<option value="2160">2160p</option>
			<option value="1440">1440p</option>
			<option value="1080">1080p</option>
			<option value="720">720p</option>
			<option value="420">480p</option>
		</select>

		<br>
		<br>

		<p>
		<span class="ce-loc" data-ce-loc="multi_tip_can_add">
		You can add cams to this viewer by clicking "Add to multi cam viewer" on room page or by using this icon in room lists</span>: <span id="cb-enh-multi-tip-add-icon"></span>
		</p>

		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_enter_shortcut">Press F11 to enter/exit fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_mini_shortcut">Press F10 to enter/exit semi fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_exit_shortcut">Press ESC to exit fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_drag">Simply drag & drop cams to reposition them.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_scroll">It's possible to use scroll in fullscreen.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_auto_save">Current viewer state is being automatically saved.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_vid_freeze">Click on the video if it got frozen.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_grid_size">It's possible to set grid size on fullscreen too - hover mouse cursor on the right corner to see selectors.</p>

		<br>
		<span class="ce-loc" data-ce-loc="multi_share">Current viewer state can be restored or shared with others (who also have installed Chaturbate Enhancer) via this link</span>:
		<br>
		<input type="text" id="cb-enh-multi-share" readonly>
	</div>

	<div id="cb-enh-multi-support"></div>
</div>
`;

let multiViewCamHTML = `
<div class="cb-enh-multi-cam" data-cb-enh-username="{{username}}" draggable="true">

<div class="cb-enh-multi-drag-outline"></div>

<div class="cb-enh-multi-cam-wrap">
	<div class="cb-enh-multi-cam-wrap2">
		<div class="cb-enh-multi-cam-img-wrap"></div>
		<div class="cb-enh-multi-cam-video-wrap"></div>

		<div class="cb-enh-multi-username">
			<a href="/{{username}}/" target="_blank" rel="noopener">{{username}}</a>
		</div>

		<div class="cb-enh-multi-status noselect"></div>
		<div class="cb-enh-multi-close noselect">{{closeSVG}}</div>
	</div>
</div>

</div>
`;

let closeSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M256 874.088 181.912 800l224-224-224-224L256 277.912l224 224 224-224L778.088 352l-224 224 224 224L704 874.088l-224-224-224 224Z"/></svg>';

let gMultiRooms = [];
let gMultiRoomsData = [];
let gMultiInMiniFullscreen = false;
let gIntvMultiUpdateRooms;

function initMultiView() {
	gIsInMultiView = true;

	let hash = window.location.hash;
	let roomsParamStart = hash.indexOf('?rooms=');
	if(roomsParamStart !== -1) {
		let rooms = hash.substring(roomsParamStart + '?rooms='.length).split(',');
		multiClearRoomList();
		rooms.forEach(function(room) {
			multiAddRoomToList(room);
		});
	}

	addStyle(multiViewStyle);
	$('#main').append(multiViewHTML);

	$(document).on('click', '.cb-enh-multi-close', function() {
		let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
		multiRemoveRoom(username);
	});

	// Set share URL selected on click
	$(document).on('focus', '#cb-enh-multi-share', function() {
		this.setSelectionRange(0, this.value.length);
	});

	addEventListener('storage', function(e) {
		if(e.key !== 'cb-enh-multicam') {
			return;
		}
		
		multiUpdateRooms();
	});

	multiUpdateRooms();

	// Add size selector icons
	let $selectorDiv = $('#cb-enh-multi-size-selector');
	let $svg1 = $(gGridIconSvgBig);
	$svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
	$selectorDiv.append($svg1);
	$svg1.on('click', function() {
		let width = 2;
		multiSetGridSize(width);
		setSetting('multi-grid-size', width);
	});

	let $svg2 = $(gGridIconSvgMedium);
	$svg2.addClass('cb-enh-grid-size-selector-img');
	$svg2.on('click', function() {
		let columns = 3;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});
	$selectorDiv.append($svg2);

	let $svg3 = $(gGridIconSvgSmall);
	$svg3.addClass('cb-enh-grid-size-selector-img');
	$selectorDiv.append($svg3);
	$svg3.on('click', function() {
		let columns = 4;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});

	let $svg4 = $(gGridIconSvgSmallX);
	$svg4.addClass('cb-enh-grid-size-selector-img');
	$selectorDiv.append($svg4);
	$svg4.on('click', function() {
		let columns = 5;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});
	//

	$(document).on('click', '#cb-enh-multi-btn-remove-offline', function() {
		multiRemoveOfflineRooms();
	});

	$(document).on('click', '#cb-enh-multi-btn-remove-all', function() {
		multiRemoveAllRooms();
	});

	$(document).on('click', '#cb-enh-multi-btn-mute-all', function() {
		$('.cb-enh-multi-cam video').each(function() {
			this.muted = true;
		});
	});

	$(document).on('change', '#cb-enh-multi-btn-auto-remove', function() {
		let enabled = $(this).is(":checked");
		setSetting('multi-auto-remove', enabled);

		if(enabled) {
			multiRemoveOfflineRooms();
		}
	});

	$(document).on('change', '#cb-enh-multi-btn-auto-hide', function() {
		let enabled = $(this).is(":checked");
		setSetting('multi-auto-hide', enabled);
		multiHidePrivateRoomsIfNeeded();
	});

	$(document).on('change', '#cb-enh-multi-max-quality', function() {
		let maxHeight = parseInt($(this).val());
		setSetting('multi-max-quality', maxHeight);

		$('.cb-enh-multi-cam video').each(function() {
			let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
			let hls = gMultiRoomsData[username]['hls'];
			if(hls) {
				multiUpdateVideoQuality(hls, maxHeight);
			}
		});
	});

	let autoRemove = getSetting('multi-auto-remove');
	$('#cb-enh-multi-btn-auto-remove').prop('checked', autoRemove);

	let autoHide = getSetting('multi-auto-hide');
	$('#cb-enh-multi-btn-auto-hide').prop('checked', autoHide);

	$('#cb-enh-multi-max-quality').val(multiGetMaxHeight());

	$(document).on('click', '#cb-enh-multi-btn-enter-fullscreen', function() {
		if(!document.fullscreenElement) {
			multiEnterFullscreen();
		}
		else {
			document.exitFullscreen();
		}
	});

	$(document).on('click', '#cb-enh-multi-btn-enter-fullscreen-mini', function() {
		multiToggleFullscreenMini();
	});

	document.addEventListener('fullscreenchange', multiOnFullscreenChange, false);
	document.addEventListener('mozfullscreenchange', multiOnFullscreenChange, false);
	document.addEventListener('MSFullscreenChange', multiOnFullscreenChange, false);
	document.addEventListener('webkitfullscreenchange', multiOnFullscreenChange, false);

	window.addEventListener('keydown', function(e) {
		let keyCode = e.keyCode || e.which;

		// F11
		if(keyCode === 122) {
			multiEnterFullscreen();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}
		
		// F10
		if(keyCode === 121 && !e.repeat) {
			multiToggleFullscreenMini();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}

		// ESC
		if(keyCode === 27 && gMultiInMiniFullscreen && !e.repeat) {
			multiToggleFullscreenMini();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}
	});

	multiUpdateShareURL();

	// Update room statuses
	clearIntervalEx(gIntvMultiUpdateRooms);
	gIntvMultiUpdateRooms = setInterval(multiUpdateRoomStatuses, 1000 * 30);

	$('#cb-enh-multi-tip-add-icon').append(multiAddSVG);

	// Drag & drop
	let $dragBlock = null;
	$(document).on('dragstart', '.cb-enh-multi-cam', function(e) {
		e = e.originalEvent;
		$dragBlock = $(e.target).closest('.cb-enh-multi-cam');
		e.dataTransfer.effectAllowed = 'move';
	});

	$(document).on('dragenter', '.cb-enh-multi-cam', function(e) {
		e = e.originalEvent;
		let $parent = $(e.target).closest('.cb-enh-multi-cam');

		if(!$dragBlock.is($parent)) {
			$($parent).addClass('cb-enh-multi-cam-ondragover');
		}

		e.preventDefault();
	});

	$(document).on('dragleave', '.cb-enh-multi-cam', function(e) {
		e = e.originalEvent;
		let $parent = $(e.target).closest('.cb-enh-multi-cam');
		$($parent).removeClass('cb-enh-multi-cam-ondragover');
		e.preventDefault();
	});

	$(document).on('dragover', '.cb-enh-multi-cam', function(e) {
		e = e.originalEvent;
		e.preventDefault();
	});

	$(document).on('drop', '.cb-enh-multi-cam', function(e) {
		e = e.originalEvent;
		e.preventDefault();

		let $parent = $(e.target).closest('.cb-enh-multi-cam');
		$($parent).removeClass('cb-enh-multi-cam-ondragover');
		if($dragBlock.is($parent)) {
			return;
		}

		let index = $parent.index();
		let oldIndex = $dragBlock.index();

		let arrMin1 = false;
		
		if(index === 0) {
			$dragBlock.prependTo($('#cb-enh-multi-cams'));
		}
		else {
			if(index > oldIndex) {
				index += 1;
				arrMin1 = true;
			}
			$dragBlock.insertAfter($('.cb-enh-multi-cam').eq(index - 1));
		}

		if(arrMin1) {
			index -= 1;
		}
		arraymove(gMultiRooms, oldIndex, index);
		
		// Update data in local storage
		let data = localStorage.getItem('cb-enh-multicam');
		try {
			data = JSON.parse(data);
		}
		catch(SyntaxError) {
			data = null;
		}
		
		if(!data) {
			data = {
				'rooms': []
			};
		}

		data['rooms'] = gMultiRooms;
		localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
		multiUpdateShareURL();
		
		$dragBlock = null;
	});

	$(document).on('dragend', '.cb-enh-multi-cam', function(e) {
		$dragBlock = null;
	});

	// Add support info
	multiInitSupportInfo();

	localizeStringsNextTick();
}

function multiInitSupportInfo() {
	let userType = getUserType();
	let msg = getSupportMessage(userType);
	if(msg === null) {
		return;
	}

	$(document).on('click', '#cb-enh-multi-support', function() {
		loutrreg(gCurrentBroadcaster);
	});

	$('#cb-enh-multi-support').append(msg);
}

function multiUpdateRooms() {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		return;
	}

	if(!'rooms' in data) {
		return;
	}

	data['rooms'].forEach(function(room) {
		if(gMultiRooms.indexOf(room) !== -1) {
			return;
		}

		multiAddRoom(room);
	});
}

function multiUpdateRoomStatuses() {
	gMultiRooms.forEach(function(username) {
		xmlhttpRequest({
			method: 'GET',
			url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
				'Referer': 'https://chaturbate.com/' + username + '/',
			},
			timeout: 60*1*1000,
			onload: function(resp) {
				let data;
				try {
					data = JSON.parse(resp.responseText);
				}
				catch(SyntaxError) {
					return;
				}
	
				if(!'room_status' in data) {
					return;
				}

				gMultiRoomsData[username] = {
					'status': data['room_status']
				};

				let $block = $('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]');
				if($block.length === 0) {
					return;
				}
	
				if(data['room_status'] !== 'public') {
					let statusText = getLocale('status_' + data['room_status'], data['room_status']);
					multiSetErrored($block, statusText);

					if(data['room_status'] === 'offline' && getSetting('multi-auto-remove')) {
						multiRemoveRoom(username);
					}

					else if(getSetting('multi-auto-hide')) {
						$block.hide();
					}

					return;
				}

				if(!('hls_source' in data) || data['hls_source'] === '') {
					return;
				}

				multiSetOk($block);

				let $video = $block.find('video');
				if($video.length === 0) {
					multiInitVideo($block, username, data['hls_source']);
				}
			}
		});
	});
}

function multiSetErrored($block, status) {
	$block.find('.cb-enh-multi-status').text(status);
	$block.addClass('cb-enh-multi-offline');
	$block.find('video').remove();
}

function multiSetOk($block) {
	$block.find('.cb-enh-multi-status').text('');
	$block.removeClass('cb-enh-multi-offline');
	$block.show();
}

function multiInitVideo($block, username, hls_source) {
	let videoHTML = '<video controls autoplay muted data-listener-count-webkitendfullscreen="1" class="vjs-tech cb-enh-video" id="vjs_video_3_html5_api" tabindex="-1" role="application" poster="https://cbjpeg.stream.highwebmedia.com/stream?room={{username}}&f={{random}}"></video>';
	videoHTML = videoHTML.replaceAll('{{username}}', username);
	videoHTML = videoHTML.replaceAll('{{random}}', Math.random());
	let $video = $(videoHTML);

	$video.on('pause', function() {
		makeVideoPlay($video[0]);
	});

	$video.on('click', function() {
		makeVideoPlay($video[0]);
	});

	let hls = new Hls();
	gMultiRoomsData[username]['hls'] = hls;
	
	hls.on(Hls.Events.LEVEL_LOADED, function() {
		multiUpdateVideoQuality(hls);
	});

	hls.loadSource(hls_source);
	hls.attachMedia($video[0]);

	$block.find('.cb-enh-multi-cam-video-wrap').append($video);
}

function multiGetMaxHeight() {
	let maxHeight = getSetting('multi-max-quality');
	if(maxHeight === null) {
		maxHeight = 1080;
	}
	return maxHeight;
}

function multiUpdateVideoQuality(hls) {
	let maxHeight = multiGetMaxHeight();
	let capped = false;

	hls.levels.every(function(level, i) {
		if(level.height > maxHeight) {
			let levelIndex = i - 1;
			if(levelIndex < 0) {
				levelIndex = 0;
			}

			hls.autoLevelCapping = levelIndex;
			capped = true;
			return false;
		}

		return true;
	});

	if(!capped) {
		hls.autoLevelCapping = -1;
	}
}

function multiAddRoom(username) {
	gMultiRooms.push(username);

	let blockHTML = multiViewCamHTML;
	blockHTML = blockHTML.replaceAll('{{username}}', username);
	blockHTML = blockHTML.replaceAll('{{random}}', Math.random());
	blockHTML = blockHTML.replaceAll('{{closeSVG}}', closeSVG);
	let $block = $(blockHTML);

	$('#cb-enh-multi-cams').append($block);

	xmlhttpRequest({
		method: 'GET',
		url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
			'Referer': 'https://chaturbate.com/' + username + '/',
		},
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				multiSetErrored($block, getLocale('error', 'error'));
				return;
			}

			if(!'room_status' in data) {
				multiSetErrored($block, getLocale('error', 'error'));
				return;
			}

			if($block.length === 0) {
				return;
			}
			
			gMultiRoomsData[username] = {
				'status': data['room_status']
			};

			if(data['room_status'] !== 'public') {
				let statusText = getLocale('status_' + data['room_status'], data['room_status']);
				multiSetErrored($block, statusText);

				if(data['room_status'] === 'offline' && getSetting('multi-auto-remove')) {
					multiRemoveRoom(username);
				}

				else if(getSetting('multi-auto-hide')) {
					$block.hide();
				}
			}
			else if(!('hls_source' in data) || data['hls_source'] === '') {
				multiSetErrored($block,  getLocale('multi_no_video_hls', 'error: no video available') );
			}
			else {
				multiInitVideo($block, username, data['hls_source']);
			}
		},

		onerror: function() {
			if($block.length === 0) {
				return;
			}

			multiSetErrored($block, getLocale('error', 'error'));
		}
	});

	fetchRoomSnapshot(username, function(resp) {
		if($block.length === 0) {
			return;
		}

		let $img = $('<img>');
		$img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
		$block.find('.cb-enh-multi-cam-img-wrap').append($img);
	});

	insertRoomAv($block.find('.cb-enh-multi-cam-img-wrap'), username);
	multiUpdateShareURL();
}

function multiRemoveRoom(username) {
	$('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]').remove();

	let pos = gMultiRooms.indexOf(username);
	if(pos !== -1) {
		gMultiRooms.splice(pos, 1);
		delete gMultiRoomsData[username];
	}

	let data = {
		'rooms': gMultiRooms
	};

	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
	multiUpdateShareURL();
}

function multiAddRoomToList(username) {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		data = null;
	}
	
	if(!data) {
		data = {
			'rooms': []
		};
	}

	if(data['rooms'].indexOf(username) !== -1) {
		return;
	}

	data['rooms'].push(username);
	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
	multiUpdateShareURL();
}

function multiClearRoomList() {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		data = null;
	}
	
	data['rooms'] = [];
	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
}

function multiSetGridSize(columns) {
	let width = 100 / columns;
	let gridStyle = `
	.cb-enh-multi-cam {
		width: ` + width + `% !important;
	}
	`;
	addStyle(gridStyle);
}

// Set room grid size from user settings
function multiLoadGridSize() {
	let minGridSize = 2;
	let maxGridSize = 10;
	
	let size = getSetting('multi-grid-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			multiSetGridSize(minGridSize)
		}
	
		else if(size > maxGridSize) {
			multiSetGridSize(maxGridSize)
		}
	
		else {
			multiSetGridSize(size);
		}
	}
}

function multiRemoveOfflineRooms() {
	$('.cb-enh-multi-offline').each(function() {
		let username = $(this).data('cb-enh-username');
		if(gMultiRoomsData[username]['status'] !== 'offline') {
			return;
		}

		multiRemoveRoom(username);
	});
}

function multiHidePrivateRoomsIfNeeded() {
	let autoHide = getSetting('multi-auto-hide');
	$('.cb-enh-multi-cam').each(function() {
		let username = $(this).data('cb-enh-username');
		let status = gMultiRoomsData[username]['status'];
		if(status !== 'public' && autoHide) {
			$(this).hide();
		}
		else {
			$(this).show();
		}
	});
}

function multiRemoveAllRooms() {
	$('.cb-enh-multi-cam').each(function() {
		let username = $(this).data('cb-enh-username');
		multiRemoveRoom(username);
	});
}

function multiUpdateShareURL() {
	let url = 'https://chaturbate.com/#multicam';
	if(gMultiRooms.length !== 0) {
		url += '?rooms=' + gMultiRooms.join(',');
	}
	$('#cb-enh-multi-share').val(url);
}

function multiEnterFullscreen() {
	if(document.fullscreenElement) {
		return;
	}

	$('#cb-enh-multi-content')[0].requestFullscreen();
	$('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');
	$('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
}

function multiToggleFullscreenMini() {
	let $btn = $('#cb-enh-multi-btn-enter-fullscreen-mini');

	if(!gMultiInMiniFullscreen) {
		$('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');

		$('#header').hide();
		$('.top-section').hide();
		$('#footer-holder').hide();

		$btn.val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
	}
	else {
		$('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');

		$('#header').show();
		$('.top-section').show();
		$('#footer-holder').show();

		$btn.val( getLocale('multi_btn_enter_fscreen_mini', 'ENTER SEMI FULLSCREEN (WITH ADDRESS BAR)') );
	}

	gMultiInMiniFullscreen = !gMultiInMiniFullscreen;
	window.scrollTo(0, 0);
}

function multiOnFullscreenChange() {
	if(!document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
		// Fullscreen exit
		$('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');
		$('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_enter_fscreen', 'ENTER FULLSCREEN') );
	}
}
// MULTI VIEW END

// UTILS BELOW
function isInteractiveFullScreenEnabled() {
	if(!$('#vjs_video_3').hasClass('vjs-controls-disabled')) {
		return false;
	}

	let $player = $('.videoPlayerDiv');
	return $player.innerWidth() === window.innerWidth && $player.innerHeight() === window.innerHeight;
}

function enterInteractiveFullScreen() {
	$('#fvm-link').click();
}

function isTheaterModeEnabled() {
	return $('#vjs_video_3').hasClass('vjs-controls-disabled');
}

function getFileName(fname, ext, date) {
	let d = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
	let t = ('0' + date.getHours()).slice(-2) + '-' + ('0' + date.getMinutes()).slice(-2) + '-' + ('0' + date.getSeconds()).slice(-2);
	return fname + '_' + d + '_' + t + ext;
}

function captureStream(el) {
	if(el.captureStream) {
		return el.captureStream();
	}

	if(el.mozCaptureStream) {
		return el.mozCaptureStream();
	}

	return 'err-no-func';
}

// Clear interval and null it
function clearIntervalEx(intv) {
	if(intv) {
		clearInterval(intv);
		intv = null;
	}
}

function rev(str) {
    let rv = '';
    for(let i = str.length - 1; i >= 0; i--) {
		rv += str[i];
    }
    return rv;
}

function arraymove(arr, fromIndex, toIndex) {
	let element = arr[fromIndex];
	arr.splice(fromIndex, 1);
	arr.splice(toIndex, 0, element);
}

// Start
loadSettings();

if(getSetting('reg-redir') === 1) {
	regRedir(getSetting('reg-redir-room'));
}

document.addEventListener('click', function(e) {
	if(regRedirPreventClick && e.target !== $('.logo-zone a')[0]) {
		e.stopImmediatePropagation();
		e.preventDefault();
		return;
	}

	// Enter interactive fullscreen via video fullscreen button if configured
	if(e.target === $('button.vjs-fullscreen-control > span.vjs-icon-placeholder')[0] || e.target === $('#full-screen')[0]) {
		if(videoJsPlayer && !videoJsPlayer.isFullscreen() && getSetting('use-intrf-def')) {
			e.preventDefault();
			e.stopImmediatePropagation();
			enterInteractiveFullScreen();
		}
		return;
	}

	// Disable pausing on video click
	let vid = getVideo();
	if(vid && e.target === vid[0] && isVideoPlaying(vid[0])) {
		e.stopImmediatePropagation();

		// Simulate body click so things like followers tab will close properly
		document.body.click();
	}
}, true);

document.addEventListener('dblclick', function(e) {
	let vid = getVideo();
	if(!vid || e.target !== vid[0]) {
		return;
	}

	if(isInteractiveFullScreenEnabled()) {
		// Exit interactive fullscreen on double click
		$('span#full-screen').click();
		return;
	}

	if(isTheaterModeEnabled() && videoJsPlayer && !videoJsPlayer.isFullscreen()) {
		if(getSetting('use-intrf-def')) {
			e.preventDefault();
			e.stopImmediatePropagation();
			enterInteractiveFullScreen();
			return;
		}

		// Enter theater mode fullscreen on double click
		videoJsPlayer.requestFullscreen();
		return;
	}

	if(videoJsPlayer && !videoJsPlayer.isFullscreen() && getSetting('use-intrf-def')) {
		e.preventDefault();
		e.stopImmediatePropagation();
		enterInteractiveFullScreen();
		return;
	}
}, true);

// Set room grid size from user settings
function loadGridSize() {
	let minGridSize = 50;
	let maxGridSize = 300;
	
	let size = getSetting('grid-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			setGridSize2(minGridSize)
		}
	
		else if(size > maxGridSize) {
			setGridSize2(maxGridSize)
		}
	
		else {
			setGridSize2(size);
		}
	}
}
loadGridSize();
// End of set room grid size from user settings

// Set more room grid size from user settings
function loadMoreGridSize() {
	let minGridSize = 50;
	let maxGridSize = 300;

	let size = getSetting('grid-more-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			setMoreRoomsGridSize(minGridSize)
		}
	
		else if(size > maxGridSize) {
			setMoreRoomsGridSize(maxGridSize)
		}
	
		else {
			setMoreRoomsGridSize(size);
		}
	}
}
loadMoreGridSize();
// End of set more room grid size from user settings

multiLoadGridSize();

function fetchRoomSnapshot(username, callback) {
	let req = new XMLHttpRequest();
	req.timeout = 10000;
	req.responseType = 'arraybuffer';
	req.addEventListener('load', function() {
		let length = req.getResponseHeader('Content-length');
		if(length === '4824' || length === '4456') {
			// Generic offline thumbnail
			return;
		}

		callback(req.response);
	});

	req.open('GET', 'https://roomimg.stream.highwebmedia.com/riw/' + username + '.jpg');
	req.send();
}

})();