YouTube Defaulter

Set speed, quality, subtitles and volume as default globally or specialize for each channel

当前为 2024-05-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTube Defaulter
// @namespace    https://greasyfork.org/ru/users/901750-gooseob
// @version      1.9.1
// @description  Set speed, quality, subtitles and volume as default globally or specialize for each channel
// @author       GooseOb
// @license      MIT
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// ==/UserScript==

(function () {
	// index.ts
	var debounce = function (callback, delay) {
		let timeout;
		return function (...args) {
			clearTimeout(timeout);
			timeout = window.setTimeout(() => {
				callback.apply(this, args);
			}, delay);
		};
	};
	var translations = {
		'be-BY': {
			OPEN_SETTINGS: 'Адкрыць дадатковыя налады',
			SUBTITLES: 'Субтытры',
			SPEED: 'Хуткасьць',
			CUSTOM_SPEED: 'Свая хуткасьць',
			CUSTOM_SPEED_HINT:
				'Калі вызначана, будзе выкарыстоўвацца замест "хуткасьць"',
			QUALITY: 'Якасьць',
			VOLUME: 'Гучнасьць, %',
			GLOBAL: 'глябальна',
			LOCAL: 'гэты канал',
			SHORTS: 'Адкрываць shorts як звычайныя',
			NEW_TAB: 'Адкрываць відэа ў новай картцы',
			COPY_SUBS: 'Капіяваць субтытры ў поўнаэкранным, Ctrl+C',
			STANDARD_MUSIC_SPEED: 'Звычайная хуткасьць на каналах музыкаў',
			ENHANCED_BITRATE: 'Палепшаны бітрэйт (для карыстальнікаў Premium)',
			SAVE: 'Захаваць',
			EXPORT: 'Экспарт',
			IMPORT: 'Імпарт',
			REFRESH: 'Зроблена. Абнавіце старонку',
		},
	};
	var text = {
		OPEN_SETTINGS: 'Open additional settings',
		SUBTITLES: 'Subtitles',
		SPEED: 'Speed',
		CUSTOM_SPEED: 'Custom speed',
		CUSTOM_SPEED_HINT: 'If defined, will be used instead of "speed"',
		QUALITY: 'Quality',
		VOLUME: 'Volume, %',
		GLOBAL: 'global',
		LOCAL: 'this channel',
		SHORTS: 'Open shorts as a usual video',
		NEW_TAB: 'Open videos in a new tab',
		COPY_SUBS: 'Copy subtitles by Ctrl+C in fullscreen mode',
		STANDARD_MUSIC_SPEED: 'Normal speed on artist channels',
		ENHANCED_BITRATE: 'Quality: Enhanced bitrate (for Premium users)',
		SAVE: 'Save',
		DEFAULT: '-',
		EXPORT: 'Export',
		IMPORT: 'Import',
		REFRESH: 'Done. Refresh the page',
		...translations[document.documentElement.lang],
	};
	var cfgLocalStorage = localStorage['YTDefaulter'];
	var cfg = cfgLocalStorage
		? JSON.parse(cfgLocalStorage)
		: {
				_v: 4,
				global: {},
				channels: {},
				flags: {
					shortsToUsual: false,
					newTab: false,
					copySubs: false,
					standardMusicSpeed: false,
					enhancedBitrate: false,
				},
			};
	var isDescendantOrTheSame = (child, parents) => {
		while (child !== null) {
			if (parents.includes(child)) return true;
			child = child.parentNode;
		}
		return false;
	};
	var saveCfg = () => {
		const cfgCopy = { ...cfg };
		const channelsCfgCopy = { ...cfg.channels };
		outer: for (const key in channelsCfgCopy) {
			const channelCfg = channelsCfgCopy[key];
			if (channelCfg.subtitles) continue;
			for (const cfgKey in channelCfg)
				if (cfgKey !== 'subtitles') continue outer;
			delete channelsCfgCopy[key];
		}
		cfgCopy.channels = channelsCfgCopy;
		localStorage['YTDefaulter'] = JSON.stringify(cfgCopy);
	};
	var updateCfg = () => {
		const doUpdate = cfg._v !== 4;
		if (doUpdate) {
			switch (cfg._v) {
				case 2:
					cfg.flags.standardMusicSpeed = false;
					cfg._v = 3;
				case 3:
					cfg.global.quality = cfg.global.qualityMax;
					delete cfg.global.qualityMax;
					for (const key in cfg.channels) {
						const currCfg = cfg.channels[key];
						currCfg.quality = currCfg.qualityMax;
						delete currCfg.qualityMax;
					}
					cfg._v = 4;
			}
			saveCfg();
		}
		return doUpdate;
	};
	updateCfg();
	var restoreFocusAfter = (cb) => {
		const el = document.activeElement;
		cb();
		el.focus();
	};
	var until = (getItem, check, msToWait = 1e4, msReqTimeout = 20) =>
		new Promise((res, rej) => {
			const reqLimit = msToWait / msReqTimeout;
			let i = 0;
			const interval = setInterval(() => {
				if (i++ > reqLimit) exit(rej);
				const item = getItem();
				if (!check(item)) return;
				exit(() => res(item));
			}, msReqTimeout);
			const exit = (cb) => {
				clearInterval(interval);
				cb();
			};
		});
	var untilAppear = (getItem, msToWait) => until(getItem, Boolean, msToWait);
	var ytSettingItems = {};
	var channelConfig = { current: null };
	var video;
	var subtitlesBtn;
	var muteBtn;
	var SPEED_NORMAL;
	var isSpeedChanged = false;
	var menu = {
		element: null,
		btn: null,
		isOpen: false,
		width: 0,
		_closeListener: {
			onClick(e) {
				const el = e.target;
				if (isDescendantOrTheSame(el, [menu.element, menu.btn])) return;
				menu.toggle();
			},
			onKeyUp(e) {
				if (e.code !== 'Escape') return;
				menu._setOpen(false);
				menu.btn.focus();
			},
			add() {
				document.addEventListener('click', this.onClick);
				document.addEventListener('keyup', this.onKeyUp);
			},
			remove() {
				document.removeEventListener('click', this.onClick);
				document.removeEventListener('keyup', this.onKeyUp);
			},
		},
		firstElement: null,
		_setOpen(bool) {
			if (bool) {
				this.fixPosition();
				this.element.style.visibility = 'visible';
				this._closeListener.add();
				this.firstElement.focus();
			} else {
				this.element.style.visibility = 'hidden';
				this._closeListener.remove();
			}
			this.isOpen = bool;
		},
		toggle: debounce(function () {
			this._setOpen(!this.isOpen);
		}, 100),
		fixPosition() {
			const { y, height, width, left } = this.btn.getBoundingClientRect();
			this.element.style.top = y + height + 8 + 'px';
			this.element.style.left = left + width - this.width + 'px';
		},
	};
	var $ = (id) => document.getElementById(id);
	var getChannelUsername = (aboveTheFold) =>
		aboveTheFold
			.querySelector('.ytd-channel-name > a')
			.href.match(/(?<=@).+?$/)[0];
	var getPlr = () => $('movie_player');
	var getAboveTheFold = () => $('above-the-fold');
	var getActionsBar = () => $('actions')?.querySelector('ytd-menu-renderer');
	var iconD = {
		['quality']:
			'M15,17h6v1h-6V17z M11,17H3v1h8v2h1v-2v-1v-2h-1V17z M14,8h1V6V5V3h-1v2H3v1h11V8z            M18,5v1h3V5H18z M6,14h1v-2v-1V9H6v2H3v1 h3V14z M10,12h11v-1H10V12z',
		['speed']:
			'M10,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z            M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z            M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z            M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z            M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z',
	};
	var getYtElementFinder = (elems) => (name) =>
		findInNodeList(
			elems,
			(el) => !!el.querySelector(`path[d="${iconD[name]}"]`)
		);
	var untilChannelUsernameAppear = (aboveTheFold) =>
		untilAppear(() => getChannelUsername(aboveTheFold)).catch(() => '');
	var isMusicChannel = (aboveTheFold) =>
		!!aboveTheFold.querySelector('.badge-style-type-verified-artist');
	var findInNodeList = (list, callback) => {
		for (const item of list) if (callback(item)) return item;
	};
	var ytMenu = {
		updatePlayer(plr) {
			this.element = plr.querySelector('.ytp-settings-menu');
			this._btn = plr.querySelector('.ytp-settings-button');
			restoreFocusAfter(() => {
				this._btn.click();
				this._btn.click();
			});
		},
		element: null,
		_btn: null,
		isOpen() {
			return this.element.style.display !== 'none';
		},
		setOpen(bool) {
			if (bool !== this.isOpen()) this._btn.click();
		},
		openItem(item) {
			this.setOpen(true);
			item.click();
			return this.element.querySelectorAll(
				'.ytp-panel-animate-forward .ytp-menuitem-label'
			);
		},
		findInItem(item, callback) {
			return findInNodeList(this.openItem(item), callback);
		},
	};
	var validateVolume = (value) => {
		const num = +value;
		return num < 0 || num > 100
			? 'out of range'
			: isNaN(num)
				? 'not a number'
				: false;
	};
	var getElCreator = (tag) => (props) =>
		Object.assign(document.createElement(tag), props);
	var comparators = {
		['quality']: (target, current) =>
			+target >= parseInt(current) &&
			(cfg.flags.enhancedBitrate || !current.toLowerCase().includes('premium')),
		['speed']: (target, current) => target === current,
	};
	var logger = {
		prefix: '[YT-Defaulter]',
		err(...msgs) {
			console.error(this.prefix, ...msgs);
		},
		outOfRange(what) {
			this.err(what, 'value is out of range');
		},
	};
	var valueSetters = {
		_ytSettingItem(value, settingName) {
			const isOpen = ytMenu.isOpen();
			const compare = comparators[settingName];
			ytMenu
				.findInItem(ytSettingItems[settingName], (btn) =>
					compare(value, btn.textContent)
				)
				?.click();
			ytMenu.setOpen(isOpen);
		},
		speed(value) {
			this._ytSettingItem(isSpeedChanged ? SPEED_NORMAL : value, 'speed');
			isSpeedChanged = !isSpeedChanged;
		},
		customSpeed(value) {
			try {
				video.playbackRate = isSpeedChanged ? 1 : +value;
			} catch {
				logger.outOfRange('Custom speed');
				return;
			}
			isSpeedChanged = !isSpeedChanged;
		},
		subtitles(value) {
			if (subtitlesBtn.ariaPressed !== value.toString()) subtitlesBtn.click();
		},
		volume(value) {
			const num = +value;
			muteBtn ||= document.querySelector('.ytp-mute-button');
			const isMuted = muteBtn.dataset.titleNoTooltip !== 'Mute';
			if (num === 0) {
				if (!isMuted) muteBtn.click();
				return;
			}
			if (isMuted) muteBtn.click();
			try {
				video.volume = num / 100;
			} catch {
				logger.outOfRange('Volume');
			}
		},
		quality(value) {
			this._ytSettingItem(value, 'quality');
		},
	};
	var delay = (ms) => new Promise((res) => setTimeout(res, ms));
	var onPageChange = async () => {
		if (location.pathname !== '/watch') return;
		const aboveTheFold = await untilAppear(getAboveTheFold);
		const channelUsername = await untilChannelUsernameAppear(aboveTheFold);
		channelConfig.current = cfg.channels[channelUsername] ||= {};
		const plr = await untilAppear(getPlr);
		await delay(1000);
		const getAd = () => plr.querySelector('.ytp-ad-player-overlay');
		if (getAd()) await until(getAd, (ad) => !ad, 200000);
		ytMenu.updatePlayer(plr);
		const getMenuItems = () =>
			ytMenu.element.querySelectorAll('.ytp-menuitem[role="menuitem"]');
		const getYtElement = getYtElementFinder(
			await until(getMenuItems, (arr) => !!arr.length)
		);
		Object.assign(ytSettingItems, {
			quality: getYtElement('quality'),
			speed: getYtElement('speed'),
		});
		if (!SPEED_NORMAL)
			restoreFocusAfter(() => {
				const btn = ytMenu.findInItem(
					ytSettingItems.speed,
					(btn2) => !+btn2.textContent
				);
				if (btn) SPEED_NORMAL = btn.textContent;
			});
		const doNotChangeSpeed =
			cfg.flags.standardMusicSpeed && isMusicChannel(aboveTheFold);
		const settings = {
			...cfg.global,
			...channelConfig.current,
		};
		const isChannelSpeed = 'speed' in channelConfig.current;
		const isChannelCustomSpeed = 'customSpeed' in channelConfig.current;
		if ((doNotChangeSpeed && !isChannelCustomSpeed) || isChannelSpeed)
			delete settings.customSpeed;
		if (doNotChangeSpeed && !isChannelSpeed) settings.speed = SPEED_NORMAL;
		if (doNotChangeSpeed) {
			settings.speed = SPEED_NORMAL;
			delete settings.customSpeed;
		}
		const { customSpeed } = settings;
		delete settings.customSpeed;
		isSpeedChanged = false;
		video ||= plr.querySelector('.html5-main-video');
		subtitlesBtn ||= plr.querySelector('.ytp-subtitles-button');
		restoreFocusAfter(() => {
			for (const setting in settings) valueSetters[setting](settings[setting]);
			if (!isNaN(+customSpeed)) {
				isSpeedChanged = false;
				valueSetters.customSpeed(customSpeed);
			}
			ytMenu.setOpen(false);
		});
		if (menu.element) {
			const getInput = (name) => $('YTDef-' + name + '-thisChannel');
			for (const name of ['speed', 'customSpeed', 'quality', 'volume']) {
				getInput(name).value = channelConfig.current[name];
			}
			getInput('subtitles').checked = channelConfig.current.subtitles;
			return;
		}
		const div = getElCreator('div'),
			input = getElCreator('input'),
			checkbox = (props) => input({ type: 'checkbox', ...props }),
			option = getElCreator('option'),
			_label = getElCreator('label'),
			labelEl = (forId, props) => {
				const elem = _label(props);
				elem.setAttribute('for', forId);
				return elem;
			},
			selectEl = getElCreator('select'),
			btnClass = 'yt-spec-button-shape-next',
			_button = getElCreator('button'),
			button = (text2, props) =>
				_button({
					textContent: text2,
					className: `${btnClass} ${btnClass}--tonal ${btnClass}--mono ${btnClass}--size-m`,
					onfocus() {
						this.classList.add(btnClass + '--focused');
					},
					onblur() {
						this.classList.remove(btnClass + '--focused');
					},
					...props,
				});
		menu.element = div({
			id: 'YTDef-menu',
		});
		menu.btn = button('', {
			id: 'YTDef-btn',
			ariaLabel: text.OPEN_SETTINGS,
			tabIndex: 0,
			onclick() {
				menu.toggle();
			},
		});
		const toOptions = (values, getText) =>
			[
				option({
					value: text.DEFAULT,
					textContent: text.DEFAULT,
				}),
			].concat(
				values.map((value) =>
					option({
						value,
						textContent: getText(value),
					})
				)
			);
		const speedValues = [
			'2',
			'1.75',
			'1.5',
			'1.25',
			SPEED_NORMAL,
			'0.75',
			'0.5',
			'0.25',
		];
		const qualityValues = [
			'144',
			'240',
			'360',
			'480',
			'720',
			'1080',
			'1440',
			'2160',
			'4320',
		];
		const createSection = (sectionId, title, sectionCfg) => {
			const section = div({ role: 'group' });
			section.setAttribute('aria-labelledby', sectionId);
			const getLocalId = (name) => 'YTDef-' + name + '-' + sectionId;
			const addItem = (name, innerHTML, elem) => {
				const item = div();
				const id = getLocalId(name);
				const label = labelEl(id, { innerHTML });
				const valueProp = elem.type === 'checkbox' ? 'checked' : 'value';
				Object.assign(elem, {
					id,
					name,
					onchange() {
						const value = this[valueProp];
						if (value === '' || value === text.DEFAULT) delete sectionCfg[name];
						else sectionCfg[name] = value;
					},
				});
				const cfgValue = sectionCfg[name];
				if (cfgValue)
					setTimeout(() => {
						elem[valueProp] = cfgValue;
					});
				item.append(label, elem);
				section.append(item);
				if (elem.hint) section.append(elem.hint.element);
				return { elem };
			};
			const addSelectItem = (name, label, options, getText) => {
				const { elem } = addItem(
					name,
					label,
					selectEl({ value: text.DEFAULT })
				);
				elem.append(...toOptions(options, getText));
				return elem;
			};
			section.append(
				getElCreator('span')({ textContent: title, id: sectionId })
			);
			const createHint = (prefix, props) => {
				const obj = {
					element: div({
						className: 'YTDef-setting-hint',
						...props,
					}),
					hide() {
						this.element.style.display = 'none';
					},
					show(msg) {
						this.element.style.display = 'block';
						if (msg) this.element.textContent = prefix + msg;
					},
				};
				obj.hide();
				return obj;
			};
			const firstElement = addSelectItem(
				'speed',
				text.SPEED,
				speedValues,
				(val) => val
			);
			if (sectionId === 'global') menu.firstElement = firstElement;
			addItem(
				'customSpeed',
				text.CUSTOM_SPEED,
				input({
					type: 'number',
					onfocus() {
						this.hint.show();
					},
					onblur() {
						this.hint.hide();
					},
					hint: createHint(null, { textContent: text.CUSTOM_SPEED_HINT }),
				})
			);
			addSelectItem('quality', text.QUALITY, qualityValues, (val) => val + 'p');
			addItem(
				'volume',
				text.VOLUME,
				input({
					type: 'number',
					min: '0',
					max: '100',
					oninput() {
						settings.volume = this.value;
						const warning = validateVolume(this.value);
						if (warning) {
							this.hint.show(warning);
						} else {
							this.hint.hide();
						}
					},
					hint: createHint('Warning: '),
				})
			);
			addItem('subtitles', text.SUBTITLES, checkbox());
			return section;
		};
		const sections = div({ className: 'YTDef-sections' });
		sections.append(
			createSection('global', text.GLOBAL, cfg.global),
			createSection('thisChannel', text.LOCAL, channelConfig.current)
		);
		const checkboxDiv = (id, prop, text2) => {
			const cont = div({ className: 'check-cont' });
			id = 'YTDef-' + id;
			cont.append(
				labelEl(id, { textContent: text2 }),
				checkbox({
					id,
					checked: cfg.flags[prop],
					onclick() {
						cfg.flags[prop] = this.checked;
					},
				})
			);
			return cont;
		};
		const controlStatus = div();
		const updateControlStatus = (content) => {
			controlStatus.textContent = `[${new Date().toLocaleTimeString()}] ${content}`;
		};
		const controlDiv = div({ className: 'control-cont' });
		controlDiv.append(
			button(text.SAVE, {
				onclick() {
					saveCfg();
					updateControlStatus(text.SAVE);
				},
			}),
			button(text.EXPORT, {
				onclick: () => {
					navigator.clipboard
						.writeText(localStorage['YTDefaulter'])
						.then(() => {
							updateControlStatus(text.EXPORT);
						});
				},
			}),
			button(text.IMPORT, {
				onclick: async () => {
					try {
						const raw = await navigator.clipboard.readText();
						const newCfg = JSON.parse(raw);
						if (typeof newCfg !== 'object' || !newCfg._v) {
							throw new Error('Import: Invalid data');
						}
						if (!updateCfg()) {
							localStorage['YTDefaulter'] = raw;
							cfg = newCfg;
						}
					} catch (e) {
						updateControlStatus(e.message);
						return;
					}
					updateControlStatus(text.IMPORT + ': ' + text.REFRESH);
				},
			})
		);
		menu.element.append(
			sections,
			checkboxDiv('shorts', 'shortsToUsual', text.SHORTS),
			checkboxDiv('new-tab', 'newTab', text.NEW_TAB),
			checkboxDiv('copy-subs', 'copySubs', text.COPY_SUBS),
			checkboxDiv(
				'standard-music-speed',
				'standardMusicSpeed',
				text.STANDARD_MUSIC_SPEED
			),
			checkboxDiv('enhanced-bitrate', 'enhancedBitrate', text.ENHANCED_BITRATE),
			controlDiv,
			controlStatus
		);
		menu.element.addEventListener('keyup', (e) => {
			const el = e.target;
			if (e.code === 'Enter' && el.type === 'checkbox')
				el.checked = !el.checked;
		});
		const settingsIcon = document.createElementNS(
			'http://www.w3.org/2000/svg',
			'svg'
		);
		const iconStyle = {
			viewBox: '0 0 24 24',
			width: '24',
			height: '24',
			fill: 'var(--yt-spec-text-primary)',
		};
		for (const key in iconStyle) settingsIcon.setAttribute(key, iconStyle[key]);
		settingsIcon.append($('settings'));
		menu.btn.setAttribute('aria-controls', 'YTDef-menu');
		menu.btn.classList.add(btnClass + '--icon-button');
		menu.btn.append(settingsIcon);
		const actionsBar = await untilAppear(getActionsBar);
		actionsBar.insertBefore(menu.btn, actionsBar.lastChild);
		document.querySelector('ytd-popup-container').append(menu.element);
		menu.width = menu.element.getBoundingClientRect().width;
		sections.style.maxWidth = sections.offsetWidth + 'px';
	};
	var lastHref;
	setInterval(() => {
		if (lastHref === location.href) return;
		lastHref = location.href;
		setTimeout(onPageChange, 1000);
	}, 1000);
	var onClick = (e) => {
		const { shortsToUsual, newTab } = cfg.flags;
		if (!shortsToUsual && !newTab) return;
		let el = e.target;
		if (el.tagName !== 'A') {
			el = el.closest('a');
			if (!el) return;
		}
		if (!/shorts\/|watch\?v=/.test(el.href)) return;
		if (shortsToUsual) el.href = el.href.replace('shorts/', 'watch?v=');
		if (newTab) {
			el.target = '_blank';
			e.stopPropagation();
		}
	};
	document.addEventListener('click', onClick, { capture: true });
	document.addEventListener(
		'keyup',
		(e) => {
			if (e.code === 'Enter') return onClick(e);
			if (!e.ctrlKey) return;
			if (cfg.flags.copySubs && e.code === 'KeyC') {
				const plr = document.querySelector('.html5-video-player');
				if (!plr?.classList.contains('ytp-fullscreen')) return;
				const text2 = Array.from(
					plr.querySelectorAll('.captions-text > span'),
					(line) => line.textContent
				).join(' ');
				navigator.clipboard.writeText(text2);
				return;
			}
			if (e.code !== 'Space') return;
			e.stopPropagation();
			e.preventDefault();
			let setting;
			if (e.shiftKey) {
				setting = 'quality';
			} else {
				const value = channelConfig.current
					? channelConfig.current.customSpeed ||
						(!channelConfig.current.speed && cfg.global.customSpeed)
					: cfg.global.customSpeed;
				if (value) return valueSetters.customSpeed(value);
				setting = 'speed';
			}
			restoreFocusAfter(() => {
				valueSetters[setting]((channelConfig.current || cfg.global)[setting]);
			});
		},
		{ capture: true }
	);
	var listener = () => {
		if (menu.isOpen) menu.fixPosition();
	};
	window.addEventListener('scroll', listener);
	window.addEventListener('resize', listener);
	var m = '#YTDef-menu';
	var d = ' div';
	var i = ' input';
	var s = ' select';
	var bg = 'var(--yt-spec-menu-background)';
	var underline = 'border-bottom: 2px solid var(--yt-spec-text-primary);';
	document.head.append(
		getElCreator('style')({
			textContent: `
#YTDef-btn {position: relative; margin-left: 8px}
${m} {
display: flex;
visibility: hidden;
color: var(--yt-spec-text-primary);
font-size: 14px;
flex-direction: column;
position: fixed;
background: ${bg};
border-radius: 2rem;
padding: 1rem;
text-align: center;
box-shadow: 0px 4px 32px 0px var(--yt-spec-static-overlay-background-light);
z-index: 2202
}
.control-cont > button {margin: .2rem}
${m + d} {display: flex; margin-bottom: 1rem}
${m + d + d} {
flex-direction: column;
margin: 0 2rem
}
${m + d + d + d} {
flex-direction: row;
margin: 1rem 0
}
${m + s}, ${m + i} {
text-align: center;
background: ${bg};
border: none;
${underline}
color: inherit;
width: 5rem;
padding: 0;
margin-left: auto
}
${m} .YTDef-setting-hint {margin: 0; text-align: end}
${m + i} {outline: none}
${m + d + d + d}:focus-within > label, ${m} .check-cont:focus-within > label {${underline}}
${m} .check-cont {padding: 0 1rem}
${m + s} {appearance: none; outline: none}
${m} label {margin-right: 1.5rem; white-space: nowrap}
${m + i}::-webkit-outer-spin-button,
${m + i}::-webkit-inner-spin-button {-webkit-appearance: none; margin: 0}
${m + i}[type=number] {-moz-appearance: textfield}
${m + s}::-ms-expand {display: none}`,
		})
	);
})();