Raw Source
jelloleaf / Supercharged Local Directory File Browser

/* eslint-disable no-mixed-spaces-and-tabs */
/* eslint-disable no-useless-escape */
/* eslint-disable no-fallthrough */
/* eslint-disable no-case-declarations */
/* eslint-disable indent */
/* eslint-disable quotes */
/* jshint esversion: 6 */

// ==UserScript==
// @name			Supercharged Local Directory File Browser
// @version			7.0.1
// @description		Makes directory index pages (either local or remote open directories) actually useful. Adds sidebar and content preview pane; keyboard navigation; sorting; light/dark UI; preview images/fonts in navigable grids; browse subdirectories w/o page reload; media playback, shuffle/loop options; basic playlist (m3u, extm3u) & cuesheet* (with .cuetxt ext) support; create, edit, preview, save markdown/plain text files; open font files, view complete glyph repertoire, save glyphs as .svg; more.
// @author			gaspar_schot
// @license			GPL-3.0-or-later
// @homepageURL		https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
// @contributionURL https://paypal.me/mschrauzer
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC
// @include			file://*
// @include			about:blank
// @include 		https://www.example.com/path/to/directory/*

// @require https://cdn.jsdelivr.net/npm/markdown-it@12.3.2/dist/markdown-it.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-footnote@3.0.2/dist/markdown-it-footnote.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@4.0.2/dist/markdown-it-multimd-table.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
// @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js

// @updateURL	https://openuserjs.org/meta/gaspar_schot/Supercharged_Local_Directory_File_Browser.meta.js

// ==/UserScript==

(function() {
	'use strict';
	// ***** USER SETTINGS ***** //
	const $settings = {
		// NOTE: These settings will be overwritten whenever the script is updated. Use the "Export User Settings" menu item to save them.
		// You can paste the exported settings between the two lines below:
		//--------------------------------------------------------//

		bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
		// You may add as many menus and links as you like; just copy the example below and edit as needed.
		// Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
		[
			//	{ 'menu_title':'My Sample Menu',
			//		'links': [
			//			{ 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
			//			{ 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
			//			{ 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
			//			{ 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
			//		]},
			{ 'menu_title':'My Sample Bookmark Menu',
				'links': [
					{ 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
					{ 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
					{ 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
					{ 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
				]},
		],
		// GENERAL USER SETTINGS (Note: any changes you make will be reset to defaults every time the script is updated)
		alternate_background: true,				// If true (default true), alternate sidebar row background color.
		apps_as_dirs: true,						// Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
												// If false, treat apps as ignored files.
		autoload_media: true,					// If true (default), the first audio or video file found in a directory will be automatically selected and loaded for playback.
												// Also, cover art files (if any) will be loaded in the preview pane.
												// Files with 'cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes' in title will be loaded first, in that order, with exact matches having preference.
												// Note that there can be false positives because a file will be matched whenever there is no exact match and one of these words appears in an image name.
												// Otherwise the first image file in directory will be loaded.
		autoload_index_files: true,				// If true, automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
												// Note: the browser will automatically load any index.html files it finds in the directory, so the script will not work properly in such cases.
		theme: 'light',							// Options: 'light' or 'dark'
		sort_by: 'default',						// Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
												// default = Chrome sorting: dirs on top, files alphabetical.
		sort_direction: 'sort_ascending',		// Choose from: 'sort_ascending' (A-Z) [default] or 'sort_descending' (Z-A).
		dirs_on_top: false,						// If true, directories will always be listed firs except when sorting by "name" (since otherwise sorting by "name" would equal "default").
												// If false (default), directories and files will be sorted together. (In practice, dirs will typically still be separated when sorting by size, kind, and extension.)
		grid_font_size: 1,						// Default = 1
		grid_image_size: 184,					// Default = 184 (200px - 16px)
		show_details: true,						// If true (default), hide file and directory details; if false, show them.
		hide_ignored_items: false,				// If true, ignored files (= files the browser cannot natively open, e.g., common office and graphics files) will be hidden.
												// If false (default), ignored files will appear greyed-out.
		ignore_ignored_items: true,				// If true (default), clicking ignored file types (see $row_settings below) in the directory list will not cause the browser to attempt to open the file.
												// It is recommended to leave the default unchanged.
												// Ignored items can still be downloaded with ctrl-click or right-click.
		show_invisibles: true,					// Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
		show_numbers: true,						// If true (default true), number index items
		show_image_thumbnails: true,			// If true, images thumbnails will be shown instead of the default icon; this can slow page load.
		UI_font: 'system-ui, sans-serif',		// Choose an installed font for the UI; if undefined, use browser defaults instead. [system-ui, sans-serif]
		UI_font_size: '13px',					// Choose a default UI font size; use any standard CSS units.
		use_custom_icons: true,					// if true (default), use custom icons for dirs and files
												// if false, use browser/server default icons
		// TEXT EDITING SETTINGS
		enable_text_editing: true,							// If true (default), allow plain text files to be edited.
		text_editor_theme: 'default',						// Options: 'default' = use "theme" setting, else always 'light' or 'dark'.
		text_editor_default_view: 'text_editor_preview',	// Options: 'text_editor_preview','text_editor_html'
		text_editor_split_view: true,						// If true, show split view on plain text file load.
															// if false (default), use default text_editor_preview setting.
		text_editor_sync_scroll: true,						// If true (default: true), show split view on plain text file load
															// if false, use default text_editor_preview setting.
		hide_sidebar: false, 					// Show or hide sidebar by default
		play_all_media: true					// Continuous playback of all media files (i.e., both audio and video), not just selected media type.

		//--------------------------------------------------------//
		// Paste your exported settings between the above two lines.
	};

	// $ITEM_KIND:
	// DO NOT DELETE ANY EXISTING CATEGORIES!
	// Add file extensions for sorting and custom icon display to the existing categories.
	// You can also define your own new categories, but do not add an extension to more than one row_type category.
	// Do not add leading "." to the extensions.
	const $item_kind = {
		// myRowType: ['ext1','ext2'],
		dir:			['/'], // loaded in iframe#content_iframe
		app:			['app/','app','bat','cgi','com','exe','jar','msi','wsf'], // generally ignored; apps may be opened as directories
		alias:			['alias','desktop','directory','lnk','symlink','symlink/'],
		archive:		['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx','zxp'], // ignored
		audio:			['aac','aif','aiff','ape','flac','m4a','mka','mp3','ogg','opus','wav'], // loaded in audio#audio
		bin:			['a','bundle','dll','dyld','dylib','gem','icc','msi','pyc','pyo','o','rakefile','ri','so','xml','2'], // ignored
		code:			['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','cuetxt','custom_aliases','d','default','description','dist','editorconfig','emacs','example','gemspec','gitconfig','gitignore','gitignore_global','h','hd','ini','js','json','jsx','less','list','local','login','logout','lua','mkshrc','old','pc','php','pl','plist','pre-oh-my-zsh','profile','pth','py','rb','rc','rdoc','sass','settings','sh','strings','taskrc','tcl','viminfo','vimrc','vue','yaml','yml','zlogin','zlogout','zpreztorc','zprofile','zsh','zshenv','zshrc'], //  treated as text, opened in iframe#content_iframe text editor
		database:		['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'], // ignored
		ebook:			['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'], // ignored
		font:			['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'], // opened in div#content_font
		graphics:		['afdesign','afpub','ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf','ai','arw','cr2','dng','eps','jpf','nef','psd','psd','raw','tif','tiff'], // ignored
		htm:			['htm','html','xhtm','xhtml'], // opened in iframe#content_iframe
		image:			['apng','bmp','gif','ico','jpeg','jpg','png','svg','webp'],
		link:			['url','webloc','inetloc'],
		markdown:		['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'], // treated as text, opened in iframe#content_iframe text editor
		other_ignored:	['alias','cue','xmp'],
		office:			['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'], // ignored
		playlist:		['m3u','m3u8','pls','asx','wpl','xspf'],
		pdf:			['pdf'], // open in embed#content_pdf
		system:			['DS_Store','ds_store','icon','ics','spotlight-v100/','temporaryitems/','documentrevisions-v100/','trashes/','fseventsd/','dbfseventsd','file','localized','programdata'], // ignored system items
		text:			['log','nfo','txt','readme'], // opened in iframe#content_iframe text editor
		video:			['m4v','mkv','mov','mp4','mpeg','webm'] // loaded in video#content_video
	};
	// $ROW_SETTINGS: Ignore or Exclude files by extension
	const $row_settings = {
		// ignored: $item_kind or files with extensions added here will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
		ignored: $item_kind.archive.concat( $item_kind.bin, $item_kind.database, $item_kind.graphics, $item_kind.other_ignored, $item_kind.office, $item_kind.playlist, $item_kind.system)
	};
	// ***** END USER SETTINGS ***** //

	// ************ J + M + J ************* //

	// ************************************ //
	// DON'T EDIT ANYTHING BELOW THIS LINE. //
	// ************************************ //

	// ***** UTILITIES ***** //
	function loadFileURL() {																												// ===> LOAD FILE URL
		// if window.location points to a file, change window location to file container dir, add search_param of file name; then load file container directory and load file in content pane.
		let search_params = getSearchParams();
			search_params.set( 'file', window.location.pathname.split('/').reverse()[0]);
			window.location = window.location.pathname.slice( 0,window.location.pathname.lastIndexOf('/') ) +'/?'+ search_params ;
		return;
	}
	if ( !window.location.pathname.endsWith('/') && window.top === window.self ) { loadFileURL(); } 										// load file urls
	//==============================//
	function isTopWindow() { return ( window.top === window.self || false ) }																	// ===> TOP WINDOW OR IFRAME
	function getBrowser() { //*** needs testing for new userAgentData object --> what are possible brand names?; combine with getOS()		// ===> GET BROWSER
		let brand = ( navigator.userAgentData !== undefined ? navigator.userAgentData.brands[0].brand.toLowerCase() : navigator.userAgent );
		switch(true) {
			case brand === 'chromium' || ( /chrome/.test(brand) ):	return 'is_chrome';
			case brand === 'firefox'  || ( !/chrome/.test(brand) ):	return 'is_gecko';
			case brand === 'msie'	  || ( /msie/.test(brand) ):	return 'is_explorer';
			// case brand === 'edge'	  || ( /edge/.test(brand) ):	return 'is_edge'; // need case for ms edge
			case brand === 'opera'    || ( /opera/.test(brand) ):	return 'is_opera';
			case brand === 'safari'   || ( /safari/.test(brand) ):	return 'is_safari';
		}
	}
	function getOS() { // modded from https://***stackoverflow.com/questions/38241480/detect-macos-ios-windows-android-and-linux-os-with-js	// ===> GET OS
		var platform = window.navigator.platform, macos_platforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windows_platforms = ['Win32', 'Win64', 'Windows', 'WinCE'], os = null;
		switch(true) {
			case macos_platforms.indexOf(platform) !== -1:		os = 'macos';	break;
			case windows_platforms.indexOf(platform) !== -1:	os = 'windows';	break;
			// case iosPlatforms.indexOf(platform) !== -1:		os = 'ios';		break; // just in case;
			// case /Android/.test(userAgent):					os = 'android';	break; // just in case;
			case !os && /Linux/.test(platform):					os = 'linux';	break;
		}
	  return os;
	}
	function newURL(link) {																													// ===> NEW URL
		try { return new URL(link,document.baseURI); }
		catch(error) { return; } //console.log('This link is invalid. Please check the file.'); }
	}
	function decodeURIComponentSafe(str) { 																									// ===> DECODE URI COMPONENT SAFE
		if ( !str ) { return str; }
		try { // Fix "%" error in file name; see https://***stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser ***//
			return decodeURIComponent(str.replace(/%(?![0-9a-fA-F]{2})/g,'%25') ).replace(/\"/g,'\"'); // replace % with %25 if not followed by two a-f/number; replace " with html entity
		} catch(e) {
			return str;
		}
	}
	function escapeStr(str) { str = str.replace(/([$?*+()[]|^])/g,'\\$1'); return str; }													// ===> ESCAPE STRING
	//==============================//
	const $protocol = window.location.protocol;																								// GLOBAL: protocol
	const $origin = $protocol +'//'+ window.location.host;																					// GLOBAL: origin
	  let current_location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );				// GLOBAL: current location
	const current_dir_path = current_location.replace(/([/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); 											// GLOBAL: current dir path w/o query string for display
	const current_dir = current_location.split('/').slice(-2,-1).toString();																// GLOBAL: current dir
	//==============================//
	function getSearchParams() { return new URL(window.location).searchParams; }															// ===> GET SEARCH PARAMS
	function getSearchParam(key) {																											// ===> GET SEARCH PARAM value by key
		let search_params = getSearchParams(), value = '';
		switch(true) {
			case key === 'width': 																													// width: set the stored sidebar width or use default 30%
				value = ( !search_params.has(key) || window.innerWidth === 0 ? 30 : Math.round(100 * Number.parseInt(search_params.get('width'))/window.innerWidth) ); // percentage
				break;
			default: 																														// if query_string has key/value pair, use it, else use key/value pair from $settings
				value = ( search_params.has(key) ? search_params.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
				value = value.replace('%2F','').replace('/',''); 																					// some servers add a '/' to end of query string
		}
		return value;
	}
	function setSearchParam(key, value) { let search_params = getSearchParams(); search_params.set( key, value ); updateSearchParams(search_params); }	// ===> SET SEARCH PARAM
	function removeSearchParam(key) { let search_params = getSearchParams(); search_params.delete(key); updateSearchParams(search_params); }			// ===> REMOVE SEARCH PARAM
	function makeSearchParams(params) {																										// ===> MAKE SEARCH PARAMS
		let query_str = new URLSearchParams();
		for ( let param of params ) { query_str.append(param,getSearchParam(param)); }
		return query_str;																															// return query_str
	}
	function toggleSearchParam(key) { 																										// ===> TOGGLE SEARCH PARAM by key
		let search_params = getSearchParams();
		let non_bool_prefs = { 																														// 'body_class,data-ui_pref': {'pref_name':'value'}
			'theme':						{'theme':(getSearchParam('theme') === 'light' ? 'dark' : 'light') },
			'theme_light':					{'theme':'light'},
			'theme_dark':					{'theme':'dark'},
			'text_editor_theme':			{'text_editor_theme':(getSearchParam('text_editor_theme') === 'light' ? 'dark' : 'light') },
			'text_editor_theme_default':	{'text_editor_theme':getSearchParam('theme') },
			'text_editor_theme_light': 		{'text_editor_theme':'light'},
			'text_editor_theme_dark':		{'text_editor_theme':'dark'},
			'text_editor_raw':				{'text_editor_default_view':getSearchParam('text_editor_default_view')},
			'text_editor_preview':			{'text_editor_default_view':'text_editor_preview'},
			'text_editor_html':				{'text_editor_default_view':'text_editor_html'},
			'sort_by_name':					{'sort_by':'name'},
			'sort_by_default':				{'sort_by':'default'},
			'sort_by_duration':				{'sort_by':'duration'},
			'sort_by_size':					{'sort_by':'size'},
			'sort_by_date':					{'sort_by':'date'},
			'sort_by_kind':					{'sort_by':'kind'},
			'sort_by_ext':					{'sort_by':'ext'},
			// 'sort_by_duration':			{'sort_by':'time'}, // don't add sort by time because times aren't available on initial page load
			'sort_ascending':				{'sort_direction':'sort_ascending'}, 																			// sort A-Z
			'sort_descending':				{'sort_direction':'sort_descending'}, 																			// sort Z-A
		};
		var value, param_value, settings_value;
		switch(true) {
			case non_bool_prefs[key] !== undefined:
				value = Object.values( non_bool_prefs[key] ).toString(); 																			// get the value for the key
				key = Object.keys(non_bool_prefs[key]).toString(); 																					// must come after value: i.e., don't redefine key before getting value
				setSearchParam( key,value );	 																							break;	// if the setting[key] = value, delete from search_params; else set query_pref
			default: 																																// boolean prefs
				settings_value = $settings[key]; 																									// get the default pref value from $settings
				param_value = search_params.get(key); 																								// see if pref is set in query prefs
				// key = key.replace('toggle_','');
				value = ( param_value === null ? settings_value.toString() : param_value.toString() ); 												// if pref not set in queries, use default value; else use query value
				value = ( value === 'true' ? 'false' : 'true' ); 																					// toggle value
				if ( ( param_value !== null && param_value !== settings_value ) ) { removeSearchParam( key ); } else { setSearchParam( key,value ); }
		}
	}
	function updateSearchParams(search_params) {																							// ===> UPDATE SEARCH PARAMS
		let search_params_str = search_params.toString().replace('%2F','').replace('/','');
		let new_location = ( search_params_str.length === 0 ? window.location.pathname : window.location.pathname +'?'+ search_params_str );		// don't add ? if no search params
		if ( search_params_str.length > 0 ) { window.history.replaceState({}, document.title, new_location); }
		if ( isTopWindow() ) { updateParentLinks(); }
	}
	//==============================//
	function hasClass(elSelector,className) { let el = document.querySelector(elSelector); return ( el === undefined || el === null ? false : el.classList.contains(className) ); }	// ===> HAS CLASS
	function addClass(elSelector,className) {																								// ===> ADD CLASS
		let els = Array.from(document.querySelectorAll(elSelector));
		if ( els !== undefined && els !== null ) { els.forEach( el => el.classList.add(...className.replace(/\s{2,}/g,' ').split(' ')) ); }
	}
	function removeClass(elSelector,className) {																							// ===> REMOVE CLASS; if no className provided, remove all classes
		let classes = (className !== undefined ? className.replace(/\s{2,}/g,' ').split(' ') : '');
		let els = Array.from(document.querySelectorAll(elSelector));
		if ( els !== undefined && els !== null ) {
			switch(true) {
				case className === undefined: case className === '':	els.forEach( el => el.removeAttribute('class') ); break;
				default: 												els.forEach( el => el.classList.remove(...classes) ); break;
			}
		}
	}
	function addRemoveClassSiblings(elSelector,className) {																					// ===> ADD/REMOVE CLASS SIBLINGS, add to elements
		let el = document.querySelector(elSelector);
		if ( el !== undefined && el !== null ) {
			let siblings = el.parentElement.children;
			Array.from(siblings).forEach( sibling => sibling.classList.remove(...className.replace(/\s{2,}/g,' ').split(' ') ) ); 					// remove class name from els
			addClass(elSelector,className); 																										// restore class name to selected el
		}
	}
	//==============================//
	function getAttribute(elSelector,attributeName) 		{ let el = document.querySelector(elSelector); if ( el !== null ) { return el.getAttribute(attributeName); } }	// ===> GET ATTRIBUTE
	function setAttribute(elSelector,attributeName,value)	{ let el = document.querySelector(elSelector); if ( el !== null ) { el.setAttribute(attributeName,value); } }	// ===> SET ATTRIBUTE
	function removeAttribute(elSelector,attributeNamesArr) {						 																						// ===> REMOVE ATTRIBUTE
		let els = document.querySelectorAll(elSelector); 	if ( els !== null ) { Array.from(els).forEach( el => attributeNamesArr.forEach( attributeName => el.removeAttribute(attributeName) ) ); }
	}
	function getContentPaneData() { return ( document.getElementById('content_pane') !== null ? getAttribute('#content_pane','data-content') : ''); } // ===> GET CONTENT_PANE DATA content
	function getVisibleElsBySelector(selector)		{																						// ===> GET VISIBLE ELS BY SELECTOR
		let els = Array.from( document.querySelectorAll(selector) ).filter( function(el) {
			let el_styles = window.getComputedStyle(el); 																	// get el styles
			return ( el_styles.getPropertyValue('display') !== 'none' || ( el.offsetWidth > 0 || el.offsetHeight > 0 ) );	// return els with display:none or zero width/height
		});
		return els;
	}
	//==============================//
	function dblclick(el,func) 	{ var evt = new MouseEvent('dblclick'); el.addEventListener(evt,func); el.dispatchEvent(evt); }				// ===> DOUBLE CLICK
	function altKey(e)			{ return ( !e.metaKey && !e.ctrlKey && e.altKey && !e.shiftKey ); }											// ===> ALT KEY test
	function altShiftKey(e)		{ return ( !e.metaKey && !e.ctrlKey && e.altKey && e.shiftKey ); }											// ===> ALT SHIFT KEY test
	function cmdKey(e)			{ return ( (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey ); }										// ===> CMD/CTRL KEY test
	function cmdAltKey(e)		{ return ( (e.metaKey || e.ctrlKey) && e.altKey && !e.shiftKey ); }											// ===> CMD/CTRL ALT KEY test
	function cmdShiftKey(e)		{ return ( (e.metaKey || e.ctrlKey) && !e.altKey && e.shiftKey ); }											// ===> CMD/CTRL SHIFT KEY test
	// function cmdAltShiftKey(e)	{ return ( (e.metaKey || e.ctrlKey) && e.altKey && e.shiftKey; }										// ===> CMD/CTRL ALT SHIFT KEY test (not used)
	//==============================// OPEN/SAVE FILES
	function openFile(e,type) { // type: font or playlist.																					// ===> OPEN FILE
		removeClass('body','has_menu faded');
		if (window.File && window.FileReader && window.FileList && window.Blob) {
			let files = e.target.files[0];
			let reader = new FileReader();
			if ( type === 'font' ) 			{ reader.readAsArrayBuffer(files); }
			if ( type === 'playlist' )		{ reader.readAsText(files); }
			reader.onload = function() {
				if ( type === 'font' ) 		{ openFontFile(files,reader); }
				if ( type === 'playlist' )	{ openPlaylist(files.name,'',reader.result); }
				return true;
			};
		} else {
			alert('Can\'t open file: file APIs are not fully supported in this browser.');
		}
	}
	function saveFile(content,mimetype,file_name) {																							// ===> SAVE FILE
		let blob = new Blob([content], {type: mimetype});
		let $download_el = window.document.createElement('a');
			$download_el.style = "display:none";
			$download_el.href = window.URL.createObjectURL(blob);
			$download_el.download = file_name;
		document.body.appendChild($download_el);
		$download_el.click();
		document.body.removeChild($download_el);
		URL.revokeObjectURL(blob);
	}
	// END UTILITIES
	//==============================//
	// ***** SET UP UI ELEMENTS ***** //
	function updateParentLinkSearchParams(str) { //*** decrement selected and history values ***//											// ===> UPDATE PARENT LINK SEARCH PARAMS
		let query_str = new URLSearchParams(str); 																									// make new search params from window.location.search
		let history = ( query_str.has('history') ? query_str.get('history') : undefined );
		switch(true) {
			case history !== undefined:
				history = history.split(' ');
				switch(true) {
					case history.length > 1:	query_str.set('selected',history[0]); 	history.shift(); 	query_str.set('history',history.join('+')); break;
					case history.length === 1:	query_str.set('selected',history[0]); 	history.shift();	query_str.delete('history'); break;
				}
				break;
			default: query_str.delete('selected');
		}
		return decodeURIComponentSafe(query_str.toString());
	}
	function createParentLinks() {																											// ===> CREATE PARENT LINKS
		let link, links = [], query_str = window.location.search.toString().slice(1);
		let link_pieces = current_location.split('/');																								// make array of parent directories
			link_pieces = link_pieces.slice(2,-2); 																									// remove beginning and ending empty elements and current directory
		while ( link_pieces.length > 0 ) {															 												// while there are link pieces...
			query_str = updateParentLinkSearchParams(query_str); 																					// update selected and history
			link = $protocol +'//'+ link_pieces.join('/') + '/?' + query_str;																		// assemble link
			links.push(link);																														// add to link array
			link_pieces.pop();																														// remove last link piece and repeat...
		}
		return links;
	}
	function createParentLinkItems() {																										// ===> CREATE PARENT LINK ITEMS
		let parent_link_menu_items = [], links = createParentLinks();
		for ( let i = 0; i < links.length; i++ ) {
			let display_name = links[i].split('/?')[0];
				display_name = display_name.replace(/\//g,'\/<wbr>');
			let menu_item = `<li><a href="${ links[i] }" class="display_block">${ display_name }/</a></li>`;
			parent_link_menu_items.push(menu_item);
		}
		let parent_link = ( links[0] === undefined ? window.location.href : links[0])
		return [parent_link_menu_items.join(''),parent_link];																						// return parents link items
	}
	function updateParentLinks() { let links = createParentLinkItems();  document.getElementById('parents_links').innerHTML = links[0]; document.querySelector('#parent_dir_nav a').href = links[1]; } // ===> UPDATE PARENT LINKS
	function bookmarksMenuItems() {																											// ===> BOOKMARK MENU ITEMS: create user bookmarks from $setting
		const bookmarks = $settings.bookmarks;
		  let menu_items = [], links_arr = [], links_arr_str = '', links;
		if ( bookmarks.length > 0 ) {
			for ( let i = 0; i < bookmarks.length; i+=1 ) {
				links = bookmarks[i].links;
				for ( let j = 0; j < links.length; j+=1 ) {
					if ( !links[j].link.endsWith('/') ) {
						links[j].link = links[j].link.slice(0,links[j].link.lastIndexOf('/') + 1) + '?file=' + links[j].link.slice(links[j].link.lastIndexOf('/') + 1) ;
					}
					links[j].link_name = links[j].link_name.split('/').join('/<wbr>');
					links_arr[j] = `<li class="is_submenu"><a class="menu_item has_icon_before" href="${ links[j].link }">${ links[j].link_name }</a></li>`;
				}
				links_arr_str = links_arr.join('');
				menu_items[i] = `	<li class="bookmark has_submenu" title="User bookmarks (added in code @ $settings)."><span class="menu_item">${ bookmarks[i].menu_title }</span>${SVG_UI_Icons['arrow']}
										<ul class="submenu background_grey_85 border_all">${ links_arr_str }</ul>
									</li>`;
			}
			menu_items = menu_items.join('');
		}
		return menu_items;
	}
	//==============================//
	// SVG UI ICONS
	const SVG_UI_Icons = {
		'arrow':				'<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\' class=\'invert\'><path fill=\'%23888\' fill-opacity=\'.75\' d=\'m4 4 12 6-12 6z\'/></svg>',
		'bookmark':				'<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\' class=\'invert\'><path fill=\'%23888\' d=\'m2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4zm2 0v15l6-3 6 3v-15z\'/></svg>',
		'check_mark':			'<svg viewBox=\'0 0 12 9\' xmlns=\'http://www.w3.org/2000/svg\'><path fill=\'currentColor\' d=\'m-.071 10.929 2.571-2.571 4.5 4.499 10.285-10.285 2.571 2.572-12.856 12.856z\' transform=\'matrix(.55 0 0 .55 .578932 -1.01245)\'/></svg>',
		'chevron':				'<svg viewBox=\'0 0 24 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0v14h3v-11h11v-3z\' transform=\'matrix(.707107 .707107 -.707107 .707107 11.8995 1)\'/></svg>',
		'document':				'<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\'><path fill=\'%23222222\' d=\'M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z\' /></svg>',
		'error':				'<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\'><g fill-opacity=\'.75\'><path fill=\'%23444\' d=\'m1.075 18.05 8.146-16.683c.236-.484.924-.491 1.169-.011l8.537 16.683c.223.435-.093.952-.582.952h-16.683c-.483 0-.799-.507-.587-.941z\' fill=\'%23ffb636\'/><path d=\'m11.055 7.131-.447 6.003c-.034.45-.425.787-.874.753-.408-.03-.724-.356-.753-.753l-.447-6.003c-.052-.696.47-1.302 1.167-1.354.696-.052 1.302.47 1.354 1.166.005.061.004.129 0 .188zm-1.26 8.037c-.641 0-1.159.518-1.159 1.158 0 .641.518 1.159 1.159 1.159.64 0 1.158-.518 1.158-1.159 0-.64-.518-1.158-1.158-1.158z\'/></g></svg>',
		'folder':				'<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-16a2 2 0 0 1 -2-2zm2 2v10h16v-10z\'/></svg>',
		'grid':					'<svg viewBox=\'0 0 20 20\'><path fill=\'currentColor\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
		'menu':					'<svg viewBox=\'0 0 13 10\'><g fill=\'%23222\'><path d=\'m0 0h13v2h-13z\'/><path d=\'m0 4h13v2h-13z\'/><path d=\'m0 8h13v2h-13z\'/></g></svg>',
		'minus':				'<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m1 8h18v4h-18z\'/></svg>',
		'multiply':				'<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m10 7 6-6 3 3-6 6 6 6-3 3-6-6-6 6-3-3 6-6-6-6 3-3z\'/></svg>',
		'music':				'<svg viewBox=\'0 0 20 20\'><path fill=\'%23888\' fill-opacity=\'.4\' d=\'m15.987 13.982c0 .906-.413 1.664-1.239 2.274-.757.554-1.604.831-2.541.831-.548 0-.998-.129-1.348-.388-.389-.295-.583-.708-.583-1.238 0-.838.398-1.574 1.192-2.209.752-.597 1.559-.896 2.421-.896.727 0 1.257.145 1.59.434v-9.489l-6.755 1.82v10.774c0 .906-.413 1.663-1.238 2.273-.758.555-1.605.832-2.541.832-.549 0-.998-.13-1.35-.388-.388-.296-.582-.709-.582-1.238 0-.838.398-1.574 1.192-2.209.752-.597 1.559-.896 2.421-.896.727 0 1.257.145 1.589.434v-11.605l7.772-2.098z\'/></svg>',
		'plus':					'<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m8.001 1h3.999v7h7v4h-7l-.001 7h-3.999v-7h-7v-4h7z\'/></svg>',
		'prev_next_track':		'<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m13 5h2v10h-2zm-8 0 8 5-8 5z\'/></svg>',
		'spinner':				'<svg viewBox=\'0 0 100 100\' class=\'display_none invert\' preserveAspectRatio=\'xMidYMid\' width=\'32\' height=\'32\' xmlns=\'http://www.w3.org/2000/svg\'><animateTransform attributeName=\'transform\' type=\'rotate\' values=\'0;45\' keyTimes=\'0;1\' dur=\'0.25s\' repeatCount=\'indefinite\'/><path fill=\'%23000\' fill-opacity=\'.5\' d=\'m29.49-5.5h8v11h-8a30 30 0 0 1 -4.75 11.46l5.66 5.66-7.78 7.78-5.66-5.66a30 30 0 0 1 -11.46 4.75v8h-11v-8a30 30 0 0 1 -11.46-4.75l-5.66 5.66-7.78-7.78 5.66-5.66a30 30 0 0 1 -4.75-11.46h-8v-11h8a30 30 0 0 1 4.75-11.46l-5.66-5.66 7.78-7.78 5.66 5.66a30 30 0 0 1 11.46-4.75v-8h11v8a30 30 0 0 1 11.46 4.75l5.66-5.66 7.78 7.78-5.66 5.66a30 30 0 0 1 4.75 11.46m-29.49-14.5a20 20 0 1 0 0 40 20 20 0 1 0 0-40\' transform=\'matrix(.7189408 .69507131 -.69507131 .7189408 50 50)\'/></svg>',
		'toggle':				'<svg viewBox=\'0 0 20 20\'><g fill=\'%23222\'><path d=\'m10.207 9.293-.707.707 5.657 5.657 1.414-1.414-4.242-4.243 4.242-4.243-1.414-1.414z\'/><path d=\'m4.207 9.293-.707.707 5.657 5.657 1.414-1.414-4.242-4.243 4.242-4.243-1.414-1.414z\'/></g></svg>',
	};
	const SVG_UI_File_Icons = { // n.b.: order is important
		'file_icon_dir':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6 2.5-1-1.5h-5v12h14v-10.5z\' fill=\'%2339f\'/><path d=\'m1.5 4h11v7.5h-11z\' fill=\'%239cf\'/></svg>',
		'file_icon_dir_open':		'<svg viewBox=\'0 0 14 14\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.1 2.7-1.3-1.7h-4.8v12h14v-10.3z\' fill=\'%2339f\' fill-rule=\'nonzero\'/><path d=\'m7 6h5.5v5.5h-11z\' fill=\'%239cf\'/></svg>',
		'file_icon_file':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8.3 0h-6.8v14h11v-9.8l-4.2-4.2z\' fill=\'%23888\'/><g fill=\'%23fff\'><path d=\'m11 12.5h-8v-11h3.8v4.2h4.2z\'/><path d=\'m8.3 4.2h1.9l-1.9-2z\'/></g></svg>',
		'file_icon_invisible':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8.3 0h-6.8v14h11v-9.8l-4.2-4.2z\' fill=\'%23888\'/><path d=\'m11 12.5h-8v-11h3.8v4.2h4.2z\' fill=\'%23bbb\'/><path d=\'m8.3 4.2h1.9l-1.9-2z\' fill=\'%23bbb\'/><circle cx=\'7\' cy=\'9\' fill=\'%23878787\' r=\'1.5\'/></svg>',
		'file_icon_ignored':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'M 10.695,1.774 1.856,10.613 3.482,12.239 12.321,3.4 Z M 7,2 c 2.8,0 5,2.2 5,5 0,2.8 -2.2,5 -5,5 C 4.2,12 2,9.8 2,7 2,4.2 4.2,2 7,2 M 7,0 C 3.1,0 0,3.1 0,7 c 0,3.9 3.1,7 7,7 3.9,0 7,-3.1 7,-7 C 14,3.1 10.9,0 7,0 Z\' style=\'fill:%23888888;fill-opacity:1\' /></svg>',
		'file_icon_dirinvisible':	'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6 2.5-1-1.5h-5v12h14v-10.5z\' fill=\'%23888\'/><path d=\'m1.5 4h11v7.5h-11z\' fill=\'%23bbb\'/><circle cx=\'7\' cy=\'7.5\' fill=\'%23888\' r=\'1.5\'/></svg>',
		'file_icon_alias':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><path d=\'m3 12.5c0-3.863 2.253-7.5 6.259-7.5\' fill=\'none\' stroke=\'%23fc6\' stroke-width=\'3\'/><path d=\'m13 5-4-4v8z\' fill=\'%23fc6\'/></svg>',
		'file_icon_archive':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m11 8.5v-1h2v2l-5 1h-2v1.5h4v1h-4v1h-3v-1h-2v-1h2v-1.5h-2v-2h2v-6.5h-2v-2h7l5 1v2h-2v-1h-5v6.5z\' fill=\'%23666\'/></svg>',
		'file_icon_app':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.125 0-.292 1.859c-.587.135-1.146.38-1.64.693v-.018l-1.532-1.094-1.221 1.221 1.094 1.532h.018c-.313.495-.559 1.051-.693 1.64l-1.859.292v1.75l1.859.292c.134.589.38 1.145.693 1.64h-.018l-1.094 1.532 1.221 1.221 1.532-1.094v-.018c.494.313 1.053.558 1.64.693l.292 1.859h1.75l.292-1.859c.596-.137 1.14-.372 1.64-.693l1.532 1.112 1.221-1.221-1.112-1.532c.309-.492.523-1.057.656-1.64l1.896-.292v-1.75l-1.896-.292c-.133-.583-.347-1.148-.656-1.64h.018l1.094-1.532-1.221-1.221-1.532 1.094v.018c-.5-.321-1.044-.556-1.64-.693l-.292-1.859h-1.75zm.875 4.667c1.288 0 2.333 1.036 2.333 2.333s-1.045 2.333-2.333 2.333-2.333-1.036-2.333-2.333 1.045-2.333 2.333-2.333z\' fill=\'%237a7ab8\'/></svg>',
		'file_icon_audio':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><circle cx=\'7\' cy=\'7\' fill=\'%230f8a8a\' r=\'7\'/><g fill=\'%23fff\'><path d=\'m11 9.5c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5c.268-.038.523-.031.75.013v-4.488h-4v6.5c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5c.268-.038.523-.031.75.013v-6.488l6-1z\'/><path d=\'m11 2-6 1v2l6-1z\'/></g></svg>',
		'file_icon_code':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%2372d\'/><g fill=\'%23fff\'><path d=\'m5.923 12.965c-1.049 0-1.784-.161-2.209-.48-.425-.317-.638-.82-.638-1.503v-2.067c0-.446-.146-.764-.438-.95-.292-.188-.709-.281-1.256-.281v-1.368c.547 0 .967-.094 1.259-.28s.438-.5.438-.938v-2.092c0-.675.217-1.172.65-1.491.432-.32 1.164-.479 2.195-.479v1.312c-.401.01-.718.09-.952.24-.233.15-.348.426-.348.827v1.985c0 .876-.511 1.396-1.532 1.559v.083c1.021.154 1.532.67 1.532 1.544v1.997c0 .41.116.688.349.835.233.146.55.223.951.232z\'/><path d=\'m8.076 12.965v-1.313c.392-.009.706-.089.944-.239.236-.15.355-.426.355-.829v-1.996c0-.867.511-1.382 1.531-1.545v-.084c-1.02-.164-1.53-.679-1.53-1.546v-1.997c0-.41-.116-.688-.349-.834-.232-.146-.549-.224-.951-.233v-1.313c1.049 0 1.785.159 2.21.479.423.319.637.821.637 1.505v2.065c0 .447.146.765.438.951.292.187.711.28 1.257.28v1.367c-.546.012-.967.107-1.259.287-.293.183-.438.5-.438.945v2.08c0 .674-.217 1.172-.65 1.491-.432.319-1.165.479-2.195.479z\'/></g></svg>',
		'file_icon_database':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 2.5v9c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5v-9\' fill=\'%23808080\'/><path d=\'m13 2.5v9c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5v-9\' fill=\'%23b4b4b4\'/><path d=\'m14 8.5c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5\' fill=\'%23808080\'/><path d=\'m13 8.5c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5\' fill=\'%23b4b4b4\'/><path d=\'m14 5.5c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5\' fill=\'%23808080\'/><path d=\'m13 5.5c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5\' fill=\'%23b4b4b4\'/><ellipse cx=\'7\' cy=\'2.5\' fill=\'%23808080\' rx=\'7\' ry=\'2.5\'/><ellipse cx=\'7\' cy=\'2.5\' fill=\'%23b4b4b4\' rx=\'5.5\' ry=\'1.5\'/></svg>',
		'file_icon_ebook':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m2.668-.001c1.705.001 3.492.35 4.332 1.257.84-.908 2.627-1.256 4.332-1.257h2.668v12.541c-.818 0-2.181.005-3 .023-1.184.026-3.008.42-3 1.437l-1-.017-1 .017c.008-1.017-2-1.437-3-1.437-.819 0-2.182-.023-3-.023v-12.541h2.668z\' fill=\'%23808080\'/><g fill=\'%23cdcdcd\'><path d=\'m1.5 1.499v9.501h1.286c1.086.025 2.213.081 3.204.568l.01.006c0-2.859 0-5.717 0-8.576 0-1.136-1.49-1.398-2.336-1.47-.708-.059-1.438-.029-2.164-.029z\'/><path d=\'m12.5 1.499v9.501h-1.286c-1.086.025-2.213.081-3.204.568l-.01.006c0-2.859 0-5.717 0-8.576 0-1.136 1.49-1.398 2.336-1.47.708-.059 1.438-.029 2.164-.029z\'/></g></svg>',
		'file_icon_font':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%23709\'/><path d=\'m4.678 11.179h1.393v-8.266h-2.616v1.052h-1.455v-2.553h10v2.554h-1.456v-1.053h-2.599v8.266h1.347v1.409h-4.614z\' fill=\'%23fff\'/></svg>',
		'file_icon_graphics':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><path d=\'m7.774 8.285 4.726 4.715-8-3.525-1.5-4.975h-2v-3.5h3.525l-.025 2 5 1.5 3.5 8-4.7-4.752c.127-.22.2-.476.2-.748 0-.828-.672-1.5-1.5-1.5s-1.5.672-1.5 1.5.672 1.5 1.5 1.5c.283 0 .548-.079.774-.215z\' fill=\'%23ccc\'/></svg>',
		'file_icon_htm':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.967.5c-3.553.018-6.467 2.947-6.467 6.5 0 3.566 2.934 6.5 6.5 6.5s6.5-2.934 6.5-6.5c0-3.553-2.914-6.482-6.467-6.5zm.033 0v13m6.5-6.5h-13m1.467-4c3.004 2.143 7.062 2.143 10.066 0m0 8c-3.004-2.143-7.062-2.143-10.066 0m4.533-10.333c-1.874 1.582-2.957 3.914-2.957 6.366 0 2.453 1.083 4.785 2.957 6.367m1 0c1.874-1.582 2.957-3.914 2.957-6.367 0-2.452-1.083-4.784-2.957-6.366\' fill=\'%23fff\' stroke=\'%23e44d26\'/></svg>',
		'file_icon_ignoredimage':	'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m.369 9.141c-.252-.678-.369-1.396-.369-2.141 0-3.863 3.137-7 7-7s7 3.137 7 7l-.137 1.353-3.853-3.853-3.5 3.5-2.5-2.5z\' fill=\'%23808080\'/><path d=\'m.839 10.151-.47-1.01 3.641-3.641 2.5 2.5 3.5-3.5 3.853 3.853c-.076.395-.201.778-.341 1.147l-10.371 3.345c-.293-.194-.579-.416-.838-.651z\' fill=\'%23fff\'/><path d=\'m13.522 9.5c-.99 2.64-3.539 4.5-6.522 4.5-1.426 0-2.753-.421-3.849-1.155l6.859-6.866z\' fill=\'%23808080\'/><path d=\'m.839 10.151 3.171-3.172 1.761 1.761-3.459 3.454c-.591-.632-1.079-1.313-1.473-2.043z\' fill=\'%23808080\'/><circle cx=\'6\' cy=\'3.5\' fill=\'%23fff\' r=\'1.5\'/></svg>',
		'file_icon_image':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m.369 9.141c-.252-.678-.369-1.396-.369-2.141 0-3.863 3.137-7 7-7s7 3.137 7 7l-.137 1.353-3.853-3.853-3.5 3.5-2.5-2.5z\' fill=\'%238080ff\'/><path d=\'m.839 10.151-.47-1.01 3.641-3.641 2.5 2.5 3.5-3.5 3.853 3.853c-.076.395-.201.778-.341 1.147l-10.371 3.345c-.293-.194-.579-.416-.838-.651z\' fill=\'%23fff\'/><path d=\'m13.522 9.5c-.99 2.64-3.539 4.5-6.522 4.5-1.426 0-2.753-.421-3.849-1.155l6.859-6.866z\' fill=\'%2333c\'/><path d=\'m.839 10.151 3.171-3.172 1.761 1.761-3.459 3.454c-.591-.632-1.079-1.313-1.473-2.043z\' fill=\'%2333c\'/><circle cx=\'6\' cy=\'3.5\' fill=\'%23fff\' r=\'1.5\'/></svg>',
		'file_icon_markdown':		'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%236a6a95\'/><path d=\'m12 11.5h-2.5v-5.143l-2.5 2.948-2.5-2.948v5.143h-2.5v-9h2.273l2.721 3.377 2.733-3.377h2.273z\' fill=\'%23ddd\'/></svg>',
		'file_icon_office':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><g fill=\'%23cdcdcd\'><path d=\'m10 1.5h2.5v1h-2.5z\'/><path d=\'m10 4h2.5v1h-2.5z\'/><path d=\'m10 6.5h2.5v1h-2.5z\'/><path d=\'m10 9h2.5v1h-2.5z\'/><path d=\'m10 11.5h2.5v1h-2.5z\'/><path d=\'m6.5 1.5h2.5v1h-2.5z\'/><path d=\'m6.5 4h2.5v1h-2.5z\'/><path d=\'m6.5 6.5h2.5v1h-2.5z\'/><path d=\'m6.5 9h2.5v1h-2.5z\'/><path d=\'m6.5 11.5h2.5v1h-2.5z\'/><path d=\'m1.5 1.5h4v11h-4z\'/></g></svg>',
		'file_icon_pdf':			'<svg clip-rule=\'evenodd\' fill-rule=\'evenodd\' viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h13.999986v13.999986h-13.999986z\' fill=\'%23e0382d\' stroke-width=\'.259259\'/><path d=\'m45 31.2c-2.6-2.7-9.7-1.6-11.4-1.4-2.5-2.4-4.2-5.3-4.8-6.3.9-2.7 1.5-5.4 1.6-8.3 0-2.5-1-5.2-3.8-5.2-1 0-1.9.6-2.4 1.4-1.2 2.1-.7 6.3 1.2 10.6-1.1 3.1-2.1 6.1-4.9 11.4-2.9 1.2-9 4-9.5 7-.2.9.1 1.8.8 2.5.7.6 1.6.9 2.5.9 3.7 0 7.3-5.1 9.8-9.4 2.1-.7 5.4-1.7 8.7-2.3 3.9 3.4 7.3 3.9 9.1 3.9 2.4 0 3.3-1 3.6-1.9.5-1 .2-2.1-.5-2.9zm-2.5 1.7c-.1.7-1 1.4-2.6 1-1.9-.5-3.6-1.4-5.1-2.6 1.3-.2 4.2-.5 6.3-.1.8.2 1.6.7 1.4 1.7zm-16.7-20.6c.2-.3.5-.5.8-.5.9 0 1.1 1.1 1.1 2-.1 2.1-.5 4.2-1.2 6.2-1.5-4-1.2-6.8-.7-7.7zm-.2 19.4c.8-1.6 1.9-4.4 2.3-5.6.9 1.5 2.4 3.3 3.2 4.1 0 .1-3.1.7-5.5 1.5zm-5.9 4c-2.3 3.8-4.7 6.2-6 6.2-.2 0-.4-.1-.6-.2-.3-.2-.4-.5-.3-.9.3-1.4 2.9-3.3 6.9-5.1z\' fill=\'%23fff\' fill-rule=\'nonzero\' transform=\'matrix(.344737 0 0 .35503 -2.77114 -2.5503)\'/></svg>',
		'file_icon_playlist':		'<svg viewBox=\'0 0 14 14\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%230f8a8a\' fill-rule=\'nonzero\'/><path d=\'m1.5 1.5h8v1h-8zm0 2.5h8v1h-8zm0 2.5h8v1h-8zm0 2.5h7v1h-7zm0 2.5h5.5v1h-5.5zm9.5-10h1v10c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5a2.28 2.28 0 0 1 .75.013z\' fill=\'%23fff\'/></svg>',
		'file_icon_text':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%236a6a95\'/><g fill=\'%23fff\'><path d=\'m6.5 1.5h6v1h-6z\'/><path d=\'m1.5 1.5h3.5v3.5h-3.5z\'/><path d=\'m1.5 6.5h11v1h-11z\'/><path d=\'m6.5 4h6v1h-6z\'/><path d=\'m1.5 11.5h8v1h-8z\'/><path d=\'m1.5 9h11v1h-11z\'/></g></svg>',
		'file_icon_video':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 14v-14h-14v14z\'/><g fill=\'%23fff\'><path d=\'m9.5 3v-2h-2v2z\'/><path d=\'m3.5 3v-2h-2v2z\'/><path d=\'m6.5 3v-2h-2v2z\'/><path d=\'m12.5 3v-2h-2v2z\'/><path d=\'m9.5 13v-2h-2v2z\'/><path d=\'m3.5 13v-2h-2v2z\'/><path d=\'m6.5 13v-2h-2v2z\'/><path d=\'m12.5 13v-2h-2v2z\'/></g><path d=\'m12.5 10v-6h-11v6z\' fill=\'%23eda412\'/></svg>',
		'file_icon_bin': '', 		'file_icon_other': '',		// <-- these two use file_icon_system:
		'file_icon_system':			'<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><g fill=\'%23ccc\'><path d=\'m1.247 6.495h3.263v-1.067h-.881v-3.835h-.974c-.371.232-.727.371-1.284.479v.82h.928v2.536h-1.052z\'/><path d=\'m7 6.588c1.082 0 1.825-.89 1.825-2.567 0-1.67-.743-2.521-1.825-2.521s-1.825.843-1.825 2.521c0 1.677.743 2.567 1.825 2.567zm0-1.021c-.309 0-.572-.247-.572-1.546s.263-1.5.572-1.5.572.201.572 1.5-.263 1.546-.572 1.546z\'/><path d=\'m9.598 6.495h3.263v-1.067h-.882v-3.835h-.974c-.371.232-.727.371-1.283.479v.82h.927v2.536h-1.051z\'/><path d=\'m2.825 12.588c1.082 0 1.824-.89 1.824-2.567 0-1.67-.742-2.521-1.824-2.521-1.083 0-1.825.843-1.825 2.521 0 1.677.742 2.567 1.825 2.567zm0-1.021c-.31 0-.572-.247-.572-1.546s.262-1.5.572-1.5c.309 0 .572.201.572 1.5s-.263 1.546-.572 1.546z\'/><path d=\'m5.423 12.495h3.263v-1.067h-.882v-3.835h-.974c-.371.232-.727.371-1.284.479v.82h.928v2.536h-1.051z\'/><path d=\'m11.175 12.588c1.083 0 1.825-.89 1.825-2.567 0-1.67-.742-2.521-1.825-2.521-1.082 0-1.824.843-1.824 2.521 0 1.677.742 2.567 1.824 2.567zm0-1.021c-.309 0-.572-.247-.572-1.546s.263-1.5.572-1.5c.31 0 .572.201.572 1.5s-.262 1.546-.572 1.546z\'/></g></svg>'
	};
	const SVG_Text_Editing_UI_Icons = {
		'toggle_theme':		'<svg viewBox=\'0 0 16 16\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8 0c4.415 0 8 3.585 8 8s-3.585 8-8 8-8-3.585-8-8 3.585-8 8-8zm0 2c3.311 0 6 2.689 6 6s-2.689 6-6 6z\' fill=\'%23333\'/></svg>',
		'show_markdown':	'<svg viewBox=\'0 0 100 60\' xmlns=\'http://www.w3.org/2000/svg\' width=\'100\'><g fill=\'%23333\'><path d=\'M42.215 60l.17-46.24h-.255L30.06 60h-7.99L10.255 13.76H10L10.169 60H.905V-.18H14.59l11.56 44.03h.34L37.794-.18H52.16V60h-9.945zM99.589 29.996c0 9.519-1.997 16.901-5.992 22.142C89.602 57.38 83.722 60 75.959 60H60.914V-.18h15.13c7.706 0 13.558 2.65 17.553 7.948 3.995 5.299 5.992 12.708 5.992 22.228zm-10.2 0c0-3.57-.326-6.686-.978-9.35-.651-2.663-1.572-4.873-2.762-6.63-1.19-1.756-2.607-3.073-4.25-3.953-1.645-.878-3.43-1.317-5.355-1.317h-4.845v42.33h4.845c1.926 0 3.711-.438 5.355-1.317 1.643-.878 3.06-2.195 4.25-3.953 1.189-1.756 2.11-3.952 2.762-6.587.651-2.637.978-5.709.978-9.223z\'/></g></svg>',
		'show_source':		'<svg viewBox=\'0 0 22 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path fill=\'none\' d=\'M0 0h21.996v14H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.996v14H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M0 7.393v-.786l6.062-3.5.75 1.3L2.32 7l4.492 2.593-.75 1.3L0 7.393zM21.996 6.607v.786l-6.062 3.5-.75-1.3L19.676 7l-4.492-2.593.75-1.3 6.062 3.5zM15.15 1.313l-1.3-.75-7 12.124 1.3.75 7-12.124z\'/></g></svg>',
		'show_preview':		'<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path d=\'M10 2.5V1H0v1.5h4V15h2V2.5h4zM9 6.5V8h2v4.053c0 2.211 1.547 3.442 3 3.442.989 0 1.556-.258 2-.495v-1.5c-.565.257-.882.376-1.507.376-.847 0-1.493-.474-1.493-1.876V8h2.5V6.5H13v-3h-1.98v3H9z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
		'show_html':		'<svg viewBox=\'0 0 22 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path fill=\'none\' d=\'M0 0h21.022v16H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.022v16H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M7.732.222L9.5 1.99 3.49 8l6.01 6.01-1.768 1.768L-.046 8 7.732.222zM13.268 15.778L11.5 14.01 17.51 8 11.5 1.99 13.268.222 21.046 8l-7.778 7.778z\'/></g></svg>',
		'toggle_split':		'<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path d=\'M0 0v16h16V0H0zm14 14H9V2h5v12zm-7 0H2V2h5v12z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
		'save_btn':			'<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><g fill=\'currentColor\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>',
		'save_btn_edited':	'<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><g fill=\'%23DD2222\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>'
	};
	function get_SVG_UI_Icon(icon_name) 				{ return `url("data:image/svg+xml;utf8,${ SVG_UI_Icons[icon_name] }")`; } 			// ===> GET SVG UI ICON by name
	function get_SVG_UI_File_Icon(icon_name) {																								// ===> GET SVG UI FILE Icon by name
		switch(icon_name) {
			case 'favicon': return 'iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC';
			case 'file_icon_dir_default': return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII= ")';
			case 'file_icon_file_default': return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
			default: return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
		}
	}
	function CSS_UI_Icon_Rules() {	// programatically add File icon CSS rules																// ===> CSS UI ICON RULES
		let rules = '', kind, class_name;
			rules = `
				#menu ul a::before													{ background-image:${ get_SVG_UI_File_Icon('file_icon_file') }; }
				#menu ul a[href^="file"]::before 									{ background-image:${ get_SVG_UI_File_Icon('file_icon_dir') }; }
				#menu ul a[href^="http"]::before 									{ background-image:${ get_SVG_UI_File_Icon('file_icon_htm') }; }
				body:not(.use_custom_icons) .dir .has_icon_before_before 			{ background-image:${ get_SVG_UI_File_Icon('file_icon_dir_default') }; background-size:auto 13px; }
				body:not(.use_custom_icons) .file:not(.app) .has_icon_before_before { background-image:${ get_SVG_UI_File_Icon('file_icon_file_default') }; background-size:auto 13px; }
				`;
		for ( let icon in SVG_UI_File_Icons ) {
			kind = icon.slice(icon.lastIndexOf('_') + 1);
			class_name = kind;
			// exceptions
			if ( kind === 'dirinvisible' )		{ class_name = 'dir.invisible'; }
			if ( kind === 'ignoredimage' )		{ class_name = 'ignored_image'; }
			if ( kind === 'open' )				{ class_name = 'has_subdirectory'; kind = 'dir_open'; }
			if ( /alias|symlink/.test(kind) )	{ class_name = 'link'; }
			if ( /bin|other/.test(kind) )		{ kind = 'system'; }
			// add rules for dir_list items, content_header, stats details:
			rules += `.use_custom_icons .${ class_name } a.icon .has_icon_before_before, #content_pane[data-content="has_${ class_name }"] #content_title span::before, .use_custom_icons .${ class_name }.has_icon_before::before, .use_custom_icons .${ class_name } .has_icon_before::before { background-image: url("data:image/svg+xml;utf8,${ SVG_UI_File_Icons['file_icon_'+kind] }"); }`;
		}
		return rules;
	}
	// END SVG UI ICONS
	//==============================// UI HTML
	// SIDEBAR HTML
	const Directory_Header_Menu_Elements = `
		<li id="menu_sort_by" class="menu__menu-item has_submenu border_top border_bottom" title="Sorting options."><span class="menu_item">Sort by&hellip;</span>${SVG_UI_Icons['arrow']}
			<ul id="sort_menu" class="submenu background_grey_85 border_all">     <li id="menu_sort_by_name" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_name"><span class="menu_item">Name</span></li>     <li id="menu_sort_by_duration" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_duration"><span class="menu_item">Duration</span></li>     <li id="menu_sort_by_size" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_size"><span class="menu_item">Size</span></li>     <li id="menu_sort_by_date" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_date"><span class="menu_item">Date</span></li>     <li id="menu_sort_by_kind" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_kind"><span class="menu_item">Kind</span></li>     <li id="menu_sort_by_ext" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_ext"><span class="menu_item">Extension</span></li>     <li id="menu_sort_by_default" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_default"><span class="menu_item">Default</span></li>     </ul></li>
		<li id="menu_theme_container" title="Set the main UI theme (light or dark)."><span id="menu_theme" class="toggle_UI_pref menu_item ignore_warning" data-ui_pref="theme"><span> Theme</span></span></li>
		<li id="alternate_background" class="menu__menu-item toggle_UI_pref ignore_warning" data-ui_pref="alternate_background" title="Alternate backgrounds of directory items."><span class="menu_item">Alternate Backgrounds</span></li>
		<li id="show_image_thumbnails" class="menu__menu-item toggle_UI_pref ignore_warning" data-ui_pref="show_image_thumbnails" title="Show image thumbnails in sidebar."><span class="menu_item">Show Image Thumbnails</span></li>
		<li id="show_numbers" class="menu__menu-item toggle_UI_pref border_bottom ignore_warning" data-ui_pref="show_numbers" title="Number directory list items."><span class="menu_item">Show Numbers</span></li>
		<li id="ignored_files" class="menu__menu-item has_submenu border_bottom" title="Options for ignored items."><span class="menu_item">Ignored Items</span>${SVG_UI_Icons['arrow']}     <ul id="ignored_files_submenu" class="submenu background_grey_85 border_all">     <li id="hide_ignored_items" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="hide_ignored_items" title="Show/hide ignored items (from the list of ignored file types in the user settings)."><span class="menu_item">Hide Ignored Items</span></li>     <li id="ignore_ignored_items" class="is_submenu toggle_UI_pref " data-ui_pref="ignore_ignored_items" title="If checked, the browser will not attempt to load ignored items (from the list of ignored file types in the user settings). It is recommended to leave this checked."><span class="menu_item">Ignore Ignored Items</span></li>     </ul></li>
		<li id="autoload_files" class="menu__menu-item has_submenu border_bottom" title="Options for autoloading files."><span class="menu_item">Autoload Files</span>${SVG_UI_Icons['arrow']}     <ul id="autoload_files_menu" class="submenu background_grey_85 border_all">     <li id="autoload_media" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="autoload_media" title="Automatically select and load the first media item in a directory and cover art (if any)."><span id="autoload_media_menu" class="menu_item">Autoload Media</span></li>     <li id="autoload_index_files" class="is_submenu toggle_UI_pref" data-ui_pref="autoload_index_files" title="Automatically load html index file."><span id="autoload_index_files_menu" class="menu_item">Autoload Index Files</span></li></ul>
		<li id="media_files" class="menu__menu-item has_submenu border_bottom" title="Options for media files."><span class="menu_item">Media Files</span>${SVG_UI_Icons['arrow']}     <ul id="media_files_menu" class="submenu background_grey_85 border_all">     <li id="play_all_media" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="play_all_media" title="If checked, autoplay all media types (i.e., audio and video), else just autoplay the currently selected/playing media type."><span class="menu_item">Play All Media Files</span></li>     <li id="loop_media_files" class="is_submenu" title="If checked, loop media playback."><span id="loop_media_menu" class="menu_item">Loop Media Playback</span></li>     <li id="shuffle_media_files" class="is_submenu" title="If checked, shuffle media playback."><span id="shuffle_media_menu" class="menu_item">Shuffle Media Playback</span></li></ul>
		<li id="playlist_options" class="menu__menu-item has_submenu border_bottom" title="Options for playlists."><span class="menu_item">Playlists</span>${SVG_UI_Icons['arrow']}     <ul id="playlist_menu" class="submenu background_grey_85 border_all">		<li class="is_submenu border_bottom"><label id="open_playlist_label" class="menu_item" for="open_playlist" title="Open local .m3u playlist/filelist file.">Open Playlist/Filelist&hellip;</label><input type="file" id="open_playlist" name="open_playlist" accept=".m3u,.m3u8"></li>    <li id="close_playlist_container" class="is_submenu"><span id="close_playlist" class="menu_item" href="#">Close Playlist/Filelist</span></li>     <li class="is_submenu"><span id="make_playlist" class="menu_item" href="#" title="Make an .m3u playlist/filelist of the items in the current directory (if any).">Make Playlist/Filelist&hellip;</span></li>     </ul></li>
		<li id="toggle_text_editing" class="menu__menu-item" title="Enable/disable editing of plain text files. Does not effect main text editor."><span id="enable_text_editing" class="toggle_UI_pref menu_item" data-ui_pref="enable_text_editing"><span id="disable">Text Editing </span></span></li>
		<li id="text_editing" class="menu__menu-item has_submenu border_bottom" title="Text editing options."><span class="menu_item">Text Editing Options</span>${SVG_UI_Icons['arrow']}     <ul id="text_editing_menu" class="submenu background_grey_85 border_all">     <li id="text_editor_menu_item" class="is_submenu border_bottom" title="Toggle the main text editor."><span id="text_editor" class="menu_item">Toggle Text Editor </span></li>     <li id="toggle_text_editor_theme_default" class="is_submenu border_bottom" title="Editor theme same as the main UI theme."><span id="text_editor_theme_default" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_default">Default Text Editor Theme</span></li>     <li id="toggle_text_editor_theme_light" class="is_submenu" title="Light text editor theme."><span id="text_editor_theme_light" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_light" title="Light text editor theme.">Light Text Editor Theme</span></li>     <li id="toggle_text_editor_theme_dark" class="is_submenu border_bottom" title="Dark text editor theme."><span id="text_editor_theme_dark" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_dark" title="Dark text editor theme.">Dark Text Editor Theme</span></li>     <li id="text_editor_split_view" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="text_editor_split_view" title="Toggle display of default text view and both source and rendered text."><span class="menu_item">Split View</span></li>     <li id="toggle_text_editor_raw_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_raw">View Source Markdown/Text</li>     <li id="toggle_text_editor_preview_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_preview">View Styled Markdown/Text</li>     <li id="toggle_text_editor_html_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_html">View Rendered HTML</li></ul>
		<li id="open_font_file" class="menu__menu-item border_bottom"><label id="open_font_label" class="menu_item" for="open_font" title="Open font file (.oft, .ttf, .woff) to view glyph repertoire and font info; save individual glyphs as .svg.">Open Font File&hellip;</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></li>
		<li id="user_settings" class="menu__menu-item has_submenu border_bottom" title="Reset or save user settings."><span class="menu_item">User Settings</span>${SVG_UI_Icons['arrow']}     <ul id="user_settings_menu" class="submenu background_grey_85 border_all">     <li class="is_submenu"><span id="default_settings" class="menu_item border_bottom" href="#" title="Delete UI prefs stored in the URL query string and reload page.">Reset User Settings</span></li>     <li class="is_submenu"><span id="export_settings" class="menu_item" href="#" title="Export hard-coded user settings and bookmarks to text file.">Export User Settings</span></li>     </ul></li>
		<li id="about" class="menu__menu-item" title="Go to script home page."><a id="about_link" class="menu_item flex_grow_1 ignore_warning" href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank"><span class="icon_container"></span>About</a></li>
		<li id="show_help" class="menu__menu-item " title="Show help."><span class="menu_item flex_grow_1"><span class="icon_container"></span>Help</span></li>
		<li id="donate" class="menu__menu-item " title="Buy me a coffee!"><a id="donate_link" class="menu_item flex_grow_1 ignore_warning" href="https://www.buymeacoffee.com/fiLtliTFxQ" target="_blank" rel="noopener"><span class="icon_container"></span>Buy me a Coffee <svg xmlns="http://www.w3.org/2000/svg" width="14px" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m32 2c-16.568 0-30 13.432-30 30 0 16.568 13.432 30 30 30s30-13.432 30-30c0-16.568-13.432-30-30-30m0 48c-1.371-1.814-20.53-12.883-16.602-25.218 3.53-11.073 15.094-6.597 16.602-.594 1.094-5.635 12.949-10.694 16.604.584 3.925 12.136-15.237 23.785-16.604 25.228" fill="#757575"/></svg></a></li>
		<li id="contact" class="menu__menu-item " title="Send me an email."><a id="contact_link" class="menu_item flex_grow_1 ignore_warning" href="mailto:mshroud@protonmail.com"><span class="icon_container"></span>Contact</a></li>
	`;
	const Directory_Header_Elements = function(body_id,parent_link) {
		let checked = '', parent_links = createParentLinkItems(), directory_title_element = '', directory_header_menus = '', text_editor_element = '';
		if ( getSearchParam('show_invisibles') === 'true' ) { checked = 'checked="true"'; }
		switch(body_id) {
			case 'top':
				directory_title_element = `<div id="directory_title" class="display_flex_row border_bottom background_grey_75 normal"><div class="directory_title_div center padding_4_6"></div></div>`;
				directory_header_menus = `<div id="directory_menus" class="display_flex_row background_grey_75 border_bottom pointer">
					<div id="parent_dir_menu" class="flex_justify_center width_24px padding_0">     <nav id="parent_dir_nav" class="flex_justify_center invert"><a href="${ parent_links[1] }" title="Parent Directory" class="flex_justify_center"><div>${ SVG_UI_Icons['chevron'] }</div></a></nav>     </div>
					<div id="parents_dir_menu" class="padding_0 flex_grow_1">     <nav id="parents_dir_nav" class="display_flex border_right border_left">     <div id="current_dir_path" class="bold flex_justify_center pointer" title="Parent Directories"><span class="has_icon_before">${ current_dir_path }</span></div>     <div id="close_playlist_btn_container" class="flex_justify_center width_24px border_left padding_0" title="Close Playlist">     <div id="close_playlist_btn" class="display_flex_column width_24px has_background invert padding_0"></div>     </div>     </nav>     <ul id="parents_links" class="menu background_grey_85 position_absolute border_bottom margin_0 padding_0 display_none">${ parent_links[0] }</ul>     </div>
					<div id="menu_container" class="width_24px flex_justify_center margin_0 padding_0" title="Main menu">     <nav id="menu_nav" class="invert pointer width_14px_contents"><div>${ SVG_UI_Icons['menu'] }</div></nav>     <ul id="menu" class="menu position_absolute background_grey_85 border_bottom margin_0 padding_0 display_none">     ${ bookmarksMenuItems() }     ${ Directory_Header_Menu_Elements }     </ul>     </div>
				</div>`;
				text_editor_element = `<div id="show_text_editor" class="bold border_bottom background_grey_80 display_none"><a href="#" title="Toggle Text Editor">Text Editor</a></div>`;
				break;
			case 'iframe':
				directory_header_menus = `<div id="change_dirs" class="flex_justify_center_row flex_justify_contents border_bottom background_grey_75">     <span id="parent" class="hover_bold flex_grow_1"><a href="${ parent_link }" id="iframe_parent_link" class="display_flex" title="Go to parent directory"><span class="width_14px_contents invert">${ SVG_UI_Icons['chevron'] }</span>Parent Directory</a></span>     <span id="open_in_sidebar" class="align_right hover_bold flex_grow_1"><a href="#" title="Open this directory in sidebar">Open in Sidebar<span class="width_14px_contents invert transform_rotate_270_contents">${ SVG_UI_Icons['chevron'] }</span></a></span>     </div>`;
				break;
		}
		return `<header id="directory_header" class="display_flex_column text_color_default">
					${ directory_title_element }
					<div id="directory_header_utilities" class="display_flex_column">
						${ directory_header_menus }
						<div id="directory_buttons" class="display_flex_row position_relative background_grey_80 border_bottom">
							<div id="directory_buttons_left" class="display_flex">     <button id="show_details" class="toggle_UI_pref" data-ui_pref="show_details" tabindex="-1" title="Toggle display of directory item detail information"><span id="show"> details</span></button>     <label id="show_invisibles_container" for="inv_checkbox" class="margin_0 padding_0 flex_justify_center_row"><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" data-ui_pref="show_invisibles" name="inv_checkbox" tabindex="-1"${ checked } /><span>Show Invisibles</span></label>     </div>
								<div id="show_grid" class="width_24px pointer margin_0 padding_0 has_flyout_menu" tabindex="-1" title="Show Grid"><div class="display_flex width_14px_contents">${ SVG_UI_Icons['grid']}</div>     <ul class="menu has_popout_menu margin_0 padding_0 display_none"><div class="display_flex width_24px width_14px_contents">${ SVG_UI_Icons['grid']}</div>     <li id="show_image_grid" class="item_1 border_right border_bottom">Show Image Grid</li>     <li id="show_font_grid" class="item_2 border_right">Show Font Grid</li>     </ul>     </div>
						</div>
						<div id="sorting" class="background_grey_80">
							<div id="sorting_row_1" class="container display_flex flex_justify_contents border_bottom">     <div id="sort_by_name" class="toggle_UI_pref name sorting align_left hover_bold" data-ui_pref="sort_by_name" title="Sort by name"><span><input id="play_toggle" class="display_none position_relative" type="checkbox" tabindex="-1" checked="true" />Name</span></div>     <div id="sort_by_default" class="toggle_UI_pref sorting align_right hover_bold" data-ui_pref="sort_by_default" title="Default sort"><span>Default</span></div>     <div id="sort_by_duration" class="toggle_UI_pref sorting align_right hover_bold display_none" data-ui_pref="sort_by_duration" title="Sort by media duration"><span>Duration</span></div>     </div>
							<div id="sorting_row_2" class="iframe_item border_bottom display_none">     <div id="sort_by_ext" class="toggle_UI_pref details sorting align_left hover_bold" data-ui_pref="sort_by_ext" title="Sort by extension"><span>Ext</span></div>     <div id="sort_by_size" class="toggle_UI_pref details sorting center hover_bold" data-ui_pref="sort_by_size" title="Sort by size"><span>Size</span></div>     <div id="sort_by_date" class="toggle_UI_pref details sorting center hover_bold" data-ui_pref="sort_by_date" title="Sort by date"><span>Date</span></div>     <div id="sort_by_kind" class="toggle_UI_pref details sorting align_right hover_bold" data-ui_pref="sort_by_kind" title="Sort by kind"><span>Kind</span></div>     </div>
						</div>
						${ text_editor_element }
					</div>
				</header>`;
	};
	const Directory_Elements = function(body_id,parent_link) {									// Assemble directory elements for both top and iframe directories
		let footer_utilities = '', directory_utilities = '';
		if ( body_id === 'top' ) {	// various elements not needed in iframe directories
			footer_utilities = `<div id="footer_utilities" class="width_24px flex_justify_center position_relative invert pointer"><div class="width_18px_contents display_flex transform_rotate_180">${ SVG_UI_Icons['toggle'] }</div>     <ul class="has_popout_menu invert margin_0 padding_0 display_none">     <li id="open_in_content_pane" class="align_right border_bottom padding_4_6">Open Sidebar in Content Pane</li>     <li id="view_directory_source" class="align_right padding_4_6" data-kind="view_directory_source">View Sidebar Directory Source</li>     </ul>     </div>`;
			directory_utilities = `<div id="handle" class="position_absolute "></div>     <div id="hide_sidebar" class="toggle_UI_pref width_24px width_18px_contents position_absolute flex_justify_center invert" data-ui_pref="hide_sidebar" title="Toggle Sidebar">${ SVG_UI_Icons['toggle'] }</div>     </div>`;
		}
		const directory_nav = `<nav id="directory_nav" class="display_flex_column background_grey_85"><div id="directory_nav_inner" class="position_relative border_bottom">
				<div id="directory_list_outer" class="position_relative"><ol id="directory_list" class="display_flex_column margin_0 padding_0 text_color_default" tabindex="0">insert_prepped_index</ol></div>
			</div></nav>`;
		const directory_footer = `<footer id="directory_footer" class="display_flex_row position_relative background_grey_85 border_top text_color_default">insert_stats${ footer_utilities }</footer>`;
		return `<div id="directory_wrapper" class="${body_id} display_flex_column position_relative border_right padding_0" style="width:${ Number(getSearchParam("width")) }%">
					${ Directory_Header_Elements(body_id,parent_link) }     ${ directory_nav }     ${ directory_footer }     ${ directory_utilities }
				</div>`;
	};
	//==============================//
	// CONTENT PANE HTML
	// TEXT EDITING UI
	const Text_Editing_UI_Elements = `
		<div id="toolbar" class="border_bottom background_grey_80 position_relative text_color_default">
				<ul id="toolbar_buttons" class="display_flex_row margin_0 padding_0">     <li id="text_editor_theme" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_theme" title="Toggle Editor Theme"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["toggle_theme"] }</div></li>     <li id="toggle_text_editor_raw" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_raw" title="Show source"><div class="display_flex width_16px_contents">${ SVG_Text_Editing_UI_Icons["show_markdown"] }</div></li>     <li id="toggle_text_editor_preview" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_preview" title="Show Preview"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["show_preview"] }</div></li>     <li id="toggle_text_editor_html" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_html" title="Show formatted HTML"><div class="display_flex width_18px_contents">${ SVG_Text_Editing_UI_Icons["show_html"] }</div></li>     <li id="toggle_text_editor_split_view" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_split_view" title="Toggle Split View"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["toggle_split"] }</div></li>     <li id="text_editor_sync_scroll" class="toggle_UI_pref flex_justify_center_row" data-ui_pref="text_editor_sync_scroll"><input id="text_editor_sync_scroll_input" class="toggle_UI_pref flex_justify_center_row position_relative" data-ui_pref="text_editor_sync_scroll" name="text_editor_sync_scroll" type="checkbox"><label id="text_editor_sync_scroll_label" for="text_editor_sync_scroll" class="toggle_UI_pref flex_justify_center_row" data-ui_pref="text_editor_sync_scroll">Sync Scroll</label></li>     <li class="display_flex flex_grow_1">&nbsp;</li>     <li id="clear_text" class="toolbar_icon" title="Clear Text">Clear</li>     <li id="save_btn" class="toolbar_icon display_flex has_flyout_menu" title=""><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons['save_btn']}</div><ul class="menu background_grey_80 border_all">     <li id="save_text" class="item_1 border_right border_bottom background_grey_85" title="Save source text"><span id="save_text_link" target="_blank">Save Source</span></li>     <li id="save_HTML" class="item_2 border_right background_grey_85" title="Save rendered html"><span id="save_HTML_link" target="_blank">Save HTML</span></li>     </ul></li>     </ul>
		</div>
		<div id="text_container" class="display_flex flex_grow_1">     <textarea id="text_editor_raw_pane" class=" text_container_pane margin_0 text_color_default display_none" tabindex="0"></textarea>     <iframe id="text_editor_preview_pane" class=" text_container_pane margin_0 text_color_default display_none markdown_body" tabindex="0"></iframe>     <textarea id="text_editor_html_pane" class=" text_container_pane margin_0 text_color_default display_none" tabindex="0" readonly></textarea>
		<div id="text_editing_handle" class="position_absolute"></div></div>
	`;
	const Content_Audio_Elements = `
		<div id="content_audio_title" class="display_flex background_grey_80 bold" title="Click to toggle .m3u playlist entry."><span></span></div>
		<div id="content_audio" class="display_flex_row border_bottom background_grey_80">
			<div id="audio_container" class="display_flex_row border_all">
				<nav id="cue_sheet_track_list_container_audio" class="cue_sheet_track_list_container border_right" title="Cue sheet track list">			<div class="display_none position_absolute;"><ul id="cue_sheet_track_list_audio" class="cue_sheet_track_list background_grey_85 border_bottom margin_0 padding_0">     <li class="cue_sheet_track header display_grid background_grey_85 border_top border_bottom bold">     <span class="cue_track_id">Track</span><span class="cue_performer">Performer</span>     <span class="cue_title">Title</span>     <span class="cue_index" title="mm:ss:ff">Time</span>     </li>     </ul></div>     </nav>
				<div id="prev_track" class="prev_next_track_btn audio_controls flex_justify_center pointer" title="Previous track"><div class="display_flex width_24px_contents transform_rotate_180">${ SVG_UI_Icons['prev_next_track'] }</div></div>
				<div id="next_track" class="prev_next_track_btn audio_controls flex_justify_center border_right pointer" title="Next track"><div class="display_flex width_24px_contents">${ SVG_UI_Icons['prev_next_track'] }</div></div>
				<audio id="audio" preload="auto" tabindex="0" controls>Sorry, your browser does not support HTML5 audio.</audio>
				<div id="close_audio" class="audio_controls border_left flex_justify_center position_relative pointer" title="Close audio"><div class="display_flex width_18px_contents">${ SVG_UI_Icons['multiply'] }</div></div>
				<div id="audio_options" class="display_flex_column">
					<label id="loop_label" for="loop"><input type="checkbox" id="loop" name="loop" tabindex="0" />Loop</label>
					<label id="shuffle_label" for="shuffle"><input type="checkbox" id="shuffle" name="shuffle" tabindex="0" />Shuffle</label>
				</div>
			</div>
		</div>
		<div id="content_audio_playlist" class="playlist_entry_container border_bottom center padding_4_6 display_none"><textarea id="content_audio_playlist_textarea" rows="3" spellcheck="false"></textarea></div>
	`;
	const Content_Font_Viewer = '<div id="font_file_viewer" class="position_relative display_none"><div id="font_file_grid" class="position_relative padding_0 center display_grid"></div></div>';
//			<li id="font_small_caps" class="flex_justify_center text hover_bold pointer" data-salt="smcp">Small Caps</li><li id="font_swash" class="flex_justify_center text hover_bold pointer" data-salt="swsh">Swash</li><li id="font_titling" class="flex_justify_center text hover_bold pointer" data-salt="ornm">Ornaments</li>
	const Content_Font_Elements = function() {
		const font_toolbar = `<div id="font_toolbar" class="display_none margin_0 position_fixed background_grey_80 border_bottom"><ol class="display_flex_row flex_grow_1">
			<li id="font_variants" class="flex_justify_center" title="Font Variants"><select id="font_variant_select" label="Font Variants">     <option value="">OpenType Feature Tags</option>
				<optgroup label="Caps">     <option value="normal">Normal</option>     <option value="smcp" title="smcp">Small Caps</option>     <option value="c2sc" title="c2sc">Capitals to Small Caps</option>     <option value="pcap" title="pcap">Petite Caps</option>     <option value="c2pc" title="c2pc">Capitals to Petite Caps</option>     <option value="unic" title="unic">Unicase</option>     <option value="cpsp" title="cpsp">Capital Spacing</option>     <option value="case" title="case">Case Sensitive Forms</option>     <option value="ital" title="ital">Italics</option>     <option value="ordn" title="ordn">Ordinals</option>     </optgroup>
				<optgroup label="Numbers">     <option value="normal">Normal</option>     <option value="pnum" title="pnum">Proportional Figures</option>     <option value="tnum" title="tnum">Tabular Figures</option>     <option value="frac" title="frac">Fractions</option>     <option value="afrc" title="afrc">Alternative Fractions</option>     <option value="dnom" title="dnom">Denominator</option>     <option value="numr" title="numr">Numerator</option>     <option value="sinf" title="sinf">Scientific Inferiors</option>     <option value="zero" title="zero">Slashed Zero</option>     <option value="mgrk" title="mgrk">Mathematical Greek</option>     <option value="flac" title="flac">Flattened accent forms</option>     <option value="dtls" title="dtls">Dotless Forms</option>     <option value="ssty" title="ssty">Math script style alternates</option>     </optgroup>
				<optgroup label="Ligatures &amp; Alts">     <option value="normal">Normal</option>     <option value="aalt" title="aalt">Access All Alternates</option>     <option value="swsh" title="swsh">Swash</option>     <option value="cswh" title="cswh">Contextual Swash</option>     <option value="calt" title="calt">Contextual Alternates</option>     <option value="hist" title="hist">Historical Forms</option>     <option value="locl" title="locl">Localized Forms</option>     <option value="rand" title="rand">Randomize</option>     <option value="nalt" title="nalt">Alternate Annotation Forms</option>     <option value="cv01" title="cv01">Character Variant 1–99</option>     <option value="salt" title="salt">Stylistic Alternates</option>     <option value="ss01">Stylistic Set 1–20</option>     <option value="subs" title="subs">Subscript</option>     <option value="sups" title="sups">Superscript</option>     <option value="titl" title="titl">Titling Alternates</option>     <option value="rvrn" title="rvrn">Required Variation Alternates</option>     <option value="clig" title="clig">Contextual Ligatures</option>     <option value="dlig" title="dlig">Discretionary Ligatures</option>     <option value="hlig" title="hlig">Historical Ligatures</option>     <option value="liga" title="liga">Standard Ligatures</option>     </optgroup>
				<optgroup label="Special Features">     <option value="normal">Normal</option>     <option value="size" title="size">Optical size</option>     <option value="ornm" title="ornm">Ornaments</option>     </optgroup>
				<optgroup label="Indic/Brahmic">     <option value="normal">Normal</option>     <option value="abvf" title="abvf">Above-base Forms</option>     <option value="abvm" title="abvm">Above-base Mark Positioning</option>     <option value="abvs" title="abvs">Above-base Substitutions</option>     <option value="blwf" title="blwf">Below-base Forms</option>     <option value="blwm" title="blwm">Below-base Mark Positioning</option>     <option value="blws" title="blws">Below-base Substitutions</option>     <option value="pref" title="pref">Pre-base Forms</option>     <option value="pres" title="pres">Pre-base Substitutions</option>     <option value="psts" title="psts">Post-base Substitutions</option>     <option value="pstf" title="pstf">Post-base Forms</option>     <option value="dist" title="dist">Distance</option>     <option value="akhn" title="akhn">Akhand</option>     <option value="haln" title="haln">Halant Forms</option>     <option value="half" title="half">Half Form</option>     <option value="nukt" title="nukt">Nukta Forms</option>     <option value="rkrf" title="rkrf">Rakar Forms</option>     <option value="rphf" title="rphf">Reph Form</option>     <option value="vatu" title="vatu">Vattu Variants</option>     <option value="cjct" title="cjct">Conjunct Forms</option>     <option value="cfar" title="cfar">Conjunct Form After Ro</option>     </optgroup>
				<optgroup label="East Asian">     <option value="normal">Normal</option>     <option value="smpl" title="smpl">Simplified Forms</option>     <option value="trad" title="trad">Traditional Forms</option>     <option value="tnam" title="tnam">Traditional Name Forms</option>     <option value="expt" title="expt">Expert Forms</option>     <option value="hojo" title="hojo">Hojo Kanji Forms</option>     <option value="nlck" title="nlck">NLC Kanji Forms</option>     <option value="jp78" title="jp78">JIS 78 Forms</option>     <option value="jp83" title="jp83">JIS 83 Forms</option>     <option value="jp90" title="jp90">JIS 90 Forms</option>     <option value="jp04" title="jp04">JIS 04 Forms</option>     <option value="hngl" title="hngl">Hangul</option>     <option value="ljmo" title="ljmo">Leading Jamo Forms</option>     <option value="tjmo" title="tjmo">Trailing Jamo Forms</option>     <option value="vjmo" title="vjmo">Vowel Jamo Forms</option>     <option value="fwid" title="fwid">Full Widths</option>     <option value="hwid" title="hwid">Half Widths</option>     <option value="halt" title="halt">Alternate Half Widths</option>     <option value="twid" title="twid">Third Widths</option>     <option value="qwid" title="qwid">Quarter Widths</option>     <option value="pwid" title="pwid">Proportional Widths</option>     <option value="palt" title="palt">Proportional Alternates</option>     <option value="pkna" title="pkna">Proportional Kana</option>     <option value="ruby" title="ruby">Ruby Notation Forms</option>     <option value="hkna" title="hkna">Horizontal Kana Alternates</option>     <option value="vkna" title="vkna">Vertical Kana</option>     <option value="cpct" title="cpct">Centered CJK Punctuation</option>     </optgroup>
				<optgroup label="West Asian">     <option value="normal">Normal</option>     <option value="curs" title="curs">Cursive Positioning</option>     <option value="jalt" title="jalt">Justification Alternates</option>     <option value="mset" title="mset">Mark Positioning via Substitution</option>     <option value="rclt" title="rclt">Required Contextual Alternates</option>     <option value="rlig" title="rlig">Required Ligatures</option>     <option value="isol" title="isol">Isolated Forms</option>     <option value="init" title="init">Initial Forms</option>     <option value="medi" title="medi">Medial Forms</option>     <option value="med2" title="med2">Medial Form #2</option>     <option value="fina" title="fina">Terminal Forms</option>     <option value="fin2" title="fin2">Terminal Form #2</option>     <option value="fin3" title="fin3">Terminal Form #3</option>     <option value="falt" title="falt">Final Glyph on Line Alternates</option>     <option value="stch" title="stch">Stretching Glyph Decomposition</option>     </optgroup>
				<optgroup label="Positioning">     <option value="normal">Normal</option>     <option value="ccmp" title="ccmp">Glyph Composition/Decomposition</option>     <option value="kern" title="kern">Kerning</option>     <option value="mark" title="mark">Mark Positioning</option>     <option value="mkmk" title="mkmk">Mark-to-mark Positioning</option>     <option value="opbd" title="opbd">Optical Bounds</option>     <option value="lfbd" title="lfbd">Left Bounds</option>     <option value="rtbd" title="rtbd">Right Bounds</option>/option>     </optgroup>
			</select></li>
			<li id="font_stylistic_set" class="flex_justify_center" data-salt="ssnn" title="Stylistic Sets">     <select id="font_stylistic_set_select" class="">     <option value="ss00">Stylistic Sets</option>     <option value="ss01">ss01</option>     <option value="ss02">ss02</option>     <option value="ss03">ss03</option>     <option value="ss04">ss04</option>     <option value="ss05">ss05</option>     <option value="ss06">ss06</option>     <option value="ss07">ss07</option>     <option value="ss08">ss08</option>     <option value="ss09">ss09</option>     <option value="ss10">ss10</option>     <option value="ss11">ss11</option>     <option value="ss12">ss12</option>     <option value="ss13">ss13</option>     <option value="ss14">ss14</option>     <option value="ss15">ss15</option>     <option value="ss16">ss16</option>     <option value="ss17">ss17</option>     <option value="ss18">ss18</option>     <option value="ss19">ss19</option>     <option value="ss20">ss20</option>     </select></li>
			<li id="font_tag" class="flex_justify_center" data-salt=""><textarea id="font_tag_textarea" rows="1" cols="4" spellcheck="false" maxlength="4" placeholder="otftag" title="Enter an OpenType Feature Tag (e.g.: &ldquo;smcp&rdquo;)"></textarea></li>
			<li class="spacer width_100"></li>
			<li id="unicode_char_planes" class="flex_justify_center" title="Unicode Code Ranges"><select id="unicode_char_ranges_select">
				<option value="">Unicode Code Ranges</option>
				<optgroup label="Basic Multilingual Plane">     <option value="BMP_Range_01">BMP-01: U+0000–U+0FFF</option>    <option value="BMP_Range_02">BMP-02: U+1000–U+1FFF</option>    <option value="BMP_Range_03">BMP-03: U+2000–U+2FFF</option>    <option value="BMP_Range_04">BMP-04: U+3000–U+3FFF</option>    <option value="BMP_Range_05">BMP-05: U+4000–U+4FFF</option>    <option value="BMP_Range_06">BMP-06: U+5000–U+5FFF</option>    <option value="BMP_Range_07">BMP-07: U+6000–U+6FFF</option>    <option value="BMP_Range_08">BMP-08: U+7000–U+7FFF</option>    <option value="BMP_Range_09">BMP-09: U+8000–U+8FFF</option>    <option value="BMP_Range_10">BMP-10: U+9000–U+9FFF</option>    <option value="BMP_Range_11">BMP-11: U+A000–U+AFFF</option>    <option value="BMP_Range_12">BMP-12: U+B000–U+BFFF</option>    <option value="BMP_Range_13">BMP-13: U+C000–U+CFFF</option>    <option value="BMP_Range_14">BMP-14: U+D000–U+DFFF</option>    <option value="BMP_Range_15">BMP-15: U+E000–U+EFFF</option>    <option value="BMP_Range_16">BMP-16: U+F000–U+FFFF</option>    </optgroup>
				<optgroup label="Supplementary Multilingual Plane">     <option value="SMP_Range_01">SMP-01: U+10000–U+10FFF</option>    <option value="SMP_Range_02">SMP-02: U+11000–U+11FFF</option>    <option value="SMP_Range_03">SMP-03: U+12000–U+12FFF</option>    <option value="SMP_Range_04">SMP-04: U+13000–U+13FFF</option>    <option value="SMP_Range_05">SMP-05: U+14000–U+14FFF</option>    <option value="SMP_Range_06">SMP-06: U+15000–U+15FFF</option>    <option value="SMP_Range_07">SMP-07: U+16000–U+16FFF</option>    <option value="SMP_Range_08">SMP-08: U+17000–U+17FFF</option>    <option value="SMP_Range_09">SMP-09: U+18000–U+18FFF</option>    <option value="SMP_Range_10">SMP-10: U+19000–U+19FFF</option>    <option value="SMP_Range_11">SMP-11: U+1A000–U+1AFFF</option>    <option value="SMP_Range_12">SMP-12: U+1B000–U+1BFFF</option>    <option value="SMP_Range_13">SMP-13: U+1C000–U+1CFFF</option>    <option value="SMP_Range_14">SMP-14: U+1D000–U+1DFFF</option>    <option value="SMP_Range_15">SMP-15: U+1E000–U+1EFFF</option>    <option value="SMP_Range_16">SMP-16: U+1F000–U+1FFFF</option>    </optgroup>
				<optgroup label="Supplementary Ideographic Plane">     <option value="SIP_Range_01">SIP-01: U+20000–U+20FFF</option>    <option value="SIP_Range_02">SIP-02: U+21000–U+21FFF</option>    <option value="SIP_Range_03">SIP-03: U+22000–U+22FFF</option>    <option value="SIP_Range_04">SIP-04: U+23000–U+23FFF</option>    <option value="SIP_Range_05">SIP-05: U+24000–U+24FFF</option>    <option value="SIP_Range_06">SIP-06: U+25000–U+25FFF</option>    <option value="SIP_Range_07">SIP-07: U+26000–U+26FFF</option>    <option value="SIP_Range_08">SIP-08: U+27000–U+27FFF</option>    <option value="SIP_Range_09">SIP-09: U+28000–U+28FFF</option>    <option value="SIP_Range_10">SIP-10: U+29000–U+29FFF</option>    <option value="SIP_Range_11">SIP-11: U+2A000–U+2AFFF</option>    <option value="SIP_Range_12">SIP-12: U+2B000–U+2BFFF</option>    <option value="SIP_Range_13">SIP-13: U+2C000–U+2CFFF</option>    <option value="SIP_Range_14">SIP-14: U+2D000–U+2DFFF</option>    <option value="SIP_Range_15">SIP-15: U+2E000–U+2EFFF</option>    <option value="SIP_Range_16">SIP-16: U+2F000–U+2FFFF</option>    </optgroup>
				<optgroup label="Tertiary Ideographic Plane">     <option value="TIP_Range_01">TIP-01: U+30000–U+30FFF</option>    <option value="TIP_Range_02">TIP-02: U+31000–U+31FFF</option>    </optgroup>
				<optgroup label="Supplementary Special-Purpose Plane">     <option value="SSP_Range_01">SSP-01: U+E0000–U+E0FFF</option>     </optgroup>
			</select></li>
			</ol></div>
			`;
		const sample_string = `ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789<br />!"#$%&'()*+,-./:;<=>?@[\\]^_\`{|}~`;
		const lorem_string = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
		return `${ font_toolbar }<div id="font_specimen" class="display_none">
					<div id="font_specimen_grid" class="border_left_x border_top_x"></div><hr />
					<div id="specimen" class="specimen" contenteditable="true" tabindex="0">${ sample_string }</div><hr />
					<div id="specimen_string_2" class="specimen" contenteditable="true" tabindex="0"><div id="specimen_2">Typography</div><div id="specimen_2H4">The art of using types to produce impressions on paper, vellum, &amp;c.</div></div><hr />
					<div id="specimen_string_3" class="specimen" contenteditable="true"><div id="specimen_3">S P E C I M E N</div><div id="specimen_3H3">Typography is the work of typesetters (also known as compositors), typographers, graphic designers, art directors, manga artists, comic book artists, graffiti artists, and, now, anyone who arranges words, letters, numbers, and symbols for publication, display, or distribution.</div></div><hr />
					<div id="lorem_strings" class="specimen" contenteditable="true" tabindex="0">     <div id="lorem" class="lorem">${ lorem_string }</div><div id="lorem_2" class="lorem">${ lorem_string }</div><div id="lorem_3" class="lorem">${ lorem_string }</div></div>
					<div id="specimen_glyph_container" class="background_grey_100"><div id="specimen_glyph" class="flex_justify_center position_fixed"></div><div id="specimen_glyph_overlay" class="position_fixed background_grey_100"></div></div>
				</div>
				${ Content_Font_Viewer }
				<div id="glyph_viewer" class="invert margin_0 padding_0 position_absolute display_none"><div id="glyph_viewer_info" class="background_grey_85 border_bottom_x padding_4_6 invert"><button id="save_svg_hidden">Save SVG</button><div class="flex_justify_center_row"></div><button id="save_svg">Save SVG</button></div></div>
		`;
	};
	const Content_Header_Elements = `
		<header id="content_header">
			<div id="audio_wrapper" class="text_color_default background_grey_75 display_none">     ${ Content_Audio_Elements }     </div>
			<div id="content_title_container" class="title display_flex flex_justify_contents text_color_default border_bottom pointer">
				<div id="title_buttons_left" class="display_flex padding_4_6 alighn_left">
					<nav id="cue_sheet_track_list_container_video" class="cue_sheet_track_list_container border_right background_grey_75" title="Cue sheet track list"></nav>
					<button id="reload_btn" tabindex="-1"><span></span></button>     <button id="prev_next_btns" class="split_btn padding_0 position_relative display_none" tabindex="-1"><span id="prev_btn" class="flex_justify_center"><span class="transform_rotate_270_contents">${ SVG_UI_Icons['chevron'] }</span></span><span id="next_btn" class="flex_justify_center"><span class="display_flex transform_rotate_90_contents">${ SVG_UI_Icons['chevron'] }</span></span></button>
				</div>
				<div id="content_title" class=""><span class="has_icon_before bold"></span></div>
				<div id="title_buttons_right" class="display_flex padding_4_6 align_right">     <button id="scale" class="split_btn padding_0 position_relative display_none" tabindex="-1"><span id="increase" class="flex_justify_center" title="Enlarge"><div class="display_flex width_10px_contents">${ SVG_UI_Icons['plus'] }</div></span><span id="decrease" class="flex_justify_center" title="Reduce"><div class="display_flex width_10px_contents">${ SVG_UI_Icons['minus'] }</div></span></button>     <button id="open_in_text_editor" class="display_none" title="Open in Text Editor" tabindex="-1"><span>Edit</span></button>     <button id="close_btn" tabindex="-1" title="Close Content"><span></span></button>     </div>
			</div>
			<ul id="cue_sheet_track_list_video" class="cue_sheet_track_list border_bottom margin_0 padding_0 display_none">
				<li class="cue_sheet_track header display_grid background_grey_85 border_top border_bottom bold">     <span class="cue_track_id">Track</span><span class="cue_performer">Performer</span>     <span class="cue_title">Title</span>     <span class="cue_index" title="mm:ss:ff">Time</span>     </li>
			</ul>
			<div id="content_playlist" class="playlist_entry_container border_bottom center display_none"><textarea id="content_playlist_textarea" rows="3" spellcheck="false"></textarea></div>
		</header>`;
	// CONTENT containers
	const Content_Pane_Elements = `<div id="content_pane" class="display_flex_column flex_grow_1 position_relative" data-content="has_null">
		${ Content_Header_Elements }
		<main id="content_container" class="display_flex position_relative background_grey_85 margin_0 padding_0">     ${ SVG_UI_Icons['spinner'] }     <div id="content_grid" class="content" data-kind="grid"></div>     <div id="content_text" class="background_grey_85 margin_0 padding_0 position_absolute"></div>     <div id="content_font" class="content background_grey_100 position_relative text_color_default" spellcheck="false" data-kind="font">${ Content_Font_Elements() }</div>     <div id="content_image_container" class="content background_grey_95 position_relative margin_0" data-kind="image"><img id="content_image" class="position_relative" src="#" alt="" tabindex="0" /></div>     <embed id="content_pdf" class="content position_relative" tabindex="0" data-kind="pdf">     <video id="content_video" class="content background_grey_95 media position_absolute" controls data-kind="video">Your browser does not support the video tag.</video>     <iframe id="content_iframe" class="content position_relative" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe>     <iframe id="content_iframe_utility" class="content" name="content_iframe_utility" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe>     </main>
	</div>`;
	//==============================//
	// UTILITIES HTML (warnings and help)
	const Utilities_Warnings = `
		<header id="warnings_header" class="text_color_default background_grey_85"><h3 id="warning_header"><span>Warning:</span></h3><h3 id="make_playlist_header"><span>Make Playlist/Filelist (.m3u)</span></h3></header>
		<div id="warnings" class="text_color_default background_grey_85">
			<div id="warning_close_font" class="warning">Are you sure you want to close the font file?</div>     <div id="warning_unsaved_text" class="warning">You have unsaved changes.</div>     <div id="warning_clear_text" class="warning">Are you sure you want to clear all your text?</div>     <div id="warning_local_bookmark" class="warning">Can&rsquo;t load local items from non-local pages. <br />&emsp;Please use your browser&rsquo;s bookmarks instead or enter the URL manually.</div>     <div id="warning_local_file" class="warning">Can&rsquo;t load local items from non-local pages.</div>     <div id="warning_close_playlist" class="warning">Are you sure you want to close the playlist?</div>     <div id="warning_local_playlist" class="warning">This playlist contains local files. <br />&emsp;Please reload this playlist from a local page in order to play them.</div>     <div id="warning_no_playlist" class="warning">Can&rsquo;t make playlist: no qualified items found.</div>
			<div id="warning_make_playlist" class="warning">
				<form id="make_playlist_form" action="#"><fieldset class="margin_0 padding_0">     <div><input name="make_playlist" type="radio" id="media_files_only" checked><label for="media_files_only">All media files</label></div>     <div class="indent"><input name="make_playlist" type="radio" id="audio_files_only"><label for="audio_files_only">Audio files only</label></div>     <div class="indent"><input name="make_playlist" type="radio" id="video_files_only"><label for="video_files_only">Video files only</label></div>     <div><input name="make_playlist" type="radio" id="all_non_media_files"><label for="all_non_media_files">All non-media items</label></div>     <div><input name="make_playlist" type="radio" id="all_items"><label for="all_items">All items</label></div>     <div class="indent"><input name="make_playlist" type="radio" id="directories_only"><label for="directories_only">Directories only</label></div>     <div class="indent"><input name="make_playlist" type="radio" id="files_only"><label for="files_only">Files only</label></div>     </fieldset>
				</form>
			</div>
		</div>
		<div id="warning_buttons_container" class="display_flex_column background_grey_90">     <div id="warning_buttons" class="display_flex_row">     <button id="warning_btn_dont_save">Don&rsquo;t Save</button>     <button id="warning_btn_cancel" >Cancel</button>     <button id="warning_btn_clear">Clear</button>     <button id="warning_btn_save">Save</button>     <button id="warning_btn_ok">OK</button>     </div>     </div>
	`;
	const Utilities_Help = `
		<header id="help_header" class="title display_grid padding_4_6 text_color_default border_bottom_x background_grey_75 center"><span class="spacer"></span><span class="bold flex_justify_center">HELP</span><button id="close_help" class="focus"><span>Close</span></button></header><section><p class="center bold"><a href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" class="has_icon_before link" target="_blank">SCRIPT HOME: openuserjs.org</a></p>
<ul id="utilities_help" class="info_list">     <li class="info_list_header center bold"><span class="col_1">KEYBOARD SHORTCUTS</span><span class="col_2">DESCRIPTION</span></li>     <li><span class="col_1"><kbd>&uarr;</kbd> or <kbd>&darr;</kbd></span><span class="col_2">Select the previous/next sidebar item or previewed directory item.<br />If audio is playing, and the previous/next file is also audio, the file will be highlighted but not loaded in the audio player; press <kbd>return</kbd> to load it.</span></li>          <li><span class="col_1"><kbd>&larr;</kbd> or <kbd>&rarr;</kbd></span><span class="col_2">Select prev/next item of the same kind as the current selection.<br />If current selection is a media file, select and begin playback of the next media item.</span></li>     <li><span class="col_1"><kbd>&#8997;</kbd> + <kbd>&larr;</kbd> or <kbd>&#8594;</kbd></span><span class="col_2">Skip media &plusmn;10s</span></li>     <li><span class="col_1"><kbd>&#8997;</kbd> + <kbd>&#8679;</kbd> + <kbd>&larr;</kbd> or <kbd>&rarr;</kbd></span><span class="col_2">Skip skip &plusmn;30s</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>&uarr;</kbd></span><span class="col_2">Go to parent directory</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>&darr;</kbd></span><span class="col_2">Open selected sidebar directory</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>&rarr;</kbd></span><span class="col_2">Open selected subdirectory in sidebar.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>&larr;</kbd></span><span class="col_2">Close selected subdirectory in sidebar or jump to parent directory.</span></li>     <li><span class="col_1"><kbd>Escape</kbd></span><span class="col_2">Close menus and help, unfocus textareas and content pane, etc.</span></li>     <li><span class="col_1"><kbd>Return</kbd></span><span class="col_2">Open selected directory, select file, or pause/play media.</span></li>     <li><span class="col_1"><kbd>Space</kbd></span><span class="col_2">Pause/Play media files (if media player loaded).</span></li>     <li><span class="col_1"><kbd>Tab</kbd></span><span class="col_2">Toggle focus between sidebar and content pane.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>D</kbd></span><span class="col_2">Toggle file details (size, date modified, kind) in some index page types.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>E</kbd></span><span class="col_2">Toggle main menu.</span></li>     <li><span class="col_1"><kbd>&#8679;</kbd> + <kbd>&#8984;/Ctrl</kbd> + <kbd>E</kbd></span><span class="col_2">Show text editor.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>G</kbd></span><span class="col_2">Show or reload image or font grids.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>I</kbd></span><span class="col_2">Toggle invisible files.</span></li>     <li><span class="col_1"><kbd>&#8679;</kbd> + <kbd>&#8984;/Ctrl</kbd> + <kbd>O</kbd></span><span class="col_2">Open selected sidebar item in new window/tab.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>R</kbd></span><span class="col_2">Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>W</kbd></span><span class="col_2">Close previewed content (doesn&rsquo;t work in all browsers; use close button instead), or close window if no content is being previewed.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>&#8679;</kbd> + <kbd>&lt;</kbd> or <kbd>&gt;</kbd></span><span class="col_2">Scale preview items and grids.</span></li>     <li><span class="col_1"><kbd>&#8984;/Ctrl</kbd> + <kbd>\\</kbd></span><span class="col_2">Toggle sidebar.</span></li>     <li><span class="col_1"><kbd>&#8679;</kbd> + <kbd>&#8984;/Ctrl</kbd> + <kbd>\\</kbd></span><span class="col_2">Toggle text editor split view.</span></li>     </ul>
		<div id="help_main_menu"><div>${ SVG_UI_Icons['menu'] }</div><strong>MAIN MENU</strong><dl><dt></dt><dd>Selecting many menu items will override the hard-coded default preferences in the script by adding an entry to the query string in the browser window URL.</dd><dd>This makes it more convenient to customize preferences, since defaults changed in the code will be over-written whenever the script is updated.</dd></dl><dl><dt id="help_bookmarks" class="has_icon_before">BOOKMARKS</dt><dd>You can add bookmarks to files or directories to this menu by editing the User Preference in the code.</dd><dd>However, any items you add will be over-written when the script is updated. To save them, you can "Export User Settings" (see below) before updating the script.</dd><dd>Of course, you may simply use your browser's bookmarks instead.</dd></dl>
		<dl><dt>SORT BY...</dt><dd>Sort directory items by Name, Size, Date, Kind, Extension, Duration (media items only), or Default (i.e., sorted by Name, with files on top)</dd></dl>
		<dl><dt>LIGHT THEME/DARK THEME</dt><dd>Change the UI theme.</dd></dl>
		<dl><dt>ALTERNATE BACKGROUNDS</dt><dd>Alternate background colors for directory list items.</dd></dl>
		<dl><dt>SHOW NUMBERS</dt><dd>Show numbers for directory list items.</dd></dl>
		<dl><dt>IGNORED ITEMS</dt><dd>Ignored items include files that the browser cannot handle natively, e.g., common Office and graphics files, various binary files, and many others.</dd><dd>Although they are visible in the sidebar by default, and ignored when selected, they can be hidden here.</dd><dd>Also, normal browser behavior for handling such files can be restored by unchecking "Ignore Ignored Files", but it is recommended that this preference be left checked.</dd></dl>
		<dl><dt>AUTOLOAD FILES</dt><dd> XXX </dd></dl>
		<dl><dt>MEDIA FILES</dt><dd> XXX </dd></dl>
		<dl><dt>OPEN PLAYLIST/FILELIST</dt><dd>The script supports basic .m3u playlists of audio or video files. Click to load a local .m3u file.</dd><dd>The script also has custom support for "filelists," which are standard .m3u files that contain links to directories and <i>any</i> file type supported by the script or browser.</dd><dd>Note: if you change the extension of an ordinary .m3u file to .txt, the script will read it normally as an editable text file.<br />Double-clicking the selected file in the sidebar, or typing <code>Cmd/Ctr + Down Arrow</code> or <code>Cmd/Ctr + Return</code>, will open the playlist/filelist in the sidebar.<br />NOTE: The text must begin with #EXTM3U for this work.</dd></dl>     <dl><dt>SAVE PLAYLIST/FILELIST</dt><dd>Save the files in the current sidebar directory as an .m3u playlist/filelist. Choose audio, video, all media, all non-media, all items, directories or files only.</dd></dl>
		<dl><dt>TEXT EDITING ENABLED/DISABLED</dt><dd>If text editing is disabled, text files are displayed as normal files.</dd></dl>
		<dl><dt>TEXT EDITING OPTIONS</dt><dd>Toggle the Text Editor. Select Editor UI theme: Default = same as main UI. Toggle split view. Select view of raw text, preview text, rendered HTML.</dd></dl>
		<dl><dt>OPEN FONT FILE</dt><dd>Load a local font file and view information about the font and a grid of its complete glyph repertoire.</dd><dd>Glyph grids can be navigated with the arrow keys. Individual glyphs can be selected by clicking them or pressing <kbd>Return</kbd>.</dd><dd>Individual glyphs may be saved as .svg files.</dd></dl>     <dl><dt>DEFAULT USER SETTINGS</dt><dd>This will remove the query string from the browser window URL, so that the default user settings from the code will be used instead.</dd></dl>     <dl><dt>EXPORT USER SETTINGS</dt><dd>Save a file containing the User Settings from the code, including custom bookmarks and other settings.</dd></dl></div>
		<div><strong>OTHER SCRIPT FUNCTIONS</strong><dl><dt>NAVIGATION</dt><dd>Sidebar displays list of items in current directory. Select a sidebar item to show it in the content pane.</dd><dd>Click a directory icon in the sidebar or select it and type Cmd/Ctrl Right Arrow to open subdirectory; click again to close or type Cmd/Ctrl Left Arrow.</dd></dl><dl><dt>IMAGES, FONTS, FONT GLYPHS</dt><dd>Previewed items can be scaled with <kbd>Cmd/Ctr +/&ndash;</kbd> keys.</dd></dl>     <dl><dt>IMAGE AND FONT GRIDS</dt><dd>If a directory contains images and/or font files, the "Show Grid" icon will appear in the sidebar. Click it (or type <kbd>Cmd/Ctr+G</kbd>) to show a grid of the available items.</dd><dd>Grids can be navigated with the arrow keys, and individual grid items may be viewed by clicking them or pressing <kbd>Return</kbd>.</dd><dd>When a grid item is being viewed, the grid can still be navigated with the arrow keys.</dd><dd>Closing a selected grid item will show the grid again.</dd></dl>     <dl><dt>CUE SHEETS</dt><dd>When a media file (audio or video) is loaded, the script will look for a .cue file in the same directory with <i>EXACTLY</i> the same name as the media file.</dd><dd>If it finds one, it will load the Track ID, the PERFORMER, the TITLE, and the INDEX (time position) into a menu next to the audio player; there is no support for other commands.</dd><dd>Tracks can be selected by clicking the item, and played or paused by clicking the selected item.</dd><dd>.cue files can also be selected independently in the sidebar and edited and saved (locally). This may be handy for creating "on the fly" bookmarks for a long media track before closing the page.</dd><dd>Note that you can also create and save (locally) a new .cue file by using the Text Editor.</dd><dd>Note (MacOS): If you prefer not to clutter the sidebar with .cue files, you may make them invisible by adding a dot to beginning of the file name; the script will still find them.</dd></dl><p>&nbsp;</p>
		</div></section>`;
	let Utilities_Elements = function(body_id) {
		let help_elements = '';		if ( body_id === 'top' ) { help_elements = `<aside id="help_container" class="background_grey_90 display_none">${ Utilities_Help }</aside>`; }
		return `<div id="utilities" class="display_none"> <aside id="warnings_container">${ Utilities_Warnings }</aside> ${ help_elements } </div>`;
	}
	//==============================//
	// ===> END UI HTML
	//==============================//
	// ===> STYLES
	const global_styles = `
		:root 														{ --font_size_small:0.875rem; }
		:root, html, body	{ margin:0; padding:0; border:0; border-radius:0; overflow:hidden; display:flex; flex-direction:row; width:100%; max-width:100%; height:100vh; font-family:${$settings.UI_font}; font-size:${ $settings.UI_font_size}; hyphens:auto; }
		header, footer, nav, ol, ul, li								{ margin:0; }
		header, footer, nav, a, ol, ul, li							{ padding:0; }
		a, a:hover 													{ text-decoration:none !important; }
		ul, li														{ list-style:none; }

		.margin_0													{ margin:0; }
		.padding_0													{ padding:0; }
		.padding_4_6												{ padding:4px 6px; }
		.width_10px, .width_10px_contents > *						{ width:10px; max-width:10px; min-width:10px; }
		.width_12px, .width_12px_contents > *						{ width:12px; max-width:12px; min-width:12px; }
		.width_14px, .width_14px_contents > *						{ width:14px; max-width:14px; min-width:14px; }
		.width_16px, .width_16px_contents > *						{ width:16px; max-width:16px; min-width:16px; }
		.width_18px, .width_18px_contents > *						{ width:18px; max-width:18px; min-width:18px; }
		.width_24px, .width_24px_contents > *						{ width:24px; max-width:24px; min-width:24px; }
		.width_100													{ width:100%; }
		.position_absolute											{ position:absolute; }
		.position_relative											{ position:relative; }
		.position_fixed												{ position:fixed; }
		.display_none 												{ display:none; }
		.display_block 												{ display:block; }
		.display_flex												{ display:flex; }
		.display_flex_column										{ display:flex; flex-direction:column; }
		.display_flex_row											{ display:flex; flex-direction:row; }
		.flex_justify_center										{ display:flex; flex-direction:column; justify-content:center; flex-grow:1; align-items:center; align-self:stretch; text-align:center; }
		.flex_justify_center_row									{ display:flex; flex-direction:row; justify-content:center; flex-grow:1; align-items:center; }
		.flex_justify_contents										{ justify-content:space-between; }
		.flex_grow_1												{ flex-grow:1; }
		.display_grid												{ display:grid; }
		.transform_rotate_90, 	.transform_rotate_90_contents > *	{ transform:rotate(90deg);  }
		.transform_rotate_180, .transform_rotate_180_contents > *	{ transform:rotate(180deg); }
		.transform_rotate_270, .transform_rotate_270_contents > *	{ transform:rotate(270deg); }
		svg															{ margin:auto; }
		.theme_dark .invert											{ filter:invert(1); }

		.normal														{ font-weight:normal; }
		.bold, .hover_bold:hover									{ font-weight:bold; }
		.align_left													{ text-align:left; }
		.center														{ text-align:center; }
		.align_right												{ text-align:right; }

		.pointer, label, input										{ cursor:pointer; }
		.media.local input											{ cursor:not-allowed; }

		.info_list						{ padding:0; background-color:hsl(0,0%,var(--percent_80)); font-size:var(--font_size_small); color:hsl(0,0%,var(--percent_10)); border:solid 1px hsl(0,0%,var(--border_lum)); }
		.info_list li					{ grid-template-columns:minmax(33%,100%) min(66%); border-top:solid 1px hsl(0,0%,var(--border_lum)); }
		.info_list:hover li				{ display:grid; }
		.info_list li.info_list_header	{ border-top:none;  }
		.info_list li .col_1			{ font-weight:bold; text-align:right; border-right:solid 1px hsl(0,0%,var(--border_lum)); }
		.info_list span					{ display:inline-block; padding:4px 6px; line-height:1.2; }

		.has_flyout_menu 				{ display:none; position:relative; outline:none; justify-content:center; align-content:center; z-index:9997; }
		.has_flyout_menu ul				{ display:none; position:absolute; list-style:none; }
		.has_flyout_menu:hover ul		{ display:grid; }
		.has_flyout_menu ul li			{ width:100%; margin:0; padding:4px 6px; text-align:right; box-sizing:border-box; white-space:pre; grid-column:1; }
		.has_flyout_menu ul li:hover	{ font-weight:bold; }
		.has_flyout_menu ul div			{ grid-column:2; padding-top:7px; }
		.has_flyout_menu .item_1 		{ grid-row:1; }
		.has_flyout_menu .item_2 		{ grid-row:2; }
		.border_all						{ border:			solid 1px hsl(0,0%,var(--border_lum)); }
		.border_top						{ border-top:		solid 1px hsl(0,0%,var(--border_lum)); }
		.border_right					{ border-right:		solid 1px hsl(0,0%,var(--border_lum)); }
		.border_bottom					{ border-bottom:	solid 1px hsl(0,0%,var(--border_lum)); }
		.border_left					{ border-left:		solid 1px hsl(0,0%,var(--border_lum)); }
		.border_top_x					{ border-top:		solid 1px hsl(0,0%,var(--border_lum_inverted)); } /* "x" = inverted for theme_dark */
		.border_right_x					{ border-right:		solid 1px hsl(0,0%,var(--border_lum_inverted)); }
		.border_bottom_x				{ border-bottom:	solid 1px hsl(0,0%,var(--border_lum_inverted)); }
		.border_left_x 					{ border-left:		solid 1px hsl(0,0%,var(--border_lum_inverted)); }
	`;
	const utilities_styles = `
		#utilities															{ position:absolute; left:0; right:0; top:0; z-index:9999; justify-content:center; }
		#warnings_container													{ width:26em; flex-direction:column; border-radius:0 0 3px 3px; z-index:9999; background-color:hsl(0,0%,var(--percent_90)); box-shadow:0px 2px 12px 0 #333; font-size:0.875em; color:#111; overflow:hidden; display:none; }
		#warnings_header													{ padding:1rem 1.5rem; background-position:left 1.25rem center; background-repeat:no-repeat; background-size:24px; }
		#warnings_container:not(.warning_make_playlist) #warnings_header	{ background-image:${ get_SVG_UI_Icon("error") }; }
		#warnings_header h3													{ display:none; margin:0; text-indent:2.25em; }
		#warnings_container:not(.warning_make_playlist) h3#warning_header, #warnings_container.warning_make_playlist h3#make_playlist_header
																			{ display:flex;}
		body#top.edited #warnings_container.unloading h3::before			{ content:"Text Editor:" }
		#warnings .warning													{ padding:0 1.5rem 1rem; display:none; hyphens:none; }
		#warning_buttons_container									 		{ padding:1rem 1.5rem; }
		#warning_buttons button												{ min-width:4em; display:none; font-size:1em; justify-content:center; }
		button.focus, button:focus											{ background-color:#0E4399; color:#EEE; outline:none; }
		#warning_btn_dont_save												{ margin-right:auto; }
		#warning_btn_cancel, #warning_btn_clear, #warning_btn_save			{ margin-left:0.5rem; }
		#warning_btn_ok														{ margin-left:auto; }
		#warnings_container.warning_close_font 			#warning_close_font, #warnings_container.warning_close_playlist	#warning_close_playlist, #warnings_container.unloading #warning_unsaved_text, #warnings_container.clear #warning_clear_text, #warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_no_playlist #warning_no_playlist, #warnings_container.warning_make_playlist #warning_make_playlist,
		#warnings_container:not(.unloading):not(.clear) #warning_btn_ok, #warnings_container:not(.warning_local_bookmark):not(.warning_local_file):not(.warning_no_playlist) #warning_btn_cancel, #warnings_container.clear #warning_btn_clear, #warnings_container.unloading #warning_btn_dont_save
																			{ display:flex; }
		#warnings_container.clear #warning_buttons							{ justify-content:space-between; }
		#warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_bookmark #warning_btn_ok, #warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_local_file #warning_btn_ok, #warnings_container.warning_close_font #warning_btn_ok, #warnings_container.warning_close_playlist #warning_btn_ok			{ margin-left:auto; }
		#warning_make_playlist fieldset										{ border:0; }
		#warning_make_playlist fieldset div									{ padding:0 0 2px; }
		#warning_make_playlist .indent										{ text-indent:2em; }
		#warning_make_playlist input										{ margin-right:6px; }
		#warnings_container.warning_local_playlist #warning_local_playlist, #warnings_container.warning_local_playlist #warning_btn_ok	{ display:inline-block; margin-left:auto; }
		.has_warning #utilities, .has_warning #warnings_container			{ display:flex; }
		.has_warning::before, .has_overlay::before							{ content:""; position:fixed; top:0; right:0; bottom:0; left:0; z-index:9998;-webkit-user-select:none;-moz-user-select:none; user-select:none; }

		#help_container														{ padding:0 1em 1em;  overflow:auto; }
		#help_container > header											{ position:fixed; left:0; right:0; grid-template-columns:5em auto fit-content(100%); }
		#help_container > section											{ padding-top:2rem; }
		#help_container > section > div										{ margin-top:1rem; padding-top:1rem; border-top:solid 1px hsl(0,0%,var(--border_lum)); }
		#help_container dd													{ margin-inline-start:1em; }
		#help_container dd:before											{ content:"\u2219"; margin-right:6px; }
		#utilities_help kbd					{ min-width:1em; height:fit-content; padding:2px 6px; display:inline-block; border:solid 1px #888; border-radius:3px; text-align:center; font-family:inherit; font-size:0.875em; background-color:hsl(0,0%,var(--percent_90)); }
		#help_main_menu div													{ margin-right:6px; width:14px; display:inline-flex; }
		#help_bookmarks::before												{ background-image: ${ get_SVG_UI_Icon("bookmark") }; }
		.has_help #utilities, .has_help #help_container						{ display:flex; bottom:0; }
	`;
	const directory_header_menu_styles = `
			/* PARENTS MENU */
		#parent_dir_nav svg													{ width:18px; }
		#close_playlist_btn													{ background-image:${ get_SVG_UI_Icon("multiply") }; background-position:center; background-size:12px; justify-content:center; opacity:0.55; }
		#close_playlist_btn_container, #close_playlist_btn					{ display:none; flex-basis:24px; }
		#parents_dir_nav													{ line-height:1.4; }
		#parents_links														{ right:0; left:0; box-shadow:0px 4px 6px -3px #333; z-index:9998; }
		#parents_links a													{ padding:4px 8px; }
		#current_dir_path													{ padding:2px 6px 4px; hyphens:none; word-break:break-word; z-index:9998; }
			/* MAIN MENU */
		#menu								{ right:0;left:0; zindex:9998; box-shadow:0px 4px 6px -3px #333; z-index:9998; }
		#menu li							{ display:flex; }
		#menu li.has_submenu				{ position:relative; justify-content:space-between; }
		#menu li.bookmark a::before			{ content:""; width:24px; max-width:24px; min-width:24px; height:12px; background-size:12px; }
		.submenu							{ width:100%; max-width:240px; display:none; margin:0; padding:0; position:absolute; top:-1px !important; left:100%; right:0; box-shadow:0px 4px 6px -3px #333; }
		#menu ul.submenu li a				{ padding:6px 8px 6px 0; }
		#menu input							{ width:0; float:left; }
		.menu_item							{ margin:0; padding:5px 8px 5px 0; display:flex; flex-grow:1; }
		#menu .selected ~ li:hover .submenu, #menu .selected ~ li .submenu:hover, .has_open	{ display:none; }
		#menu svg							{ margin: 0 6px; width:12px; }
			/* IFRAME MENUS */
		#parent																	{ padding:5px 3px 5px 0; }
		#parent span															{ padding:0px 1px; }
		#open_in_sidebar														{ padding:5px 2px 5px 3px; }
	`;
	const directory_header_styles = `	/* for both sidebar and content_iframe */
		#directory_wrapper														{ z-index:1; }
		#directory_wrapper.top													{ min-width:220px; }
		#directory_wrapper.iframe												{ min-width:500px; flex-basis:100%; }
		#directory_header														{ font-size:var(--font_size_small); z-index:3; user-select:none; -webkit-user-select:none; }
		.directory_title_div													{ letter-spacing:0.5em; text-indent:0.75em; flex-basis:100%; }
													.directory_title_div:before	{ content:"INDEX OF"; }
		.has_playlist								.directory_title_div:before	{ content:"PLAYLIST"; }
		.has_filelist								.directory_title_div:before	{ content:"FILELIST"; }
		${ directory_header_menu_styles }
			/* SIDEBAR BUTTONS */
		#directory_buttons_left													{ padding:6px; }
		#show_details															{ margin-top:0; margin-right:0.5em; padding:0 4px 0 4px; }
		#show::before															{ content:"Show "; }
		#show_invisibles														{ margin:0 4px 0 0; }
			/* GRID BTN ---> combine style with save_btn */
		#show_grid																{ margin:0 0 0 auto; }
		#show_grid ul															{ padding-left:0px; top:-1px; right:-1px; box-shadow:0px 4px 6px -3px #333; }
		#show_grid.has_grid div													{ color:#118888; }
		#top.has_images #show_grid, #top.has_fonts #show_grid							{ display:flex; }
		.has_images.has_fonts #show_grid:hover ul.menu							{ display:grid; }
		/* SORTING ROW */
		#sorting .sorting														{ grid-row:1; white-space:pre; cursor:pointer; }
		.show_details #sorting_row_2											{ display:grid; }
		#sorting_row_2.iframe													{ grid-template-columns:minmax(14em,100%) minmax(6em,8em) minmax(4em,8em) minmax(4em,14em) minmax(4em,8em); }
		#sorting_row_1 span, #sorting_row_2 span, .sorting span::before, .sorting span::after
																				{ display:inline-block; }
		#sorting_row_1 span, #sorting_row_2 span								{ padding:6px 0; }
		#sorting_row_1.iframe span, #sorting_row_2.iframe span					{ padding:4px 0; }
		#sorting span::before, #sorting span::after, .sorting .menu_item::after	{ content:""; width:16px; height:8px; color:#CCC; background-position:center; background-repeat:no-repeat; background-size:10px; }
		.sorting.down span::after, .sort_descending .sorting span::after		{ transform:rotate(0deg) !important; }
		#sort_by_name input														{ margin:-2px 6px -2px 0; bottom:-2px; }
		#sort_by_ext															{ grid-column: span 2; }
		.has_media #sort_by_ext													{ grid-column: span 1; }
		.has_media #sort_by_default												{ text-align:center; }
		#sort_by_default.iframe, .iframe #sort_by_size, .iframe #sort_by_date	{ text-align:right; }
		.has_media #sort_by_duration, .has_playlist #sort_by_duration			{ display:unset; }

			/* TEXT EDITOR ROW */
		.show_details #show_text_editor, #show_text_editor.has_text_editor		{ display:flex; }
		#show_text_editor a														{ padding:6px 0 6px 16px; }
	`;
	const directory_nav_styles = `	/* for both sidebar and content_iframe */
		.directory_item_name_a::before, .directory_item_input, .directory_item_details, .directory_item_details span, .directory_item_media_duration, .details.ext, .directory_item.error::before, .hide_ignored_items:not(.has_stats) #directory_list li.ignored, .hide_ignored_items:not(.has_stats) .directory_item.ignored, .hide_ignored_items:not(.show_invisibles) .directory_item.ignored.invisible, body:not(.show_invisibles) .directory_item.invisible
																		{ display:none; }
		#directory_nav							 						{ overflow-y:hidden; flex-basis:100%; font-size:var(--font_size_small); }
		#directory_nav_inner											{ overflow:auto; margin-bottom:-1px; }
		#directory_nav ol												{ -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }
		#directory_list													{ height:100%; counter-reset:item; transition:opacity .125s; }
		#directory_list:empty											{ padding:100%; }
							.directory_item								{ margin-inline-start:0; display:grid; grid-gap:0; font-variant-numeric:tabular-nums; }
		.top_item														{ grid-template-columns:minmax(8rem,auto) minmax(6em,1fr) minmax(auto,6em); }
							.directory_item_name						{ grid-row:1; display:flex; -webkit-padding-start:0; -moz-padding-start:0; word-break:break-word; }
		.top_item			.directory_item_name						{ grid-column:1 / span 3; padding:6px 12px 6px 0; }
		.top_item.media 	.directory_item_name						{ grid-column:1 / span 2; padding-right:0; }
							.directory_item_name::before				{ counter-increment:item; content:counter(item); width:36px; min-width:36px; height:14px; max-height:14px; min-height:14px; text-align:right; padding:0; }
							.directory_item_input						{ margin:1px 6px 0 0; }
		.has_icon_before												{ display:inline-block; }
		.has_icon_before::before, .has_icon_before_before				{ content:""; display:inline-block; max-width:28px; width:28px; min-width:28px; height:20px; margin-top:-3px; margin-bottom:-6px; background-position:center; background-repeat:no-repeat; background-size:14px,0px; }
		.directory_item.image											{ position:relative; }
		body.show_image_thumbnails:not(.has_grid) .directory_item.image .has_icon_before_before:hover
																		{ position:absolute; width:72px; max-width:unset; height:72px; max-height:unset; background-size:contain; background-color:hsl(0,0%,var(--percent_95)); z-index:2; top:8px; left:14px; }
		body.show_image_thumbnails:not(.has_grid) .directory_item.image .has_icon_before_before:hover + .name_span								{ padding-left:28px; }
		.directory_item.media	.directory_item_media_duration			{ grid-row:1; }
		.top_item.media			.directory_item_media_duration			{ grid-column:3; padding:6px 12px 6px 0; }
		.media:not(.local)		.directory_item_media_duration			{ display:unset; }

		.sort_by_default.show_invisibles .dir.invisible + .dir:not(.invisible), .sort_by_default.show_invisibles .dir:not(.invisible) + .dir.invisible			{ border-top:solid 1px hsl(0,0%,var(--border_lum)); }

							.directory_item_details						{ text-align:right; white-space:nowrap; }
		.top_item			.directory_item_details						{ padding:0 12px 4px 0; }
							.directory_item_details.size				{ padding-left:12px; }
							.directory_item_details.date				{ padding-bottom:0; height:1em; max-height:1em; overflow:hidden; overflow-wrap:break-word; }
							.directory_item_details.kind::first-letter	{ text-transform:uppercase; }

		#iframe_body .iframe_item										{ grid-template-columns: minmax(20em,100%) minmax(4em,6em) minmax(6em,8em) minmax(6em,14em) minmax(6em,8em); }
		#iframe_body .iframe_item.media	.directory_item_media_duration	{ grid-column:2; }
		#iframe_body .iframe_item.not_media .directory_item_name		{ grid-column:1 / span 2; }
		#iframe_body .iframe_item .directory_item_details				{ grid-row:1; height:1ex; }
		#iframe_body .iframe_item .directory_item_name_a, #iframe_body .iframe_item > span		{ padding:5px 16px 5px 0; }

		.directory_item:hover a, .selected a, .media[class*="_loaded"] a	{ opacity:1 !important; font-weight:bold; }

		.show_numbers .directory_item_name_a::before						{ display:initial; }
		.show_details .directory_item_details, .media .directory_item_input	{ display:unset; }

		.disabled, .ignore_ignored_items li.ignored, .has_filelist [id$="sort_by_size"], .has_playlist [id$="sort_by_size"], .has_filelist [id$="sort_by_date"], .has_playlist [id$="sort_by_date"]
																		{ cursor:not-allowed; opacity:0.66; }
		.directory_item.error 											{ display:block; padding:6px 8px; }
		.show_invisibles .directory_item.invisible						{ display:grid; }
		.directory_item.ignored.local .directory_item_name_a::after		{ content:"\\00a0[local file]"; display:contents; font-style:italic; }
		body.show_invisibles .invisible, .directory_item:not(.ignored):not(.invisible), body.show_invisibles .directory_item.invisible, body.has_stats .directory_item.invisible, body.has_stats .directory_item.ignored,  body.has_stats .directory_item.ignored.hovered, body.hide_ignored_items.has_stats .directory_item.ignored, body.hide_ignored_items.has_stats .directory_item.ignored.invisible, body:not(.hide_ignored_items) .directory_item.ignored:not(.invisible)									{ display:grid; }
		.tr[data-level] .directory_item_name_a::before { width:${ ( ( Number(getSearchParam("level") ) + 1) * 22 ) + 28 }px; }

		${ CSS_UI_Icon_Rules() } /* background icons */
		.is_error #is_error												{ display:block !important; grid:none !important; grid-template-columns:none !important; }
		.is_error #is_error_items										{ display:block; padding:6px 8px; }
	`;
	const iframe_dir_styles = `
		${ global_styles }
		#iframe_body																{ overflow-x:auto; font-size:${ (parseFloat($settings.UI_font_size) * 0.875) + $settings.UI_font_size.replace(/\d*/,'') }; }
		.theme_dark .sorting span::before, .theme_dark .sorting span::after 		{ filter:invert(1); }
		#iframe_body.is_blurred #directory_list_outer								{ opacity:0.75; }
		.show_details #show::before																	{ content:"Hide "; }
	`;
	const directory_footer_styles = `	/* for both sidebar and content_iframe */
		.has_stats #stats_summary, .stats_kind span.file, .stats_kind span.media	{ display:none; }
		#directory_footer									{ font-variant-numeric:tabular-nums; user-select:none; -webkit-user-select:none; font-size:var(--font_size_small); }
		#stats_container									{ width:100%; max-height:${ (window.clientHeight * 0.33) }px; }
		#stats												{ font-size:var(--font_size_small); overflow:hidden; }
		.theme_light #footer_utilities:hover ul, .theme_light #directory_footer:hover, .theme_light #stats_summary_detailed				{ box-shadow:0px -4px 4px 0px rgba(128,128,128,0.6); }
		.theme_dark #footer_utilities:hover ul, .theme_dark #directory_footer:hover, .theme_dark #stats_summary_detailed				{ box-shadow:0px -4px 4px 0px  rgba(32,32,32,0.6); }
		#stats_summary_detailed_dirs .stats_kind::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; }
		#stats_summary_detailed_files .stats_kind::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_file_default") }; }
		#stats_summary_detailed, #stats_details_container	{ overflow-y:scroll; }
		.stats_list_item 									{ line-height:1.2; }
		#stats_summary_totals, .has_media #total_duration 	{ display:flex; text-align:left; font-weight:normal; white-space:normal; padding-right:1em; }
		.has_media #total_duration::before					{ content:"Total Time:\\00a0"; }
		#stats a 											{ padding:4px 12px 4px 0; }
		#stats_summary_detailed_total li, #stats_summary_playlist_container	{ padding:5px 8px; overflow:hidden; white-space:pre; }
		#stats a::before									{ content:attr(data-count); width:36px; text-align:right; }
		.stats_kind span									{ margin-right:0.5em; white-space:pre-wrap; display:inline-block; }
		.stats_kind > span::first-letter 					{ text-transform:uppercase; }
		#stats_details span.ignored::before,#stats_details span.invisible::before		{ content:"("; }
		#stats_details span.ignored::after,#stats_details span.invisible::after			{ content:")"; }
		#stats_details li.audio a span span::after 										{ content:attr(data-audio_duration); white-space:pre; }
		#stats_details li.video a span span::after									 	{ content:attr(data-video_duration); white-space:pre; }
		#total_duration::after									 						{ content:attr(data-time_remaining); white-space:pre; }
			/* IFRAME STATS: */
		.stats_list_item_name_a								{ -webkit-padding-start:0; padding:1px 0; }
		#stats_summary, #stats_summary_detailed				{ margin-block-start:0; margin-block-end:0; }
		#stats_summary li, #stats_summary_detailed_total, #stats_summary_playlist_container { padding:4px 8px; }
		.stats_list_item_name_a_span, #stats .stats_kind span { margin-right:0.5em; white-space:pre; display:inline-block; }
		#stats a.directory_item_name_a:before 				{ display:inline-block; }
		#stats_details										{ max-height:25vh; }
		#stats_container, .has_stats #stats_summary_detailed, .has_stats #stats_details		{ display:block; }
			/* SIDEBAR FOOTER LINKS */
		#footer_utilities									{ z-index:1; right:-1px; bottom:-1px; }
		#footer_utilities ul								{ position:absolute; bottom:0; right:0; white-space:nowrap; box-shadow:-0px -3px 6px -3px #333; }
		#footer_utilities:hover ul							{ display:block; }
		.has_stats #footer_utilities						{ display:none; }

	`;
	const directory_utilities_styles = `
		#hide_sidebar										{ top:0; right:0; height:21px; z-index:9997; cursor:pointer; opacity:0.6; }
		#hide_sidebar:hover									{ opacity:1; }
			/* HAS HIDDEN SIDEBAR */
		.hide_sidebar #handle								{ display:none; }
		.hide_sidebar #hide_sidebar							{ left:2px; transform:rotate(180deg); }
		.hide_sidebar #directory_wrapper					{ width:0 !important; min-width:0; position:absolute; top:2px; left:-1px; }
		.hide_sidebar #directory_header						{ z-index:unset; display:none; }
		.hide_sidebar #directory_nav						{ visibility:hidden; } /* allows hidden sidebar to be navigated by arrows */
		.hide_sidebar #directory_list_outer					{ min-width:0; }
		.hide_sidebar #content_pane							{ width:100% !important; }
		.hide_sidebar #title_buttons_left					{ padding-left:24px; }
		#handle												{ top:0; bottom:0; z-index:1; right:-4px; width:7px; cursor:col-resize; }
		.has_overlay #handle								{ z-index:9999; }
	`;
	const sidebar_styles = `${ directory_header_styles }     ${ directory_nav_styles }     ${ directory_footer_styles }     ${ directory_utilities_styles }`;
	/* CONTENT PANE STYLES */
	const content_pane_header_styles = `
		#content_header										{ font-size:var(--font_size_small); z-index:3; }
			/***** CONTENT TITLE *****/
		#content_title_container							{ overflow-x:scroll; }
		#content_title										{ line-height:1.4; min-width:16em; min-height:18px; padding:4px 8px; text-align:center; word-break:break-word; vertical-align:top; }
		#content_title span									{ font-weight:bold; hyphens:none; }
		#content_title span::before							{ font-weight:normal; }
		#content_pane[data-content="has_font"] 												#content_title::before		{ content:"Font:" }
		#content_pane[data-content="has_font_file"]								 			#content_title::before		{ content:"Glyphs from font:" }
		.has_directory_source 																#content_title::before		{ content:"Source of:" }
		#content_pane[data-content="has_grid"] 												#content_title::before		{ content:"Fonts and Images from:"; }
		#content_pane[data-content="has_grid"].has_font_grid 								#content_title::before		{ content:"Fonts from:"; }
		#content_pane[data-content="has_grid"].has_image_grid 								#content_title::before		{ content:"Images from:"; }
		#content_pane[data-content="has_ignored"] 											#content_title::before		{ content:"Ignored content:"; }
		#content_pane[data-content="has_dir"] 												#content_title::before 		{ content:"Index of:"; }
		#content_pane[data-content="has_grid"] 												#content_title::after		{ content:"${ current_dir }"; }
		.text_editor_raw:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"]			#content_title::after		{ content:" (Source Text)"; }
		.text_editor_preview:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"]		#content_title::after		{ content:" (Text Preview)"; }
		.text_editor_html:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"] 		#content_title::after		{ content:" (HTML Preview)"; }
		#content_pane[data-content="has_text_editor"] 										#content_title span::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_markdown") }; }
		#content_pane[data-content="has_font_file"]											#content_title span::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_font") }; }
		#content_pane[data-content="has_grid"] 												#content_title span::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; height:14px !important; }
		#content_pane[data-content="has_view_directory_source"] 							#content_title span::before	{ background-image:${ get_SVG_UI_File_Icon("file_icon_dir_default") }; height:14px !important; background-size:contain; }
		.is_error 																			#content_title span::before	{ content:"ERROR"; white-space:pre; }
		#content_pane[data-content="has_image"] 											#content_title span::after	{ content:attr(data-after); font-weight:normal; }
		#content_pane[data-content="has_text_editor"] 										#content_title span::after	{ content:"Text Editor"; font-weight:bold; }
		#content_pane[data-content="has_text_editor"].edited							 	#content_title span::after	{ content:"Text Editor (edited)"; font-weight:bold; }
		#content_pane[data-loaded="unloaded"] 												#content_title::before		{ content:"Loading..." }
		#content_pane[data-loaded="unloaded"] 												#content_title span			{ display:none; }
			/* CONTENT TITLE BUTTONS LEFT */
		#reload_btn							{ width:52px; }
		#reload_btn::before					{ content:"Reload"; }
		#prev_next_btns						{ margin-left:4px; }
		#prev_next_btns span				{ width:2em; height:16px; }
		#prev_next_btns span:active			{ background-color:#0E4399; }
		#prev_next_btns:focus				{ background-color:white; }
		#prev_next_btns svg					{ width:12px; }
			/* CONTENT TITLE BUTTONS RIGHT */
		#scale								{ margin-right:4px; background-color:#FFF; }
		#scale span							{ width:2em; }
		#close_btn							{ width:52px; }
		#close_btn::before													{ content:"Close"; }
		#content_pane[data-content="has_text_editor"] #close_btn::before	{ content:"Hide"; }
		.split_btn span						{ display:inline-flex; }
		.split_btn::after					{ content:""; position:absolute; top:0; bottom:0; left:50%; border-left:solid 1px #333; }
		#open_in_text_editor				{ margin-right:4px; }
	`;
	const content_pane_audio_styles = `
			/* CONTENT AUDIO TITLE */
		#content_audio_title span												{ width:100%; padding:4px 6px 0; text-align:center; line-height:1.4; cursor:pointer; }
		#content_audio_title span::before										{ content:""; padding-right:22px; height:14px !important; font-weight:normal; background-image:${ get_SVG_UI_File_Icon("file_icon_audio") }; background-position:center; background-position:right 4px center; background-repeat:no-repeat; }
		#content_pane.has_audio #content_audio_title span::before, #content_pane[data-content="has_video"] #content_title::before	{ content:"Playing:"; }
			/* CONTENT AUDIO PLAYER */
		#content_audio 															{ justify-content:center; padding:2px 6px 6px; overflow-x:auto; }
		#audio_container														{ height:32px; background-color:rgb(241, 243, 244); }
		#prev_track, #next_track												{ width:2rem; }
		#audio																	{ height:32px; }
		audio::-webkit-media-controls-enclosure 								{ border-radius:0; }
		#close_audio															{ width:32px; }
		#audio_options															{ margin-top:0; margin-right:calc(-6em - 8px); padding:0 4px; width:6em; justify-content:start; }
		#loop_label input														{ margin:0px 4px 2px}
		#shuffle_label input													{ margin:2px 4px 0px}
			/* CUE SHEET MENU */
		.cue_sheet_track_list_container 	{ width:32px; background-image:${ get_SVG_UI_File_Icon("file_icon_playlist") }; background-position:center; background-repeat:no-repeat; background-size:18px; background-blend-mode:luminosity; background-color:inherit; display:none; }
		.cue_sheet_track_list 													{  }
		#cue_sheet_track_list_container_audio:hover > div 		{ padding-top:13px; display:flex; flex-direction:column; position:absolute; left:0; right:0; margin-top:-1px; z-index:1; overflow:auto; font-size:0.875em; }
		.cue_sheet_track 														{ display:flex; justify-content:space-between; }
		.cue_sheet_track.selected .cue_track_id::before							{ content:""; width:16px; height:8px; display:flex; }
		.cue_sheet_track span													{ padding:4px 8px; font-variant-numeric:tabular-nums; }
		#cue_sheet_title 														{ padding:4px 8px; font-variant-numeric:tabular-nums; text-align:center; }
			/* CONTENT TITLE PLAYLIST ENTRY (#content_playlist and #content_audio_playlist) */
		.playlist_entry_container									 			{ flex-direction:row; }
		.playlist_entry_container textarea										{ padding:0 6px; width:100%; border:0; resize:vertical; }
		#content_pane.has_audio #audio_wrapper, .playlist_entry_container.has_content	{ display:flex; flex-direction:column; }
		audio::-webkit-media-controls-panel										{ padding:0; }
	`;
	const content_pane_styles = `
		#content_pane						{ padding:0; height:100%; transform:scale(1); vertical-align:top; contain:strict; }
		${ content_pane_header_styles }
		${ content_pane_audio_styles }
			/***** CONTENT_CONTAINER *****/
		#content_container					{ justify-content:center; bottom:0; background-position:center; background-repeat:no-repeat; background-size:33.33%; contain:strict; flex-basis:100%; overflow:auto; }
		.content																		{ width:100%; height:100%; margin:0; padding:0; overflow:auto; display:none; }
		#content_pane[data-loaded="unloaded"] 		#content_container > svg			{ display:block; }
		#content_pane[data-loaded="unloaded"] 		.content							{ display:none !important; }
			/* CONTENT GRID (div) */
		#content_grid				{ width:100%; font-size:1rem; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(${ ( $settings.grid_image_size + 16) }px, auto)); grid-auto-rows:minmax(min-content, max-content); }
		#content_pane.has_hidden_grid #content_grid				{ max-height:100%; overflow:hidden; visibility:hidden; }
		#content_grid::after									{ content:""; width:1px; position:absolute; top:0; right:0; bottom:0; }
		#content_pane[data-content="has_grid"] #content_grid	{ display:grid; }
			/* IMAGE GRID ITEMS */
		.image_grid_item			{ padding:6px; grid-column:auto; line-height:0; }
		.image_grid_item img		{ width:auto; max-width:${ ($settings.grid_image_size).toString() }px; max-height:${ ($settings.grid_image_size) }px; position:relative; }
		.image_grid_item img[src$=".svg"]						{ height:100%; }
		.image_grid_item.selected	{ box-shadow:inset 0px 0px 4px hsl(0,0%,var(--percent_60)); }
			/* FONT GRID ITEMS */
		.font_grid_item				{ line-height:1; padding:8px 20px; grid-column:1 / -1; }
		.font_grid_item p			{ padding:0 0 6px 0; line-height:1; font-size:1rem; letter-spacing:0.1em; text-indent:0.1em; }
		.font_grid_item h2			{ font-size:${ $settings.grid_font_size * 4 }em; font-weight:normal; }
		.image_grid_item + .font_grid_item { margin-top:-1px; border-top:solid 1px hsl(0,0%,var(--border_lum_inverted)); }
			/* CONTENT FONT.content */
		#content_font				{ hyphens:none; font-size:${ $settings.grid_font_size }em; overflow-wrap:break-word; flex-direction:column; }
		#content_pane[data-content="has_font"] #content_container	{ overflow:hidden; }
		#content_pane[data-content="has_font"] #font_toolbar		{ display:flex; flex-direction:row; left:0; right:0; padding:2px 4px; overflow-x:scroll; overflow-y:hidden; -webkit-user-select:none;-moz-user-select:none; user-select:none; z-index:3; }
		#font_toolbar li			{ padding:0 4px; white-space:pre; }
		#font_toolbar li.text		{ width:50%; font-size:var(--font_size_small); }
		#font_tag_textarea			{ resize:none; }
		#font_specimen				{ max-width:100%; padding:40px 20px 20px; flex-direction:column; overflow-x:hidden; }
		#font_specimen .specimen	{ margin:6px 0; padding: 0 8px 12px; overflow:visible; font-weight:normal; line-height:1.2; outline:none; }
		#font_specimen .specimen:focus,#font_specimen .specimen:focus-visible { box-shadow:inset 0 0 2px 2px hsl(212deg 50% 60%); border-radius:3px; }
		#font_specimen hr			{ width:100%; margin:0; }
		#specimen					{ font-size:4em; font-weight:normal;}
		#font_specimen_grid			{ display:grid; font-size:4em; hyphens:auto; margin:0 0 20px; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(1.5em, auto)); grid-auto-rows:minmax(min-content, max-content); overflow:visible; }
		#font_specimen_grid:empty, #specimen_glyph:empty, #font_specimen_grid:empty + hr { display:none; }
		#font_specimen_grid div { grid-column:auto; padding:6px; display:flex; justify-content:center; position:relative; cursor:pointer; }
		#font_specimen_grid div::before, #font_toolbar::before { content:attr(data-char); position:absolute; font-size:0.75rem; bottom:0; left:2px; font-family:${$settings.UI_font}; opacity:0.66; font-feature-settings:normal; font-variant:normal; }
		#font_specimen_grid div::after, #font_toolbar::after { content:attr(data-unicode_hex); position:absolute; font-size:0.75rem; bottom:0; right:2px; font-family:${$settings.UI_font}; opacity:0.66; font-feature-settings:normal; font-variant:normal; }
		#font_toolbar::before, #font_toolbar::after		{ display:block; padding:4px 6px; bottom:calc(-9px - 1em); font-size:0.875em; opacity:1; z-index:2; }
		#specimen_2, #specimen_3	{ padding:0.125em 0; line-height:1; text-overflow:ellipsis; white-space:pre; overflow:clip; overflow-clip-margin:20em; }
		#specimen_2					{ font-size:8em; }
		#specimen_3					{ font-size:6em; }
		#specimen_2H4				{ font-size:1.618em; }
		#specimen_3H3				{ font-size:2em; hyphens:auto; }
		#specimen_string_2, #specimen_string_3	{ padding:0 0 20px; text-align:justify; hyphens:auto; }
		.lorem						{ text-align:justify; hyphens:auto; font-size:1em; column-gap:1.5em; overflow-wrap:normal; }
		#lorem::first-line			{ letter-spacing:0.1em; text-indent:0.1em; font-size:${ $settings.grid_font_size * 1.33 }em; font-variant:small-caps; }
		#lorem_2					{ padding:12px 0 0; columns:2; }
		#lorem_3					{ padding:12px 0 0; columns:3; }
		#specimen_glyph				{ z-index:2; }
		#specimen_glyph_overlay		{ z-index:1; }
		#specimen_glyph, #specimen_glyph:not(:empty) + #specimen_glyph_overlay				{ top:0; left:0; right:0; bottom:0; display:flex; justify-content:center; font-size:64vw; overflow:visible; user-select:none; }
					/* FONT GLYPHS */
		#font_file_viewer				{ font-family:unset; width:100%; }
		#content_pane[data-content="has_font_file"] #font_file_viewer, #content_pane[data-content="has_glyph"] #font_file_viewer				{ overflow:auto; }
		#content_pane[data-content="has_glyph"] #font_file_grid 	{ visibility:hidden; }
		#font_file_grid		{ margin-bottom:21px; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }
		#glyph_container			{ width:100%; justify-content:center; }
		.glyph_container:hover, .glyph_container.selected		{ z-index:1; }
		#glyph_container .glyph_container, .glyph_container:hover svg, .glyph_container.selected svg, #svg_container, #svg_container svg		{ overflow:visible; }
		#svg_container svg			{ width:50%; }
		.glyph_info					{ padding:2px; position:absolute; right:0; bottom:0; left:0; font-size:0.75rem; justify-content:space-between; }
		#glyph_viewer				{ z-index:1; top:0; right:0; bottom:0; left:0; background-color:#FFF; background-position:center; background-repeat:no-repeat; background-size:contain; }
		#glyph_viewer_info			{ position:fixed; z-index:1; right:0; left:0; text-align:center; height:18px; line-height:1.6; display:flex; font-size:var(--font_size_small); }
 		#glyph_viewer_info div		{ padding:0; }
 		#glyph_viewer svg path, #glyph_viewer svg g		{ transform-origin:center; }
		#save_svg_hidden			{ float:left; visibility:hidden; }
		#glyph_viewer_info div::before { content:"Glyph "; }
		#save_svg					{ float:right; }
		#font_info					{ margin:0; max-height:${ (window.innerHeight * 0.75) }px; left:-1px; right:0; bottom:-1px; z-index:2; font-size:var(--font_size_small); overflow-y:auto; }
		#font_info:hover			{ box-shadow:0px 4px 6px 3px #333; }
		#content_pane[data-content="has_font_file"] #font_info { display:block; }
			/* CONTENT TEXT EDITOR */
		#content_text				{ width:100%; max-width:100%; height:100%; display:none; flex-direction:column; overflow:hidden; z-index:1; }
			/* OTHER CONTENT ELEMENTS */
		#content_image_container	{ padding:2rem 2.5rem; box-sizing:border-box; }
		.has_zoom_image #content_image_container, .has_scaled_image #content_image_container { padding:0; }
		#content_image				{ margin:auto; width:auto; max-width:100%; max-height:100%; object-fit:contain; cursor:zoom-in; }
		#content_image:focus-visible	{ outline:none; }
		#content_pane[data-content="has_image"] #content_image_container		{ display:flex; }
		#content_pane.has_scaled_image #content_image_container					{ display:grid; }
		#content_pane.has_zoom_image #content_image								{ width:unset; max-width:unset; max-height:unset; cursor:zoom-out; }
		#content_video				{ background:transparent; }
		#content_iframe				{ background:white; border:0; }
	`;
	const main_styles = `
		/* WIDTHS & HEIGHTS */
		#content_font svg, .image_grid_item img[src$=".svg"]							{ width:100%; }
		/* DISPLAY */
		${ global_styles }
		.cue_sheet_track_list_container:hover .cue_sheet_track_list, #content_grid a	{ display:block; }
		/* TEXT STYLES */
		li.has_submenu:hover > span, .menu_item:hover, #directory_menus li.selected > span, #show_invisibles:hover span, .directory_item:hover a, #directory_wrapper .selected a, .media[class*="_loaded"] a, #stats_summary_detailed tr:hover, #stats_details tr:hover, .cue_sheet_track:not(.header):hover, .cue_sheet_track.selected
																						{ font-weight:bold; }
		button								{ padding:2px 6px; background-color:hsl(0,0%,95%); border:solid 1px #333; border-radius:3px; height:18px; font-size:0.875em; font-family:${$settings.UI_font} !important; cursor:pointer; }
		button.focus, button:focus 			{ outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
		textarea:focus, audio:focus 		{ outline:none; }

			/* VARIOUS CLASSES AND ELEMENTS */
		.ignored .has_icon_before::before, .ignored .has_icon_before_before									{ filter:grayscale(100%); }

		/* combine with has_flyout_menu or add background color classes to elements: */
		.has_popout_menu, .text_editor_theme_light #toolbar .has_popout_menu								{ background-color:#C0C0C0; border:solid 1px #666; }
		.has_popout_menu li																					{ background-color:#D0D0D0; }
		.has_popout_menu li:hover																			{ background-color:#E0E0E0; }
		.theme_dark #directory_wrapper .has_popout_menu														{ border:solid 1px #111; }
		.theme_dark #directory_wrapper .has_popout_menu, .theme_dark #directory_wrapper .has_popout_menu li	{ background-color:#505050; }
		.text_editor_theme_dark #toolbar .has_popout_menu li												{ background-color:#C0C0C0; }
		.theme_dark #directory_wrapper .has_popout_menu li:hover											{ background-color:#686868; }
			/***** APPEND STYLES *****/
		${ sidebar_styles }     ${ content_pane_styles }     ${ utilities_styles }
	`;
	const background_images = `
		/* BACKGROUND IMAGES */
		.has_background, .has_background_before::before, .has_background_after::after	{ background-repeat:no-repeat; background-position:center; background-color:transparent !important; }
		.bookmark > a::before															{ background-image:${ get_SVG_UI_Icon("bookmark") }; }

		.menu_item::before					{ content:""; width:24px; max-width:24px; min-width:24px; height:9px; margin:2px 0 -2px; background-position:center; background-repeat:no-repeat; display:flex; }
		.background_color_check_mark::before,     .sort_by_default #menu_sort_by_default .menu_item::before,     .sort_by_name #menu_sort_by_name .menu_item::before,     .sort_by_duration #menu_sort_by_duration .menu_item::before,     .sort_by_size #menu_sort_by_size .menu_item::before,     .sort_by_date #menu_sort_by_date .menu_item::before,     .sort_by_kind #menu_sort_by_kind .menu_item::before,     .sort_by_ext #menu_sort_by_ext .menu_item::before,     #menu_theme_container .menu_item::before,     .alternate_background #alternate_background .menu_item::before,     .show_image_thumbnails #show_image_thumbnails .menu_item::before,     .show_numbers #show_numbers .menu_item::before,     .autoload_media #autoload_media .menu_item::before,     .autoload_index_files #autoload_index_files .menu_item::before,     .play_all_media #play_all_media .menu_item::before,     #toggle_text_editing .menu_item::before,     .text_editor_split_view #text_editor_split_view_menu_item,     .text_editor_split_view #text_editor_split_view span::before,     .text_editor_raw #text_editor_raw::before,     .text_editor_preview #text_editor_preview::before,     .sort_by_default #sort_by_default span::before,     .loop_media #loop_media_menu::before,     .shuffle_media #shuffle_media_menu::before,     .sort_by_name #sort_by_name span::before,     .sort_by_duration #sort_by_duration span::before,     .sort_by_size #sort_by_size span::before,     .sort_by_date #sort_by_date span::before,     .sort_by_kind #sort_by_kind span::before,     .sort_by_ext #sort_by_ext span::before,     .alternate_background #alternate_background span::before,     .hide_ignored_items #hide_ignored_items span::before,     .ignore_ignored_items #ignore_ignored_items span::before,     .text_editor_theme_default #text_editor_theme_default::before,     .text_editor_theme_light #text_editor_theme_light::before,     .text_editor_theme_dark #text_editor_theme_dark::before, #enable_text_editing::before,     .text_editor_raw #text_editor_raw::before,     .text_editor_preview #text_editor_preview::before,     .text_editor_html #text_editor_html::before,     .cue_sheet_track.selected .cue_track_id::before
																						{ background-image:${ get_SVG_UI_Icon("check_mark") }; }
		.sort_by_default #sort_by_default span::after, .sort_by_name #sort_by_name span::after, .sort_by_duration #sort_by_duration span::after, .sort_by_size #sort_by_size span::after, .sort_by_date #sort_by_date span::after, .sort_by_kind #sort_by_kind span::after, .sort_by_ext #sort_by_ext span::after			{ background-image:${ get_SVG_UI_Icon("chevron") }; background-size:75%; transform:rotate(180deg); }
		.is_error #content_container													{ background-image:${ get_SVG_UI_Icon("error") }; }
		#content_pane[data-content="has_ignored"] #content_container					{ background-image:${ get_SVG_UI_File_Icon('file_icon_ignored') }; background-size:28px; }
		.has_audio #content_pane[data-content="has_null"]:not([data-loaded="unloaded"]) #content_container
																						{ background-image:${ get_SVG_UI_Icon("music") }; }
			${ CSS_UI_Icon_Rules() }
		#current_dir_path span::before													{ background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; display:inline-block; margin:0 0 -5px 0; width:24px; }
		.has_playlist #current_dir_path span::before									{ background-image:${ get_SVG_UI_File_Icon("file_icon_playlist") }; display:inline-flex; margin:0 0 -2px 0; width:24px; }
		.is_error #current_dir_path span::before										{ background-image:${ get_SVG_UI_Icon("error") }; float:none; display:inline-flex; margin:0 0 -2px 0; width:24px; }
		.dir_list_subdir_loading .has_icon_before_before								{ background-image:${ get_SVG_UI_Icon("spinner") } !important; background-size:20px; }
	`;
	const color_and_background_styles = `/* added to #top and #iframe_body */
		:root { color-scheme:none; }
		.theme_light, .text_editor_theme_light #content_text, #iframe_body.has_warning.text_editor_theme_light {
			--percent_100:100%; --percent_95:95%; --percent_90:90%; --percent_85:85%; --percent_80:80%; --percent_75:75%; --percent_70:70%; --percent_65:65%; --percent_60:60%; --percent_55:55%;
			--percent_50:50%;   --percent_45:45%; --percent_40:40%; --percent_35:35%; --percent_30:30%; --percent_25:25%; --percent_20:20%; --percent_15:15%; --percent_10:10%; --percent_05:05%; --percent_00:00%;
			--non_media_background_h:210deg;			--non_media_background_s:61%;			--non_media_background_l:80%;
			--media_background_h:180deg;				--media_background_s:36%;				--media_background_l:64%;
			--text_editor_item_background_h:240deg;		--text_editor_item_background_s:60%;	--text_editor_item_background_l:75%;
			--border_lum:40%;							--border_lum_inverted:40%;
		}
		.theme_dark, .text_editor_theme_dark #content_text, #iframe_body.has_warning.text_editor_theme_dark {
			--percent_100:00%; --percent_95:05%; --percent_90:10%; --percent_85:15%; --percent_80:20%; --percent_75:25%; --percent_70:30%; --percent_65:35%; --percent_60:40%; --percent_55:45%;
			--percent_50:50%;  --percent_45:55%; --percent_40:60%; --percent_35:65%; --percent_30:70%; --percent_25:75%; --percent_20:80%; --percent_15:85%; --percent_10:90%; --percent_05:95%; --percent_00:100%;
			--non_media_background_h:210deg;			--non_media_background_s:34%;			--non_media_background_l:55%;
			--media_background_h:180deg;				--media_background_s:31%;				--media_background_l:43%;
			--text_editor_item_background_h:240deg;		--text_editor_item_background_s:25%;	--text_editor_item_background_l:50%;
			--border_lum:05%;							--border_lum_inverted:40%;
		}
		${ background_images }
			/* BACKGROUND COLORS */
		.background_grey_60					{ background-color:hsl(0,0%,var(--percent_60)); }
		.background_grey_65					{ background-color:hsl(0,0%,var(--percent_65)); }
		.background_grey_70					{ background-color:hsl(0,0%,var(--percent_70)); }
		.background_grey_75					{ background-color:hsl(0,0%,var(--percent_75)); }
		body, .menu li:hover, .menu span:hover
											{ background-color:hsl(0,0%,var(--percent_75)); }
		.background_grey_80, .alternate_background .directory_item:nth-of-type(even),  .alternate_background .cue_sheet_track:not(.header):nth-of-type(even)
											{ background-color:hsl(0,0%,var(--percent_80)); }
		.background_grey_85, .alternate_background .directory_item:nth-of-type(odd),  .alternate_background .cue_sheet_track:not(.header):nth-of-type(odd), #utilities_help li:nth-of-type(even)
											{ background-color:hsl(0,0%,var(--percent_85)); }
		.background_grey_90					{ background-color:hsl(0,0%,var(--percent_90)); }
		.background_grey_95, .background_grey_90:hover, .background_grey_90.hovered, .background_grey_90.selected, .background_grey_90:focus, #content_grid::after
											{ background-color:hsl(0,0%,var(--percent_95)); }
		.background_grey_100, .background_grey_95:hover, .background_grey_95.hovered, .background_grey_95.selected
											{ background-color:hsl(0,0%,var(--percent_100)); }
	/* NON-MEDIA ITEMS .selected, .loaded, :hover ("light cyan") */
		body:not(.has_stats) .not_media:not(#is_error).selected, body:not(.has_stats) .directory_item.content_loaded:not(video), body:not(.has_stats) .directory_item.content_loaded:not(.video):hover, #menu li.selected
											{ background-color:hsla(var(--non_media_background_h),var(--non_media_background_s),var(--non_media_background_l),1.00) !important; }
		body:not(.has_stats) .directory_item.content_loaded:not(.video),	body:not(.has_stats) .not_media:not(#is_error):hover, body.alternate_background:not(.has_stats) .not_media:not(#is_error):hover, .not_media:not(#is_error).hovered			{ background-color:hsla(var(--non_media_background_h),var(--non_media_background_s),var(--non_media_background_l),0.80) !important; }
	/* MEDIA ITEMS .audio_loaded, .video.content_loaded, .selected, :hover */
		body:not(.has_stats) .directory_item.media[class*="_loaded"], .cue_sheet_track.selected
											{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),1) !important; }
		body:not(.has_stats) .directory_item.media.selected:not([class*="_loaded"]),	.directory_item.media.hovered, .cue_sheet_track:not(.header):hover
											{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),0.80) !important; }
		body:not(.has_stats) .directory_item.media:not([class*="_loaded"]):hover
											{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),0.70) !important; }
		body.has_text_editor #show_text_editor, body.edited #show_text_editor
											{ background-color:hsla(var(--text_editor_item_background_h),var(--text_editor_item_background_s),var(--text_editor_item_background_l),1.00); }
		#preview_text_menu_item:hover		{ background-color:initial; }
	/* TEXT COLORS */
		a:hover, .text_color_default.selected, .selected .text_color_default 						{ color:hsl(0,0%,var(--percent_05)); }
		a, .text_color_default, #help_container, #help_container a									{ color:hsl(0,0%,var(--percent_10)); }
		.text_color_333 																			{ color:hsl(0,0%,var(--percent_25)); }
		`;
	const conditional_styles = `
			/* PSEUDO-ELEMENTS */
		#reload_btn.reset::before, #content_pane.has_zoom_image #reload_btn::before, #content_pane.has_scaled_image #reload_btn::before			{ content:"Reset"; }
		.edited #show_text_editor a:after, .iframe_edited:not(.has_text_editor) #content_pane.has_iframe #content_title::after					{ content:" (edited)"; }
		.theme_light #menu_theme span::before														{ content:"Light "; }
		.theme_dark #menu_theme span::before														{ content:"Dark "; }
		.show_details #show::before																	{ content:"Hide "; }
		#disable::after																				{ content:"Enabled"; }
		.disable_text_editing #disable::after														{ content:"Disabled"; }
		#disable::after																				{ content:"Enabled"; }
		.is_error #directory_header_utilities														{ border-bottom:0; }
		.is_error #menu_sort_by, .is_error #alternate_background, .is_error #show_numbers, .is_error #ignored_files, .is_error #autoload_files, .is_error #media_files, .is_error #make_playlist, .is_error #playlist_options, .is_error #open_font_file, .is_error #toggle_text_editing, .is_error #text_editing					{ display:none; }
		#is_error																					{ display:block !important; grid:none !important; grid-template-columns:none !important; }
		#is_error_items																				{ padding:6px 8px; }
		.theme_dark #is_error_items																	{ filter:invert(1); }
			/* CONDITIONAL DISPLAY */
		.is_error #directory_header_utilities > div:not(:first-of-type), .is_error #directory_footer, .is_non_local #show_invisibles_container, body:not(.has_media) #time, #close_playlist_container
																				{ display:none; }
		.has_media #play_toggle, .theme_dark #theme_dark, .theme_light #theme_light, #content_pane[class^="has_"] #close_btn, #content_pane[data-content="has_text_editor"] #close_btn
																				{ display:unset; }
		.has_playlist #stats_summary_playlist_files, .has_filelist #stats_summary_playlist_files
																				{ display:table-row; }
		.has_menu #menu, .has_menu_parents #parents_links, #directory_menus .has_submenu:hover .submenu, #menu .has_submenu.hovered .submenu, #content_pane[data-content="has_font_file"] #font_file_viewer, #content_pane[data-content="has_glyph"] #font_file_viewer, #directory_footer li, .has_warning #overlay_container, .cue_sheet_track_list_container.has_cue_sheet, .has_playlist #close_playlist_container, .has_filelist #close_playlist_container														{ display:block; }
		.has_stats .directory_item.invisible, .has_stats .directory_item.ignored, .has_stats .directory_item.ignored.hovered, .hide_ignored_items.has_stats .directory_item.ignored, body:not(.hide_ignored_items) .directory_item.ignored:not(.invisible)											{ display:grid; }
		#content_pane[data-content="has_grid"] .split_btn, #content_pane[data-content="has_image"] .split_btn, #content_pane[data-content="has_font"] .split_btn, #content_pane[data-content="has_font_file"] #title_buttons_left .split_btn, #content_pane[data-content="has_glyph"] #title_buttons_left .split_btn, #content_pane[data-content="has_glyph"] #title_buttons_right .split_btn, body:not(.disable_text_editing) #content_pane[data-content="has_htm"] #open_in_text_editor,
		#content_pane .content.has_content, #content_pane[data-content="has_text_editor"] #content_text, #content_pane[data-content="has_font"] #font_specimen, #content_pane[data-content="has_glyph"] #glyph_viewer, .has_playlist #close_playlist_btn_container, .has_filelist #close_playlist_btn_container
																				{ display:flex; }
		#content_pane[data-content="has_text_editor"] .content.has_content, #content_pane[data-content="has_text_editor"] #content_grid, #content_pane[data-content="has_grid"] #content_text, #content_pane[data-content="has_grid"] .content.has_content						{ display:none !important; }
		.theme_dark #menu li > span::before, .theme_dark #sorting span::before, .theme_dark #sorting span::after
																				{ filter:invert(1); }
		#content_pane[data-content="has_ignored"]::before						{ opacity:0.3; }
		.has_warning #directory_wrapper, .has_warning #content_pane, #close_audio:hover::after, .has_menu_parents #directory_list, .has_menu #directory_list, .faded:not(.has_stats) .directory_item:not(.hovered), .has_stats:not(.faded) .directory_item:not(.hovered), #parent_dir_menu:not(:hover), #menu_container:not(:hover) nav, #show_grid, #footer_utilities, .split_btn span, .disabled:not(.local), .focus_content .directory_item:not(.hovered)													{ opacity:0.6; }
		.disabled:not(.local).selected											{ opacity:0.8; }
		#show_grid:hover, #prev_next_btns span:hover, #show_grid:hover, #footer_utilities:hover, .split_btn span:hover
																				{ opacity:1.0; }
	`;
	const text_editor_styles = `
		html, body, #iframe_body												{ margin:0; padding:0; height:100%; position:relative; font-family:${ $settings.UI_font }; font-size:${ $settings.UI_font_size }; }
		button.focus, button:focus 												{ outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
		#iframe_body															{ position:relative; overflow:hidden; }
		#iframe_body #content_text												{ display:flex; flex-direction:column; height:100%; position:absolute; z-index:1; width:100%; overflow:hidden;}
			/* TOOLBAR */
		#toolbar																{ width:100%; overflow-x:scroll; z-index:100; font-size:${ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') }; -webkit-user-select:none; -moz-user-select:none; user-select:none; }
		.toolbar_icon															{ margin:0 4px; padding:4px; min-width:16px; height:16px; cursor:pointer; display:flex; opacity:0.5; }
		.text_editor_theme_dark .toolbar_icon:not(#save_btn):not(#clear_text), .text_editor_theme_dark #text_editor_preview_pane			{ filter:invert(1); }
		#text_editor_sync_scroll												{ opacity:1; height:24px; padding:0 8px; flex-grow:unset; }
		#text_editor_sync_scroll input											{ margin:0 4px 0 0; z-index:-1; }
		#text_editor_sync_scroll label											{ white-space:pre; }
		#save_btn ul															{ padding:0 32px 0 0; top:-3px; right:-4px; box-shadow:0px 4px 6px -3px #333; }
		.edited #save_btn > div, .edited #save_btn ul div						{ color:red; }
		#toolbar li:hover, .text_editor_raw #toggle_text_editor_raw, .text_editor_split_view #toggle_text_editor_raw, .text_editor_split_view:not(.text_editor_html) #toggle_text_editor_preview, .text_editor_preview #toggle_text_editor_preview, .text_editor_html #toggle_text_editor_html, .text_editor_split_view #toggle_text_editor_split_view							{ opacity:1; }
			/* TEXT CONTENT CONTAINERS */
		#text_container															{ overflow:hidden; }
		#text_container .text_container_pane									{ padding:1em; width:100%; height:100%; overflow-y:scroll; box-sizing:border-box; border:0; z-index:1; background:transparent; line-height:1.2; font-size:${ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') }; }
		#text_container .text_container_pane:focus								{ background-color:hsl(0,0%,var(--percent_95)); outline:none; box-shadow:inset 0px 0px 4px hsl(0,0%,var(--percent_60)); }
		.text_editor_split_view #text_container .text_container_pane			{ width:50%; }
		#text_container textarea							 					{ resize:none; font-family:monospace; }
			/* SPLIT VIEWS */
		.text_editor_raw #text_editor_raw_pane, .text_editor_preview:not(.text_editor_raw) #text_editor_preview_pane, .text_editor_html:not(.text_editor_raw) #text_editor_html_pane,
		.text_editor_split_view #text_editor_raw_pane, .text_editor_preview.text_editor_split_view #text_editor_preview_pane, .text_editor_html.text_editor_split_view #text_editor_html_pane
																				{ display:block; }
			/* THEMES & COLORS */
		.text_editor_theme_default #content_text .background_grey_95, .background_grey_90:focus, #content_grid::after, #text_editor_preview_pane table th, #text_container
																				{ background-color:hsl(0,0,var(--percent_95)); }
		.text_editor_theme_light #content_text .text_color_default, .theme_light.text_editor_theme_default #content_text .text_color_default
																				{ color:hsl(0,0%,var(--percent_05)); }
		.text_editor_theme_dark #content_text .text_color_default				{ color:#EEE; }
			/* custom previewed text styles */
		#text_editor_preview_pane pre								{ border:solid 1px #CCC; border-radius:3px; white-space:pre-wrap; word-break:break-word; font-size:${ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') }; }
		#text_editor_preview_pane th, #text_editor_preview_pane td				{ vertical-align:top; }
		#text_editor_preview_pane blockquote									{ margin-top:1em; margin-bottom:1em; color:#555; }
		#text_editor_preview_pane blockquote + blockquote						{ margin-top:0; }
		.markdown_body input[type="checkbox"]									{ margin-top:0.375em; margin-right:6px; float:left; }
		h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink		{ display:inline-block; font-size:0.875em; transition:opacity 0.25s; opacity:0; cursor:pointer; margin:0; padding:0; }
		h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink
																				{ transition:opacity 0.25s; opacity:0.5; }
		#text_editor_preview_pane table											{ font-size:inherit; }
		.markdown_body table tr, .markdown_body .highlight pre, .markdown_body pre { background-color:transparent !important; }
		.markdown_body::before, .markdown_body::after							{ display:none !important; background:transparent; }
			/* disabled text editing */
		#save_btn div, .comment, .disable_text_editing #toolbar,.disable_text_editing #text_editor_preview, .disable_text_editing #text_editing_handle
																				{ display:none; }
		#iframe_body.has_warning::after 										{ content:""; position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,0.33); z-index:9998; }
			/* TEXT EDITOR RESIZE HANDLE */
		#text_editing_handle													{ display:none; width:8px; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:3; }
		#text_editing_handle::before											{ content:""; width:1px; background:#666; position:absolute; top:0; bottom:0; left:calc(50%); }
		.text_editor_theme_dark #text_editing_handle::before					{ background:#111; }
		.text_editor_split_view #text_editing_handle							{ display:block; }
	`;
	// Gecko (Firefos) Styles:
	const gecko_style_rules = `
		.dir::before															{ content:"" !important; display:none !important; }
		.is_gecko button														{ padding:revert; }
		.is_gecko #show_grid .menu												{ top:-7px; left:-120px; }
		.is_gecko thead															{ font-size:100%; }
		.is_gecko .directory_item.dir::before									{ position:absolute; }
		.is_gecko .directory_item_name span										{ display:-webkit-box; width:auto; white-space:normal; }
		.directory_item.dir td:not(:first-child), .directory_item.file td:not(:first-child)			{ width:unset !important; }
		.is_gecko .directory_item td											{ min-width:calc(100% - 24px); }
		.is_gecko .dir::before													{ content:"" !important; display:none !important; }
		.is_gecko.use_default_icons:not(.is_converted_list) .directory_item.file .icon	{ padding-left:4px; background:none; }
		.is_gecko.use_default_icons .directory_item.file .icon img				{ margin-right:6px; height:14px; }
		.is_gecko #directory_list > tr > td:not(:first-of-type)					{ float:left }
		.is_gecko #content_audio_title span										{ padding-top:6px;, padding-bottom:0; }
		.is_gecko #audio,.is_gecko #audio_container 							{ background-color:rgba(26,26,26,1); }
		.is_gecko #prev_track, .is_gecko #next_track, .is_gecko #close_audio	{ filter:invert(1); border:none !important; }
		.is_gecko #content_pane.has_zoom_image #content_image_container			{ display:block !important; }
	`;
	const safari_style_rules = `
		.is_safari button { background-color:#FFF; }
		.is_safari.theme_dark #prev_track, .is_safari.theme_dark #next_track, .is_safari.theme_dark #close_audio	{ filter:invert(1); }
	`;
	const chrome_style_rules = `
		video::-webkit-media-controls-enclosure 					{ border-radius:0 !important; }
	`;
	//==============================//
	function addStyles(user_agent) {																										// ===> ADD STYLES
		let styleEls = document.head.querySelectorAll('style, link[rel="stylesheet"], link[href$="css"]');
			styleEls.forEach( el => el.remove() ); 																									// remove any existing stylesheets
		let default_styles = `<style id="main_styles">${ main_styles }</style>     <style id="conditional_styles">${ conditional_styles }</style>     <style id="color_and_background_styles">${ color_and_background_styles }</style>     <style id="font_styles"></style>     <style id="font_grid_styles"></style>`;
		switch(user_agent) {
			case user_agent === 'is_gecko':		default_styles += `<style id="gecko_style_rules">${ gecko_style_rules }</style>`;		break;
			case user_agent === 'is_safari':	default_styles += `<style id="safari_style_rules">${ safari_style_rules }</style>`;	break;
			case user_agent === 'is_chrome':	default_styles += `<style id="chrome_style_rules">${ chrome_style_rules }</style>`;	break;
		}
		document.querySelector('head').insertAdjacentHTML('beforeend', default_styles);															// add assembled styles to head
	}
	// ***** END STYLES ***** //
	//==============================//
	// ***** INDEX PREP ***** //
	// Try to determine index type from parent directory link container, with fallbacks for indexes that don't have parent directories, or for parent directory links that aren't siblings or ancestors of the index itself.
	function getIndexType(agent) {																											// ===> GET INDEX TYPE
		// let title = document.querySelector('title').innerText || '';
		let index_el = document.querySelectorAll('body > ul, body > pre, body > table:last-of-type, body > div > table')[0];
		let node_name = ( index_el !== undefined ? index_el.nodeName.toLowerCase() : 'body' ); 														// "body" is likely to be an error page
		let types = {'gecko':'gecko','ul':'list','pre':'pre','table':'table','th':'table','td':'table','div':'default','error':'error','body':'error','permission_denied':'permission_denied'}; // object array of types
		switch(true) {
			// case ( /Error|404|Not Found/i.test(title) ): node_name = 'error'; break;																// this method too brittle; breaks if a file or dir name contains "error"
			case $protocol.startsWith('file'): if ( agent === 'is_gecko' ) { node_name = 'gecko'; } break;
		}
		return types[node_name];																													// return index type
	}
	function getIndexItems(agent) {																											// ===> GET INDEX ITEMS
		let type = getIndexType(agent), items;																										// get index type, define items
		switch(type) {
			// case 'error':	items = document.getElementsByTagName('body')[0].innerHTML;													break; 	// error type
			case 'pre':		items = document.querySelector('body > pre').innerHTML;															break; 	// pre type
			case 'list':	items = document.querySelectorAll('body > ul li');																break; 	// list type
			case 'gecko':	items = document.querySelectorAll('body > table > tbody > tr');													break;	// gecko type
			case 'table': case 'td':																												// table types
				switch(true) {
					case document.querySelector('table > tbody') !== null: 	items = document.querySelectorAll('body table > tbody tr'); 	break;	// ordinary tables
					case document.querySelector('table > tbody') ===  null: items = document.querySelectorAll('body table tr'); 			break;	// tables without tbody element
				}
				break;
			case 'default': items = document.querySelectorAll('body > table > tbody tr');													break;	// default: how is this different from table type?
		}
		return [items,type];																														// return index items and index type
	}
	//==============================//
	function convertPreType(items_str) {																									// ===> CONVERT PRE TYPE
		let prepped_index = [];
		const spaces = /\s{2,}/; // define regex for splitting rows; assumes pre-type only uses two or more spaces between "columns"
		// remove header elements | link text nodes | links with empty text nodes (which are sometimes duplicated) |  some parent & sorting links (href beginning with "?"; name, last modified, size, description)
		items_str = items_str.replace(/[ ]*<(hr|img)[^>]*>[ ]*|\&lt;dir\&gt;/gm,'  ') 																// remove various elements: img, hr, br
					 		 .replace(/<br>/gi,'\n')
					 		 .replace(/[ ]*<h\d>[^<]*<\/h\d>[ ]*|[ ]*(<a[^>]+?>)(Parent|Parent Directory|Up|Root)(<\/a>.+?$|[ ]*<a[^>]+?>\s*<\/a>[ ]*|[ ]*<a href="(\.*\/"|\?[^>]*?)>[^<]*<\/a>[ ]*)/gmi,'')
					 		 .replace(/[ ]*(<a[^>]+?>)[^<]*(<\/a>)/g,'$1$2  ') 																		// remove link text nodes
					 		 .replace(/(\w)<a /g,'$1  <a ')
		;
		const items = items_str.split('\n'); 																										// create array of item strings from items
		for ( let i = items.length; i--; ) {
			let prepped_item = [], link;
			let cells = items[i].split(spaces);
			for ( let j = cells.length; j--; ) {
				let cell = cells[j];
				if ( cell.trim().length > 0 ) {
					if ( !cell.startsWith('<a ') ) { prepped_item.push(cell); } else { link = cell.split('"')[1]; } 								// extract link
				}
			}
			if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else {  prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
			if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); } 																	// add prepped row to index
		}
		return prepped_index;																														// return prepped index
	}
	function convertListType(items) {																										// ===> CONVERT LIST TYPE
		let prepped_index = [];
		for ( let i = items.length; i--; ) {
			let item = items[i];
			if ( item.innerHTML.indexOf('Parent Directory') === -1 ) {
				let prepped_item = [];
				let link = item.querySelector('a').href;
					item = item.innerHTML.replace(/<a .+?<\/a>\s*/,''); 																			// remove <a> element up to first
				let cells = item.split(' ');
				for ( let cell of cells ) {
					if ( cell.trim().length > 0 ) { prepped_item.push(cell); }
				}
				if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else {  prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
				if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); }
			}
		}
		return prepped_index;																														// return prepped index
	}
	function convertGeckoType(items) {																										// ===> CONVERT GECKO TYPE
		let prepped_index = [];
		for ( let item of items ) {
			let prepped_item = [], cellContents = '', cells = item.cells, link = item.innerHTML.split('href=\"')[1].split('\">')[0];
			for ( let cell of cells ) {
				cellContents = cell.innerText;
				cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
				prepped_item.push(cellContents);
			}
			prepped_item[1] = prepped_item[1].replace(/\s*KB/,'000'); 																				// convert reported size in KB to total bytes
			prepped_item[2] = prepped_item[2] + ' '+ prepped_item[3];
			prepped_item = prepped_item.slice(1,-1);
			if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else {  prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
			if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); }
		}
		return prepped_index;																														// return prepped index
	}
	function convertTableType(type,items) { //*** for local chrome indexes and server-generated table-type indexes							// ===> CONVERT TABLE TYPE
		const testString = new RegExp(/<hr>|<th|href=\"\?|href=\"\/\"|href="\.\.\/"|alt=\"\[PARENTDIR\]|>\s*Parent Directory\s*<|>\s*\&nbsp;\s*<|^\s*-\s*$|\?sort=|\?path=\&|^<img|<dir>/,'mi');
		let prepped_index = [], items_length = items.length, prepped_item, cells, cells_length, i, j;
		for ( i = 0; i < items_length; i++ ) {
			prepped_item = []; cells = items[i].cells, cells_length = cells.length;
			if ( items[i].querySelector('a') !== null && ( !/^\?|^\.\.\/$|^\/$/m.test(items[i].querySelector('a').href ))) { prepped_item.push(items[i].querySelector('a').href); } // get link and add to prepped_item
			for ( j = 0; j < cells_length; j++ ) {
				if ( cells[j].childElementCount === 0 && !/^\s*\&nbsp;\s*$/m.test(cells[j].innerHTML) && !testString.test(cells[j].innerHTML) ) {	// exclude various cells
					prepped_item.push( cells[j].innerText.trim().replace(/(^[ ]*-[ ]*$|[ ]*-[ ]*\&nbsp;[ ]*$)/m,'—') ); 							// get content from other cells // replace empty with dash
				}
			}
			if ( prepped_item.length > 1 ) { prepped_index.push(prepped_item); } 																	// prepped_item.length > 2 in order to omit parent directory row
		}
		return prepped_index;																														// return prepped index
	}
	function convertErrorType(items) { return items; }																						// ===> CONVERT ERROR TYPE; receives and returns html string
	function convertPlaylist(items) {																										// ===> CONVERT PLAYLIST items
		let prepped_index = []; let prepped_item; let items_arr, type;
			items = items.replace(/\s*#EXTM3U.*\s*/g,'').replace(/^\*\n{2,}/gm,'\n').replace(/\.pdf\?.+?\n/g,'.pdf\n').replace(/\?/g,'%3F'); 		// remove header comment and multiple returns
		switch(true) { 																																// determine playlist type;
			case ( /#EXTINF:/i.test(items) ):	type = 'extm3u';	items_arr = items.split('#EXTINF:');											break;	// rows made by splitting at "#EXTIMG:" prefix
			default:							type = 'm3u'; 		items_arr = items.split('\n');													break;	// rows are just naked links
		}
		items_arr.forEach(function(item) {
			switch(true) { // get entry information: title, link, etc.
				case type === 'extm3u':		item = item.trim().split('\n'); if ( item[1] !== undefined ) { prepped_item = [item[1],'','']; }		break;	// split into info (row[0]) & link, but we only use the link anyway
				case type === 'm3u':		prepped_item = [item,'',''];																			break;	// m3u with urls only
			}
			if ( prepped_item !== undefined ) { prepped_index.push(prepped_item); }
		});
		return prepped_index;																														// return prepped index
	}
	function convertIndexItems(type,items) {																								// ===> CONVERT INDEX ITEMS by type; returns [prepped_index]
		let converted = [];
		switch(type) {
			case 'gecko':	converted = convertGeckoType(items);		break;
			case 'list':	converted = convertListType(items);			break;
			case 'pre':		converted = convertPreType(items);			break;
			case 'table':
			case 'default': converted = convertTableType(type,items);	break;
			case 'error':	converted = convertErrorType(items);		break;
		}
		return converted;
	}
	//==============================//
	function buildNewIndex(id,prepped_index,sort,type,body_id) {																				// ===> BUILD NEW INDEX from prepped rows
		let new_index_items = [], body_classes = new Set();
		let index_html = '';
		let new_item, item, item_info = [], item_link, item_name, item_sort_name, item_size_and_date, item_size, item_sort_size, item_date, item_sort_date, item_ext, item_sort_kind, item_kind, item_classes;
		let name_span, cell_link, cell_name, cell_size, cell_date, cell_kind, cell_ext, cell_time, prepped_index_length = prepped_index.length, item_disabled, item_input;
		let stats, stats_classes = [], stats_kinds = [];
		let dir_list_parent_class = ( ( body_id === (null || 'top') || type === 'playlist' ) ? 'top_item' : 'iframe_item' );						// body_id used to set dir list details style
		let parent_id = ( getSearchParam('parent_id') || '' ), connector = ( getSearchParam('parent_id') ? '_' : '' ), level = ( Number(getSearchParam('level')) || 0 ), level_style = ( level === 0 ? '' : `style="padding-left:${ Number(level) * 22 }px;"` ); 																								// ensure unique ids and set indents for subdirectory items
		switch(type) {																																// add body classes according to index type
			case 'error':				body_classes.add('is_error');			break;
			case 'pre':					body_classes.add('is_converted_pre');	break;
			case 'list':				body_classes.add('is_converted_list');	break;
			case 'gecko':				body_classes.add('is_converted_gecko'); break;
			case 'table': case 'td':	body_classes.add('is_converted_table');	break;
			case 'default': 			body_classes.add('is_default');			break;
		}
		// create and format directory row
		for ( let i = prepped_index_length; i--; ) {
			item 				= prepped_index[i];
			item_info			= getLinkInfo(item[0]); 																							// = [link,name,ext,kind,item_classes,body_classes];
			item_link			= item_info[0];
			item_name			= item_info[1]; 																									// prep display name, with word breaks added after unbreakable chars
			item_sort_name		= escapeStr( item_info[1].toLocaleLowerCase() );
			item_size_and_date 	= getItemSizeAndDate(item);
			item_size			= item_size_and_date[0];
			item_sort_size		= item_size_and_date[1];
			item_date			= item_size_and_date[2];
			item_sort_date		= item_size_and_date[3];
			item_ext			= item_info[2];
			item_kind			= item_info[3];
			item_sort_kind		= item_info[3];
			item_classes		= item_info[4] +" "+ dir_list_parent_class;
			item_disabled		= ( item_classes.indexOf('local') > -1 || type === 'gecko' ? ' disabled="disabled"' : '' );
			item_input			= ( /audio|video/.test(item_kind) ? '<input class="directory_item_input" type="checkbox" tabindex="-1" checked="true" ${ item_disabled } autocomplete="off" />' : '' );
			// Assemble row elements
			name_span			= `<span class="has_icon_before_before"></span><span class="name_span display_flex">${ item_input }${ item_name }</span>`;
			cell_link			= `<a href="${ item_link }" class="icon directory_item_name name directory_item_name_a text_color_default position_relative">${ name_span }</a>`;
			cell_name			= `${ cell_link }`;
			cell_time		 	= `<span class="directory_item_media_duration align_right" data-duration=""></span>`;
			cell_size			= `<span class="directory_item_details size details" data-size="${ item_sort_size }">${ item_size }</span>`;
			cell_date			= `<span class="directory_item_details date details" data-date="${ item_sort_date }">${ item_date }</span>`;
			cell_kind			= `<span class="directory_item_details kind details" data-kind="${ item_sort_kind }">${ item_kind }</span>`;
			cell_ext			= `<span class="ext details" data-ext="${ item_ext }"></span>`;
			// Assemble rows
			new_item			= `<li id="${ parent_id }${ connector }rowid-${ (prepped_index.length - i) }" class="directory_item ${ item_classes }" data-name="${ item_sort_name.split("/")[0] }" data-kind="${ item_sort_kind }" data-ext="${ item_ext }" data-level="${ level }" ${ level_style }>${ cell_name } ${ cell_time } ${ cell_size } ${ cell_date } ${ cell_kind } ${ cell_ext }</li>\n`;
			new_index_items.push(new_item);																											// add item to index items
			body_classes.add(item_info[5].join(' '));																								// add item classes to body_classes
			if ( /audio|video/.test(item_sort_kind) ) {																								// if media item...
				getMediaDuration( item_link, item_sort_kind, function( duration ) { 																// get media duration
					let item_id = parent_id + connector +'rowid-'+ ( prepped_index.length - i);
 					if ( window.location.search.indexOf('subdirectory') > -1 ) {
 						sendMessage('top','set_media_duration','', [ item_id, item_sort_kind, duration ] );											// if subdirectory, send message from utility iframe to top
 					} else {
						setMediaDuration( item_id, item_sort_kind, duration ); 																		// else set media duration
					}
					if ( duration === 0 ) { addClass('#'+ item_id,'disabled'); }																	// if duration === 0, disable media item
				});
			}
			stats_kinds.push(item_kind);																											// add item kind to stats_kinds
			stats_classes.push(item_info[6]); 																										// add item classes for stats
		}
		stats = buildStats(stats_classes,stats_kinds);																								// build stats
		// Sort items
		if ( sort === '' || sort === undefined ) { sort = getSearchParam('sort_by'); } 																// get sort_by pref
		let sort_direction = getSearchParam('sort_direction'); 																						// get sort_direction pref
		let sorted_index_items = sortDirList(new_index_items, 'sort_by_'+ sort, sort_direction); 													// make initial sort
		return [sorted_index_items, Array.from(body_classes).join(' '),stats,index_html]; 															// return [sorted_index_items, body_classes, stats, index_html]
	}
	//==============================//
	function getLinkInfo(link) {																											// ===> GET LINK INFO; returns [link,name,ext,kind,item_classes,body_classes]
		switch(true) {
			case link === undefined: return; 																										// return if link undefined
			case link === null: 													link = document.getElementById('content_iframe').src; break;	// link from opening local link files links in iframe
			case link.startsWith('file://') && window.location.protocol === 'file:':link = link.split('file://')[1];	break;						// local links
			case link.startsWith('/') && window.location.protocol === 'file:': 		link = 'file://'+ link; 			break;						// local links
			case !link.startsWith('/') && !link.endsWith('/') && !/\./.test(link): 	link = '/'+ link +''; 				break;
		}
		if ( /\.php\?(path)=/.test(link) ) 		{ link = link.split('=')[1]; }																		// attempt to deal with some php links
		if ( /%3C/.test(link) ) 				{ link = link.replace(/%3C/g,'\&lt;'); } 															// replace "<" with html entity
		let URL = newURL(decodeURIComponentSafe(encodeURIComponent(link)));
		let prepped_link, display_name, kind, ext, item_classes = [], body_classes = [], stats_classes = [], aliases = new RegExp(/(symlink|alias|symbolic link)$/,'m');
		switch(true) {																																// prep link
			case $protocol !== 'file:': 																											// for non-local pages
				switch(true) {
					case URL.protocol === 'file:': case URL.protocol === undefined: prepped_link = link; item_classes.push('local','ignored'); break; // local links from non-local pages
					default: prepped_link = URL.href; 																								// non-local pages
				}																															break;
			default: 																																// for local pages;
				switch(true) {
					case URL.protocol !== 'file:': prepped_link = URL.href; 																break;
					default: prepped_link = URL.pathname;
				}
		}
		switch(true) {																																// prepare display name, body_classes, and item_classes
			case URL.pathname.endsWith('/'): 																										// dirs and apps
				display_name = URL.pathname.split('/').reverse()[1] + '/';
				switch(true) {
					case /\.app$|\.app\/$|\.exe$/m.test(display_name):																				// apps
						ext = 'app'; kind = ext;
						if ( $settings.apps_as_dirs === false ) { item_classes.sort().unshift('file','app'); } else { item_classes.sort().unshift('dir','app'); }
						break;
					default: kind = 'dir'; ext = 'dir';																				 				// dirs
						item_classes.unshift(kind);																									// remove kind from item_classes
				}
				item_classes.push('not_media');																								// add "not_media" to item_classes
				if ( display_name.startsWith('.') ) { item_classes.push('invisible'); stats_classes.push('invisible'); }					break;
			default: 																																// files
				display_name = prepped_link.trim().split('/?')[0].split('/').reverse()[0];
				switch(true) {
					case display_name.toLowerCase().endsWith('symlink'):	ext = 'symlink';												break;
					case !/\./.test(display_name):							ext = display_name.toLowerCase();								break;	// if no '.' in link (typical for bin files), ...
					default: 																														// find the last . and get the remaining characters
						ext = display_name.toLowerCase().slice(display_name.toLowerCase().lastIndexOf('.') + 1);
						for ( let item_kind in $item_kind ) { if ( $item_kind[item_kind].includes( ext ) ) { kind = item_kind; } } 					// kind = types
						if ( /url|url\/|webloc|webloc\//.test(ext) ) { kind = 'link'; } 															// links
						switch(true) {
							case kind === 'audio': item_classes.push('media'); body_classes.push('has_media','has_audio');					break;
							case kind === 'video': item_classes.push('media'); body_classes.push('has_media','has_video');					break;
							case kind === 'font' : body_classes.push('has_fonts'); 															break;
							case kind === 'image': body_classes.push('has_images'); 														break;
						}
						if ( $row_settings.ignored.includes( ext ) )	{ item_classes.push('ignored'); stats_classes.push('ignored'); }
						if ( display_name.startsWith('.') ) 			{ item_classes.push('invisible'); stats_classes.push('invisible'); }
				}
				if ( kind === undefined ) { kind = 'other'; }
				if ( !/audio|video/.test(kind) ) { item_classes.push('not_media'); }
				item_classes.unshift(kind); item_classes.unshift('file');
		}
		stats_classes.push(kind);
		if ( ext === undefined ) { ext = ''; }
		if ( aliases.test(display_name) ) { item_classes.push('alias'); }
		for ( let item_kind_system of $item_kind.system ) { if ( display_name.endsWith(item_kind_system) ) { item_classes.push('ignored'); } } 		// ignore various system items
		item_classes = item_classes.sort();
		item_classes = Array.from(new Set(item_classes)).join(' '); 																				// remove dupe classes
		return [decodeURIComponentSafe(encodeURIComponent(prepped_link)).trim(),decodeURIComponentSafe(display_name).trim(),ext,kind,item_classes,body_classes,stats_classes.join(' ')];
	}
	//==============================//
	function getItemSizeAndDate(cells) {																									// ===> GET ITEM SIZE AND DATE
		let item_size_and_date = [], row_display_size, item_sort_size, item_display_date, item_sort_date, size_units = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
		if ( cells.length > 1 ) {																													// test for typical date/time separators.
			if ( /[-:\/]/.test(cells[1]) ) { item_display_date = cells[1]; row_display_size = cells[2]; } else { item_display_date = cells[2]; row_display_size = cells[1]; }
		}
		switch(true) {																																// get size
			case row_display_size !== undefined && row_display_size.toLowerCase() === 'dir':
			case ( /undefined|—|-|,|\*/.test(row_display_size) ):
			case row_display_size === '': 																											// if size is undefined, empty, or punctuation
				row_display_size = '&mdash;'; item_sort_size = '0'; 																				// if no size supplied, use these defaults
				break;
			default:
				item_sort_size = getItemSortSize(row_display_size);
				switch(true) {
					case !row_display_size.toUpperCase().match(size_units) : 																		// if provided size is only numeric
						row_display_size = formatBytes(row_display_size,1); 																		// format byte size
						break;
					default:
						row_display_size = row_display_size.replace('K','k').replace(/(\d+)\s*([A-z])/,'$1 $2'); 									// ensure display size has space between number and units
					}
				break;
		}
		if ( row_display_size === 'NaN undefined' ) { row_display_size = '0 B'; }
																																					// get date
		if ( [undefined,'','-'].includes(item_display_date) ) { item_display_date = '&mdash;'; item_sort_date = '0'; } else { item_sort_date = getItemDate(item_display_date); }
		item_display_date = item_display_date.replace(/, (.+)/,'<wbr>,&nbsp$1').replace(/ (AM|PM)$/im,'<wbr> $1').replace(/\s/g,'&nbsp;'); 			// ensure that time acts as a block for wrapping in narrow sidebar
		item_size_and_date.push( row_display_size, item_sort_size, item_display_date, item_sort_date );
		return item_size_and_date;
	}
	function getItemSortSize(val) {																											// GET ITEM SORT SIZE
		let sort_size, values = val.replace(/(\d+)\s*([A-z]+)/,'$1 $2').split(' '), size = values[0], unit = values[1];
		const factor = { undefined:1, '':1, B:1, K:1000, KB:1000, M:1000000, MB:1000000, G:1000000000, GB:1000000000, T:1000000000000, TB:1000000000000, P:1000000000000000, PB:1000000000000000, E:1000000000000000000, EB:1000000000000000000, Z:1000000000000000000000, ZB:1000000000000000000000 }; // unit to file size
		if ( unit !== undefined ) { unit = unit.toUpperCase(); }
		sort_size = size * factor[unit]; // convert byte size to multiplication factor
		return sort_size;
	}
	function formatBytes(val, decimals) {																									// ===> FORMAT BYTES: format numeric sizes for display
		const k = 1024, dm = (decimals < 0 ? 0 : decimals), sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], i = Math.floor(Math.log(val) / Math.log(k));
		if (val === 0) { return '0 Bytes'; } else { return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i]; }
	}
	function processDate(match,p1,p2,p3) { //***date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11 ***//		// ===> PROCESS DATE
		const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; 																		// e.g., convert month into number, or use number
		return p3 +'-'+ mo +'-'+ p1;																												// return assembled date: YYYY-MM-DD
	}
	function getItemDate(val) {																												// ===> GET ITEM DATE: for sorting (YYYY-MM-DD)
		let sort_date = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) 	// convert Month to number
						   .replace(/\b(\d{1})[-:/]/g,'0$1/') 					// add leading 0 for single digit numbers
						   .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') 		// reorder MM/DD/YY dates to YY/MM/DD
						   .replace(/-|:|\s+|\//g,''); 							// remove spacing characters
		return sort_date;
	}
	//==============================//
	var getFormattedDuration = (secs) => {																									// ===> GET FORMATTED TIME
		let sec_num = parseInt(secs, 10), hours = Math.floor(sec_num / 3600), minutes = Math.floor(sec_num / 60) % 60, seconds = sec_num % 60;
		let formattedTime = [hours,minutes,seconds].map( v => v < 10 ? "0" + v : v ).filter( (v,i) => v !== "00" || i > 0 ).join(":");
			formattedTime = formattedTime.replace(/^0/m,''); 																						// remove initial 0
		return formattedTime;
	};
	function getMediaDuration(link, kind, mediaInfoCallback) {																				// ===> GET MEDIA DURATION
		if ( !/audio|video/.test(kind) || link === undefined ) { return; } 																			// abort if not a media file
		let media = document.createElement(kind); 																									// create audio or video element
			media.src = link; 																														// set the media element source
			media.onloadedmetadata = function() { mediaInfoCallback( media.duration ); media = null; }												// return media_info = [duration, kind]
			media.onerror = function() { mediaInfoCallback( 0 ); }; 																				// media.onerror = function() { callback( new Error("File not found")); };
	}
	function setMediaDuration(id,kind,duration) { 																							// ===>  SET MEDIA DURATION
		setAttribute('#'+ id +' .directory_item_media_duration','data-duration',duration);
		if ( document.getElementById(id) !== null ) { document.querySelector('#'+ id +' .directory_item_media_duration').innerText = getFormattedDuration(duration); } // add time to dir_list row details
		let totalDurationEl = document.getElementById('total_duration'), total_duration;
		if ( totalDurationEl !== null ) {
			total_duration = Number( totalDurationEl.getAttribute('data-total_duration') );
			total_duration += Number(duration);
			setAttribute('#total_duration','data-total_duration',total_duration);
			document.getElementById('total_duration').innerText = getFormattedDuration(total_duration);
		}
		addClass('body','has_media'); // why is this needed here? subdirs?
	}
	function updateDurations(bool) { // bool === true: don't bother updating stats details, since they aren't visible						// ===> UPDATE DURATIONS
		let media_items = document.querySelectorAll('.directory_item.media');
		if (media_items.length === 0 ) { removeClass('body','has_media has_audio has_video'); return; }
		let kind, total_duration = 0, duration = 0, audio_duration = 0, video_duration = 0;
		for ( let i = 0; i < media_items.length; i++ ) { 																							// get classes and kind for each item
			kind = media_items[i].getAttribute('data-kind'); 																						// = [link,name,ext,kind,item_classes,body_classes];
			duration = Number(media_items[i].getElementsByClassName('directory_item_media_duration')[0].getAttribute('data-duration'));
			setMediaDuration(media_items[i].getAttribute('id'),kind,duration );
			total_duration += duration;
			setAttribute('#total_duration','data-total_duration',total_duration);
			document.getElementById('total_duration').innerText = getFormattedDuration(total_duration);
			addClass('body','has_media');
			switch(true) { 																															// update stats details
				case bool === true: break;
				case kind === 'audio': addClass('body','has_audio');
					audio_duration += Number(duration);
					document.getElementById('stats_details').querySelectorAll('span.audio')[0].setAttribute('data-audio_duration',' (Total Time: '+ getFormattedDuration(audio_duration) +')');
					break;
				case kind === 'video': addClass('body','has_video');
					video_duration += Number(duration);
					document.getElementById('stats_details').querySelectorAll('span.video')[0].setAttribute('data-video_duration',' (Total Time: '+ getFormattedDuration(video_duration) +')');
					break;
			}
		}
	}
	//==============================//
	function buildStats(stats_classes,stats_kinds) { 																						//*** BUILD STATS
		stats_classes.sort();
		let total_items = stats_classes.length, counts = {}, kinds = [], stats_rows = [], total_dirs = 0, total_files = 0, total_dirs_invisible = 0, total_files_invisible = 0, total_invisibles = '';
		for ( let i = 0; i < total_items; i++ ) {
			stats_classes[i] = stats_classes[i].split(' ').reverse().join(' ')																	// reorder classes to make invisible/ignored last
			counts[stats_classes[i]] = 1 + ( counts[stats_classes[i]] || 0 );																	// get key/value pairs for item_classes/total counts
			switch(true) {
				case ( !/invisible|ignored/.test(stats_classes[i]) ): 																	break;	// don't count :not(.invisible) and :not(.ignored)
				case (getSearchParam('show_invisibles') === 'true' ) && ( getSearchParam('hide_ignored_items') === 'true' ):					// show_invisibles && hide_ignored
					if ( /invisible/.test(stats_classes[i]) && /ignored/.test(stats_classes[i]) ) { break; }									// don't count .invisible.ignored
					if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; }					break;	// else count .ignored
				case (getSearchParam('show_invisibles') === 'false' ) && ( getSearchParam('hide_ignored_items') === 'true' ):					// hide_invisibles && hide_ignored (hide all)
					if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; }					break;	// count .invisible and .ignored (count all)
				case (getSearchParam('show_invisibles') === 'true' ) && ( getSearchParam('hide_ignored_items') === 'false' ):					// show_invisibles && show_ignored (show all)
					if ( /invisible/.test(stats_classes[i]) || /ignored/.test(stats_classes[i]) ) { break; }							break;	// don't count .invisible or .ignored (count none)
				case (getSearchParam('show_invisibles') === 'false' ) && ( getSearchParam('hide_ignored_items') === 'false' ):					// hide_invisibles && show_ignored
					if ( !/invisible/.test(stats_classes[i]) && /ignored/.test(stats_classes[i]) ) { break; }									// don't count .ignored:not(.invisible)
					if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; }					break;	// else count .invisible and .invisible.ignored
			}
		}
		for ( let i = 0; i < stats_kinds.length; i++ ) { kinds[stats_kinds[i]] = 1 + ( kinds[stats_kinds[i]] || 0 ); }							// get key/value pairs for item kinds/counts
		total_dirs = ( kinds.dir || 0 );		total_files = ( total_items - total_dirs );														// total dirs && files count
		if ( getSearchParam('show_invisibles') === 'false' || getSearchParam('hide_ignored_items') === 'true' ) {
			total_invisibles = ' (+'+ (total_dirs_invisible + total_files_invisible) +')';
			total_items = total_items - (total_dirs_invisible + total_files_invisible);
			total_dirs = total_dirs - total_dirs_invisible;
			total_files = total_files - total_files_invisible;
		}
		for ( let count in counts ) { 																											// make detail row for each kind of item --> doesn't preserve order
			let kinds_items = count.split(' '), stats_row_kinds = '';
				kinds_items.forEach( item => ( stats_row_kinds += (`<span class="${ item }" >${ item }</span>`)) );						 		// create kind span
			let stats_row = `<li class="stats_list_item ${ count } hover_bold" data-kind="${ kinds_items }"><span class="stats_list_item_name name stats_count">
				<a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ counts[count] }"><span class="stats_list_item_name_a_span has_icon_before stats_kind">${ stats_row_kinds }</span></a>
			</span></li>`;
				stats_rows.push(stats_row);
		}
		stats_rows.sort();
		let stats = `<nav id="stats_container" class="display_flex"><div id="stats" class="normal pointer">
						<ol id="stats_summary" class="background_grey_80 text_color_default margin_0 padding_0">     <li class="stats_list_item padding_0">     <span id="stats_summary_totals">${ total_items } Items${ total_invisibles }: ${ total_dirs } Directories, ${ total_files } Files</span>     <span id="total_duration" class="display_none" data-total_duration=""></span>     </li>     </ol>
						<ol id="stats_summary_detailed" class="border_bottom position_relative background_grey_80 text_color_default margin_0 padding_0 display_none">     <li id="stats_summary_detailed_total" class="summary_detailed border_bottom hover_bold">     <span>${ total_items } Items (${ total_dirs_invisible + total_files_invisible } invisible or ignored)</span>     </li>     <li id="stats_summary_detailed_dirs" class="stats_list_item dir summary_detailed background_grey_85 hover_bold padding_0">     <span class="stats_list_item_name name stats_count">     <a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ total_dirs }">     <span class="stats_list_item_name_a_span has_icon_before stats_kind">Dirs (${ total_dirs_invisible } invisible or ignored)</span>     </a>     </span>     </li>     <li id="stats_summary_detailed_files" class="stats_list_item file summary_detailed background_grey_85 hover_bold padding_0">     <span class="stats_list_item_name name stats_count">     <a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ total_files }">     <span class="stats_list_item_name_a_span has_icon_before stats_kind">Files (${ total_files_invisible } invisible or ignored)</span>     </a>     </span>     </li>     </ol>
						<div id="stats_details_container">     <ol id="stats_details" class="margin_0 padding_0 position_relative display_none">     ${ stats_rows.join('\n') }      </ol>     </div>
					</div></nav>`;
	return stats;
	}
	function updateStats() { 																												// ===> UPDATE STATS
		let items = document.querySelectorAll('.directory_item');																					// get all dir_list items
		let stats_classes = [], stats_kinds = [], item_classlist = [];																				// define various arrays
		let item_info;
		for ( let i = 0; i < items.length; i++ ) { 																									// get classes and kind for each item
			item_info = getLinkInfo( items[i].getElementsByClassName('directory_item_name_a')[0].href ); 											// get item info = [link,name,ext,kind,item_classes,body_classes];
			item_classlist = item_info[4];																											// get item_classlist
			item_classlist = item_classlist.replace(/file|media|audio_loaded|content_loaded|has_subdirectory|selected|not_/g,'').trim();			// remove unwanted classes --> why file and media?
			stats_classes.push(item_classlist);																										// add item_classlist to stats_classes
			stats_kinds.push( item_info[3] );																										// add item_kind to stats_kinds
		}
		document.getElementById('stats_container').remove(); 																						// remove old stats
		document.getElementById('directory_footer').insertAdjacentHTML( 'afterbegin',buildStats(stats_classes,stats_kinds) ); 						// build new stats and add to directory_footer
		updateDurations(true); 																														// update after building stats
		initStatsEventListeners();																													// initial event listeners for new stats items
	}
	// ***** END DIR_LIST SETUP ***** //
	//============================//
	// ***** UI SETUP ***** //
	function prepDocHead(agent) {																											// ===> PREP DOC HEAD
		document.title = 'Index of: '+ current_location; 																							// change the doc title to current location
		document.querySelectorAll('head script, head style, head link').forEach( headEl => headEl.remove() ); 										// remove any existing scripts
		document.querySelector('head').insertAdjacentHTML('afterbegin','<meta charset="utf-8"><base href="'+ window.location.origin  +'">');
		document.querySelector('head title').removeAttribute('id');
		if ( window.location.protocol.startsWith('file') ) {  																						// add custom favicon for local directories
			document.querySelector('head').insertAdjacentHTML('afterbegin','<link href="data:image/png;base64,' + get_SVG_UI_File_Icon('favicon') +'" rel="icon" sizes="16x16" />');
		}
		addStyles(agent); 																															// add styles
	}
	function getUIPrefBodyClasses(agent) {																									// ===> GET UI PREF BODY CLASSES and other initial settings
		let queries = new URLSearchParams(window.location.search).entries(); 																		// make new search params from window.location.search
			queries = Object.fromEntries(queries);
		let body_classes = [], settings = Object.assign({},queries,$settings); 																		// merge $settings and query settings
		for ( let key in settings ) {
			switch(true) {
				case ['bookmarks','grid_font_size','grid_image_size','UI_font','UI_font_size'].includes(key):									break;  // ignore these keys (values set in css or by setUpTextEditorUI)
				case key === 'enable_text_editing':								if ( getSearchParam(key) === 'true') { body_classes.push(key); } else { body_classes.push('disable_text_editing'); }	break;
				case key === 'text_editor_theme':
					switch(true) {
						case getSearchParam('text_editor_theme') === 'default':		body_classes.push( 'text_editor_theme_default' );	 		break;
						// case getSearchParam('text_editor_theme') === 'default':	body_classes.push( 'text_editor_theme_'+ getSearchParam('theme');
						case getSearchParam('text_editor_theme') === 'light':		body_classes.push( 'text_editor_theme_light' ); 			break;
						case getSearchParam('text_editor_theme') === 'dark':		body_classes.push( 'text_editor_theme_dark' ); 				break;
					}
					break;
				case ['text_editor_default_view'].includes(key):					body_classes.push('has_'+ getSearchParam(key) );			break;	// some non-booleans: class = search_param value
				case ['sort_direction'].includes(key):								body_classes.push( getSearchParam(key) );					break;	// some non-booleans: class = search_param value
				case ['theme','sort_by'].includes(key):								body_classes.push( key +'_'+ getSearchParam(key) );			break;	// other non-booleans: class = key + value
				case getSearchParam(key) === 'true':								body_classes.push(key);										break;	// booleans: only add the key to body classes
				}
		}
		body_classes.push(agent); body_classes.push('is_'+getOS()); 																				// add browser and os classes
		return body_classes.join(' ');
	}
	function makeNewIndex(el,sort,agent,body_id) {																									// ===> MAKE NEW INDEX
		const index_items = getIndexItems(agent), items = index_items[0], type = index_items[1];
		const converted_index = convertIndexItems( type, items ); 																					// = array of rows: ["link","date","size"]
		switch(type) {
			case 'error':	return [[['<tr id="is_error"><td id="is_error_items">'+ items +'</td></tr>'],'is_error'],''];
			default: 		{ let new_index = buildNewIndex( el.id, converted_index, sort, type, body_id ); return [new_index]; }
		}
	}
	function buildUI() {																													// ===> BUILD UI: Append all assembled elements to $body
		const agent = getBrowser();
		switch(true) {
			case isTopWindow(): { 																										// if it's not an iframe...
				const make_new_index = makeNewIndex('body','',agent,'top');												// make index
				let body_classes = make_new_index[0][1] +' '+ getUIPrefBodyClasses(agent); 															// delete extra spaces, create array of body class names
				let main_content = `${ Directory_Elements('top') }     ${ Content_Pane_Elements }     ${ Utilities_Elements('top') }`;
				main_content = main_content.replace(/insert_prepped_index/,make_new_index[0][0]).replace(/insert_stats/,make_new_index[0][2]);		// add dir_index and stats to MainContent
				document.body.innerHTML = '';																						// remove body contents
				prepDocHead(agent); 																												// add title, favicon, meta tags, styles to head
				setAttribute('body','id','top'); 																									// add body id
				setAttribute('body','lang','en');																				 					// add body lang attr
				if ( document.body.getAttribute('class') !== null ) { document.body.removeAttribute('class'); }	// remove body classes, if any
				addClass('body',body_classes); 																										// add body classes
				document.body.innerHTML = main_content; 																			// add main content to body
				if ( make_new_index[0][1] !== 'is_error' ) { initEventListeners(); autoLoadFile(); } else { initBaseEventListeners(); }				// initialize event listeners & autoloadfiles if not error page
				break;
				}
			case !isTopWindow() && !window.location.pathname.endsWith('.pdf'): 	setUpIframeUI(agent);	break;							// if iframe and not pdf, setup iframe UI
		}
		if ( getSearchParam('show_image_thumbnails') === 'true' ) { showImageThumbnails(); }														// load image thumbnails after building ui
	}
	buildUI();																																// CALL BUILDUI
	//============================//
	function defaultSettings(e) {																											// ===> DEFAULT SETTINGS: remove queries;
		if (window.confirm( 'Are you sure you want to remove all your temporary UI settings from the URL query string?' ) ) {
			e.preventDefault(); e.stopPropagation();
			removeClass('body','has_menu');
			let query_str = '';
			if ( getSearchParam('selected') !== undefined ) { query_str += 'selected='+ getSearchParam('selected'); }
			if ( getSearchParam('history') !== undefined ) { query_str += 'history='+ getSearchParam('history'); }
			if ( query_str.length > 0 ) { query_str = '?' + query_str.replace(/\s/g,'+'); }
			window.location.assign(current_location + query_str);
		}
	}
	function exportSettings() {																												// ===> EXPORT SETTINGS
		removeClass('body','has_menu');
		const settings_string = ( JSON.stringify($settings,null,'\t'));
		saveSettings('settings.json',settings_string);
	}
	function saveSettings(file_name, data) { saveFile(data,'application/json',file_name); }													// ===> SAVE SETTINGS
	//============================//
	// INITIALIZE EVENT LISTENERS
	function initEventListeners() {																											// ===> INIT EVENT LISTENERS
		initBaseEventListeners();																													// init base event listeners
		initStatsEventListeners();																													// init stats event listeners
		document.getElementById('default_settings').onclick = function(e) 		{ defaultSettings(e); }; 											// settings to default
		document.getElementById('export_settings').onclick = function(e) 		{ e.preventDefault(); e.stopPropagation(); exportSettings(); };		// export settings
		document.getElementById('open_font_label').onclick = function(e) 		{ e.stopPropagation(); };											// open font file (req: opentype.js font parsing)
		document.getElementById('open_font').onchange = function(e) 			{ e.stopPropagation(); openFile(e,'font'); };						// open font
		document.getElementById('save_svg').onclick = function(e) 				{ e.stopPropagation(); saveGlyph(); };								// save glyph as svg
		document.getElementById('view_directory_source').onclick = function(e)	{ e.stopPropagation(); showWarning( 'showDirectorySource' ); };		// toggle show directory source
		document.getElementById('open_in_content_pane').onclick = function() 	{ showWarning( 'openSidebarInContentPane' ); };						// open sidebar in content pane
		document.getElementById('open_in_text_editor').onclick = function(e) 	{ e.preventDefault();  openInTextEditor(); this.blur() }; 			// sendMessage, the receiveMessage from iframe & openInTextEditor
		document.getElementById('show_grid').onclick = function(e) 				{ e.stopPropagation(); showContent('show_grid'); };					// show grid
		document.getElementById('show_font_grid').onclick = function(e) 		{ e.stopPropagation(); showContent('show_font_grid'); };			// show font grid
		document.getElementById('show_image_grid').onclick = function(e) 		{ e.stopPropagation(); showContent('show_image_grid'); };			// show image grid
		document.getElementById('close_audio').onclick = function(e) 			{ e.stopPropagation(); closeMedia('audio'); }; 						// close audio button click
		document.querySelectorAll('#prev_btn, #next_btn, .prev_next_track_btn').forEach( el => el.onclick = function(e) 	{ clickPrevNextButtons(e,el.id); }); // ============> combine with next?
		document.getElementById('directory_footer').onclick = function() 			{ removeClass('.directory_item','hovered'); };
		document.getElementById('content_image_container').querySelector('img').onclick = function(e)	{ scaleImages(e); focusContent('content_image_container'); }; // Zoom image on click
		document.getElementById('content_title').onclick = function() 			{ if ( getContentPaneData() !== 'has_font_file' ) { showPlaylistEntry('content_title'); } };
		document.getElementById('content_audio_title').onclick = function() 	{ if ( getContentPaneData() !== 'has_font_file' ) { showPlaylistEntry('content_audio_title'); } };
		document.getElementById('play_toggle').onclick = function(e) 			{ toggleAllChecked(e); };											// toggle media checkboxes
		document.getElementById('open_playlist_label').onclick = function(e)	{ e.stopPropagation(); };											// Open playlist
		document.getElementById('open_playlist').onchange = function(e) 		{ e.stopPropagation(); openFile(e,'playlist'); };					// Open playlist
		document.getElementById('make_playlist').onclick = function(e) 			{ e.preventDefault(); e.stopPropagation(); closeMenus(); showWarning('warning_make_playlist'); };
		document.getElementById('close_playlist').onclick = function(e) 		{ e.preventDefault(); e.stopPropagation(); showWarning('closePlaylist'); };				 	// close playlist/filelist
		document.getElementById('close_playlist_btn_container').onclick = function(e)		{ e.preventDefault(); e.stopPropagation(); showWarning('closePlaylist',[true]); };// close playlist/filelist
		document.querySelectorAll('textarea,div[contenteditable],select').forEach( el => el.onclick = function(e) 	{ e.stopPropagation(); });								// prevents losing textarea focus on click
		document.querySelectorAll('#content_pane .content').forEach( el => el.onclick = function() 					{ focusContent(); });									// focus content on click
		document.querySelectorAll('#text_editor, #show_text_editor').forEach( el => el.onclick = function(e) 		{ e.preventDefault(); showContent( el.id ); });			// set up and show top level text editor
		document.querySelectorAll('#increase,#decrease').forEach( el => el.onclick = function(e) 					{ e.stopPropagation(); scaleButtons(e,el.id); });			  				// click scale buttons
		document.querySelectorAll('#warnings_container button').forEach( el => el.onclick = function(e) 			{ e.preventDefault(); e.stopPropagation(); warningButtons( this.id ); });
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onclick = function(e) 	{ e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmousedown = function(e) { e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmouseup = function(e) 	{ e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
		// HOVER EVENTS
		document.querySelectorAll('#menu li').forEach( el => el.onmouseenter = function() {
			if ( document.querySelector('#menu li.selected') !== null ) { removeClass('#menu li','selected'); addClass('#menu li:hover','selected'); }	// hide open submenus, and add selected class to hovered menu items
			//if ( document.querySelector('#menu li.has_open_submenu') !== null ) { removeClass('#menu li.has_open_submenu,#menu .hovered','has_open_submenu hovered'); }	// hide open submenus, and add selected class to hovered menu items
		});
		document.querySelector('#cue_sheet_track_list_container_audio').onmouseenter = function() {
			let el = document.getElementById('cue_sheet_track_list_container_audio');
			document.querySelector('#cue_sheet_track_list_container_audio > div').style.top = el.offsetTop + el.clientHeight - 4 +'px';										// set y position of cuesheet track list
		};
		document.getElementById('footer_utilities').onmouseenter = function() 		{ addClass('body:not(.has_menu), body:not(.has_menu_parents)','faded'); };
		document.getElementById('footer_utilities').onmouseleave = function() 		{ removeClass('body:not(.has_menu), body:not(.has_menu_parents)','faded'); };
		document.querySelectorAll('#audio,#content_video').forEach( el => el.onended = function() 					{ arrowKeyNavigation(['ArrowRight',true]); });
		document.querySelectorAll('#loop_media_files,#shuffle_media_files').forEach( el => el.onclick = function()	{ audioPlaybackOptions( el.id ); closeMenus(); });		// media option menu items
		document.querySelectorAll('#audio_options input').forEach( el => el.onclick = function() 					{ audioPlaybackOptions( el.id ); el.blur(); document.getElementById('top').focus(); });
		// document.getElementById('audio').ontimeupdate = 															getMediaTimeRemaining;			// update remaining media time; !!!disabled until remainging time fixed
		initDirListEventListeners();																												// init dir list event listeners
	}
	function initBaseEventListeners() { 																									// ===> INIT BASE EVENT LISTENERS: minimal listeners needed for error pages
		window.addEventListener('message',receiveMessage,false);																					// init receive messages
		document.onclick = function(e) { closeMenus(e); }; 																							// close menu click
//		document.querySelectorAll('#directory_menus a').forEach( el => el.onclick = function(e) { e.preventPropagation(); showMenus('parents_dir_menu'); });
		document.querySelectorAll('.toggle_UI_pref').forEach( el => el.onclick = function(e) 	 { toggleUIPrefOnClick(e,this.id); }); 				// toggle UI prefs click
		document.getElementById('directory_wrapper').onclick = 					focusSidebar; 														// focus sidebar on various clicks
		document.getElementById('parents_dir_menu').onclick = function(e) 		{ e.stopPropagation(); showMenus('parents_dir_menu'); };			// show menus click
		document.querySelector('#parent_dir_nav a').onclick = function(e)		{ e.preventDefault(); changeLocation(this.href); }; 				// parents menu items
		document.getElementById('menu_container').onclick = function(e) 		{ e.stopPropagation(); showMenus('menu_container'); };				// show menus click
		document.getElementById('show_help').onclick = function(e) 				{ e.stopPropagation(); addClass('body#top','has_help'); removeClass('body','has_menu'); };	// show help click
		document.getElementById('close_help').onclick = function(e) 			{ e.preventDefault();  removeClass('body','has_help'); };			// close help click
		document.getElementById('help_container').onclick = function(e) 		{ e.stopPropagation(); };											// help container: do nothing for clicks
		document.getElementById('close_btn').onclick = function(e) 				{ e.stopPropagation(); e.preventDefault(); closeButton();	this.blur(); }; // close button
		document.getElementById('reload_btn').onclick = function(e) 			{ e.stopPropagation(); e.preventDefault(); reloadContent(); this.blur(); this.classList.remove('reset'); };	// reset button
		document.getElementById('handle').onmousedown = 						resizeSidebar;														// resize sidebar
		document.addEventListener('mouseup',function(e) 						{ document.onmousemove = null; });	// revoke drag on mouseup
	}
	function initDirListEventListeners() { 																									// ===> INIT DIR_LIST EVENT LISTENERS; called whenever new dir list items added
		if ( !isTopWindow() ) { return; }
		if ( document.querySelector('.directory_item') !== null ) { document.querySelectorAll('.directory_item').forEach( el => el.onclick = function(e) { clickDirListItem(e,el.id); }); }	// show item or play/pause media
		if ( document.querySelector('.directory_item.dir') !== null ) {																				// open/close subdirectories
			document.querySelectorAll('.directory_item.dir .has_icon_before_before').forEach( el => el.onclick = function(e) { openCloseSubdirectory(e,el.closest('.directory_item').id); });
			document.querySelectorAll('.directory_item.dir').forEach( el => el.ondblclick = function(e) { e.preventDefault(); showWarning('doubleClickThis', [el.id, el.querySelector('a').href] ); });
		}
		if ( document.querySelector('.directory_item.link') !== null ) {																				// open link files on dblclick
			document.querySelectorAll('.directory_item.link').forEach( el => el.ondblclick = function(e) { openLinkFile(e); });
		}
		if ( document.querySelector('.directory_item.media') !== null ) {																			// Click media checkboxes
			document.querySelectorAll('.directory_item.media input').forEach( el => el.onclick = function(e) { toggleChecked(e,el.closest('.directory_item').id); });
		}
		if ( document.querySelector('.directory_item.playlist') !== null ) {
			document.querySelectorAll('.directory_item.playlist').forEach( el => el.ondblclick = function(e) { e.preventDefault(); e.stopPropagation(); document.getElementById('open_playlist').click(); });
		}
		if ( document.querySelector('#show_invisibles_container') !== null ) { document.querySelector('#show_invisibles_container').onclick = function(e) {
			e.stopPropagation(); document.querySelector('#show_invisibles_container input').click(); }
		}
	}
	function initStatsEventListeners() {																									// ===> INIT STATS EVENT LISTENERS
		if (document.getElementById('stats_summary') !== null ) { document.getElementById('stats_summary').onclick = function(e) { e.stopPropagation(); showStats(); } }	// show stats
		document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onmouseenter = function() {
			document.querySelectorAll('.directory_item'+ getHoveredStatsListClass(el)).forEach( el => el.classList.add('hovered')); 					// add the hovered class
			if ( document.querySelector('.directory_item.hovered') !== null ) {
				document.querySelector('.directory_item.hovered').scrollIntoView({ behavior:'smooth',block:'nearest',inline:'nearest' }); } 			// scroll 1st matched el
		});
		document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onmouseleave = function() { removeClass('.directory_item.hovered','hovered'); });
		document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onclick = function() {	// onclick stats footer detail items
			if ( !hasClass('body','sort_by_kind') ) { document.getElementById('sort_by_kind').click(); } 											// sort by kind
			if ( document.querySelector('.directory_item'+ getHoveredStatsListClass(el)) !== null ) {
				if ( el.classList.contains('invisible') && getSearchParam('show_invisibles') === 'false' ) { document.getElementById('show_invisibles').click(); }
				if ( el.classList.contains('ignored') && getSearchParam('hide_ignored_items') === 'true' ) { document.getElementById('hide_ignored_items').click(); }
				showThis(document.querySelector('.directory_item'+ getHoveredStatsListClass(el)).id); 												// click the first matched dir_list item
			}
		});
	}
	function initGridItemEventListeners() {																									// ===> INIT GRID ITEM EVENT LISTENERS
		document.querySelectorAll('#content_grid div').forEach( el => el.onclick = function(e) { showGridItem(e,el.dataset.id,el.querySelector('a').href,el.dataset.kind); }); // add event watchers
		document.querySelectorAll('#content_grid div:not(.selected)').forEach( el => el.onmouseenter = function() { addClass('#'+el.dataset.id,'hovered'); });
		document.querySelectorAll('#content_grid div:not(.selected)').forEach( el => el.onmouseleave = function() { removeClass('#'+el.dataset.id,'hovered'); });
		document.querySelectorAll('.directory_item.image,.directory_item.font').forEach( el => el.onmouseenter = function() { if ( getContentPaneData() === 'has_grid' ) { addClass('#content_grid > div[data-id="'+ el.id +'"]','hovered'); } });
		document.querySelectorAll('.directory_item.image,.directory_item.font').forEach( el => el.onmouseleave = function() { if ( getContentPaneData() === 'has_grid' ) { removeClass('#content_grid > div[data-id="'+ el.id +'"]','hovered'); } });
	}
	function initCueSheetListEventListeners() {																								// ===> INIT CUESHEET EVENT LISTENERS
		document.querySelectorAll('.cue_sheet_track_list_container li').forEach( el => el.onclick = function(e) {
			e.stopPropagation();
			let $parent_nav_id = el.closest('nav').id;
			switch(true) {
				case el.classList.contains('selected'): playPauseMedia(); break;
				default:
					addRemoveClassSiblings('#'+ this.id,'selected');
					let time = this.getAttribute('data-duration'); 																				// N.B.: cue time format is mm:ss:ff (ff = frames, 75 frames/sec)
					switch($parent_nav_id) {
						case 'cue_sheet_track_list_container_audio': if ( time < document.getElementById('audio').duration ) 			{ document.getElementById('audio').currentTime = time; } break;
						case 'cue_sheet_track_list_container_video': if ( time < document.getElementById('content_video').duration )	{ document.getElementById('content_video').currentTime = time; } break;
					}
				}
		});
	}
	//============================//
	// INITIALIZE IFRAME EVENT LISTENERS
	function initIframeEventListeners() {																									// ===> INIT IFRAME EVENT LISTENERS
		if ( isTopWindow() ) { return; }
		if ( document.getElementById('open_in_sidebar') !== null ) {
			document.getElementById('open_in_sidebar').onclick = function(e)	{ e.preventDefault(); sendMessage('top','open_iframe_dir_in_sidebar','',window.location.href); };
		}
		if ( document.querySelector('.directory_item.dir') !== null ) {
			document.querySelectorAll('.directory_item.dir .has_icon_before_before').forEach( el => el.onclick = function(e) { openCloseSubdirectory(e,el.closest('.directory_item').id); }); // open/close iframe subdirs on click
			document.querySelectorAll('.directory_item.dir').forEach( el => el.ondblclick = function(e) { e.preventDefault(); showWarning('doubleClickThis', [el.id, el.querySelector('a').href] ); }); // open iframe dirs on dbl
		}
		if ( document.querySelector('.directory_item') !== null ) 				{ document.querySelectorAll('.directory_item').forEach( el => el.onclick = function(e) { iframeDirectoryClick(e,el.id); }); }	// select iframe items
		document.onclick = function() { removeClass('#iframe_body','has_stats'); }
		document.querySelectorAll('#iframe_body, #iframe_body #directory_list').forEach( el => el.onclick = function() {
			removeClass('body','is_blurred'); sendMessage('top','iframe_click'); 																	// tell top to close menus, focus content
		});
		if ( document.querySelector('.directory_item.file:not(.ignored)') !== null ) 	{
			document.querySelectorAll('.directory_item.file:not(.ignored) a').forEach( el => el.ondblclick = function(e) { iframedoubleClickThis( e,this.closest('.directory_item').id, this.href );					// open iframe files on dblclick
			});
		}
		if ( document.querySelector('#iframe_parent_link') !== null ) { document.getElementById('iframe_parent_link').onclick = function(e) { iframeClickLink(e,'','iframe_parent_link') } } // iframe parent
		document.getElementById('show_grid').onclick = function(e) 				{ e.stopPropagation(); showContent('show_grid'); };					// show grid
		document.querySelectorAll('.toggle_UI_pref').forEach( el => el.onclick = function(e) { toggleUIPrefOnClick(e,el.id); }); 					// toggle UI prefs
		window.addEventListener("message", receiveMessage, true);																					// initialize receive messages
		initStatsEventListeners();																													// initialize stats events listeners
	// parent directory link for source directory view
		//document.querySelectorAll('#parentDirText,#UI_goUp a.up').forEach( el => el.onclick = function(e) {
		//	e.preventDefault(); e.stopPropagation(); removeClass('#iframe_body','has_stats');
		//	if ( window.location.search.indexOf('view_directory_source') > -1 ) { window.location = (el.closest('a').href + '?&view_directory_source=true'); }
		//});
	}
	function initTextEditorEventListeners() {																								// ===> INIT TEXT EDITOR EVENT LISTENERS
		document.querySelectorAll('#iframe_body, #iframe_body #toolbar, #iframe_body #toolbar li').forEach( el => el.onclick = function() {
			removeClass('body','is_blurred'); sendMessage('top','iframe_click'); 																	// tell top to close menus, focus content
		});
		document.querySelectorAll('#toolbar_buttons .toggle_UI_pref').forEach( el => el.onclick = function(e) { toggleUIPrefOnClick(e,el.id); });	// text editing UI is not in DOM on page load;
		document.querySelectorAll('#iframe_body textarea,#iframe_body form,#iframe_body select,#iframe_body input,#iframe_body option').forEach( el => el.onclick = function(e) {
			e.preventDefault(); e.stopImmediatePropagation();																						// needed to allow elements to be selected
		});
		let $preview = document.getElementById('text_editor_preview_pane');
		// Toolbar button functions		document.onmouseup = function() { null; focusTextEditorPanes(); };
		document.getElementById('toolbar').onmousedown = function(e) 				{ e.preventDefault(); }; 										// prevent textarea from losing focus when clicking sidebar
		document.getElementById('text_editing_handle').onmousedown = function(e)	{ e.stopPropagation(); MDresizeSplit(); };						// resize text editor panes
		document.getElementById('text_editing_handle').onmouseup = function()		{ document.onmousemove = null; focusTextEditorPanes(); };						// resize text editor panes
		document.getElementById('text_editing_handle').ondblclick = function(e)		{					 											// reset editor elements to 50/50 split
			e.stopPropagation();	document.querySelectorAll('#text_editor_raw_pane,#text_editor_preview_pane,#text_editor_html_pane,#text_editing_handle').forEach( el => el.removeAttribute('style') );
		};
		window.onresize = function()	{
			document.querySelectorAll('#text_editor_raw_pane,#text_editor_preview_pane,#text_editor_html_pane,#text_editing_handle').forEach( el => el.removeAttribute('style') );	// reset split on window resize;
		};
		// document.querySelectorAll('label').forEach( el => el.onclick = function(e)	{ el.parentElement.querySelector('input').click(); }); // Click labels to toggle checkboxes (text_editor_preview, toolbar)
		// Sync scroll // convert this so that function is triggered only by hovered element, not when targeted el is scrolled
		document.getElementById('text_editor_raw_pane').oninput = function() {																				// on input...
			if ( !hasClass('body','edited') ) {	addClass('body','edited'); if ( !isTopWindow() ) { sendMessage('top','iframe_edited','',''); } } // add edited body class, and if iframe, send edited message to top
			MDlivePreview();																														// update live preview
		};
		document.getElementById('text_editor_raw_pane').onscroll = function(e)						{ MDsyncScroll(e,'text_editor_raw_pane'); };
		document.getElementById('text_editor_html_pane').onscroll = function(e)						{ MDsyncScroll(e,'text_editor_html_pane'); };
		// $preview.on('scroll'): see "scroll_iframe" in "scroll_script" added to iframe src_doc
		document.querySelectorAll('#save_btn li').forEach( el => el.onclick = function() 			{ saveBtn(el.id); }); 							// save text editor content
		document.getElementById('clear_text').onclick = function()	 								{ showWarning('clearText'); }; 					// clear text button
		$preview.querySelectorAll('.checklist input').forEach( el => el.onclick = function(e) 		{ e.stopPropagation(); MDliveCheckBoxes(el); });// Live checkboxes
		$preview.querySelectorAll('.table-of-contents a').forEach( el => el.onclick = function(e)	{ e.preventDefault();  MDtocClick(el.id); }); 	// Preview TOC click navigation
		$preview.querySelectorAll('.uplink').forEach( el => el.onclick = function(e) 				{ e.stopPropagation(); MDheaderClick(); }); 	// Click header uplinks
		document.querySelectorAll('#warnings_container button').forEach( el => el.onclick = function(e) 			{ e.preventDefault(); e.stopPropagation(); warningButtons( this.id ); });
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onclick = function(e) 	{ e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmousedown = function(e) { e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
		document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmouseup = function(e) 	{ e.preventDefault(); e.stopPropagation(); return; });	// prevent user actions with warning or overlay
	}
	function initIframeHtmlFileEventListeners() { 																							// ===> INIT IFRAME HTML FILE EVENT LISTENERS
		if ( document.querySelector('#iframe_body a') !== null ) { document.querySelectorAll('#iframe_body a').forEach( el => el.onclick = function(e) { iframeClickLink( e, el.getAttribute('href'), '' ); }); }
	}
	function initFontPreviewEventListeners() {																								// ===> INIT FONT PREVIEW EVENT LISTENERS
		document.querySelectorAll('#font_toolbar,#font_specimen_grid,#specimen_glyph').forEach( el => el.onclick = function(e)	{ e.stopPropagation(); focusContent(); });			// Stop propagation
		document.getElementById('unicode_char_ranges_select').onchange = function(e) { e.preventDefault(); e.stopPropagation(); unicodeChars(this.value); document.getElementById('font_specimen').scroll(0,0); } // unicode chars
		document.getElementById('font_variant_select').onchange = function(e)					{ e.preventDefault(); e.stopPropagation(); setFontFeatureSettings(this.value,this.id); } // set font-variant
		document.getElementById('font_stylistic_set_select').onchange = function(e)				{ e.preventDefault(); e.stopPropagation(); setFontFeatureSettings(this.value,this.id); } // set font-variant
		document.getElementById('font_tag_textarea').oninput = function(e)						{ e.stopPropagation(); setFontFeatureSettings(this.value,this.id); }		// set custom font-feature-settings
	}
	//============================//
	// INITIALIZE KEYDOWN EVENTS
	document.querySelectorAll('#top, #iframe_body').forEach( el => el.onkeydown = function(e) {
		const $selected = ( getContentPaneData() === 'has_font_file' ? document.querySelectorAll('.glyph_container.selected .glyph')[0] : document.querySelectorAll('.directory_item.selected')[0] );
		switch(true) { 	// Disable all keydown events except escape, return, and tab when help or warning is shown, or when textarea is focused
			case document.activeElement.hasAttribute('contentEditable') && !/escape|tab/.test(e.key.toLowerCase()) && !( cmdKey(e) && e.key === 'w'):
			case ( /button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && !(cmdKey(e) && (e.key === 'r' || e.key === 'w') ) && !/escape|tab|shiftkey|metakey|altkey/.test(e.key.toLowerCase()) ):
			case (hasClass('body','has_warning') || hasClass('body','has_help') ) && !( cmdKey(e) || (/escape|tab|shiftkey|enter/.test(e.key.toLowerCase()) ) ):
			return;
		}
		// MAIN KEYDOWN EVENTS
		if ( e.key && !e.metaKey && !e.altKey && !e.ctrlKey && !/ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ) { alphaNav(e); } 	// alphanumeric navigation
		switch(true) {																														// else alphanumeric + modifier keys
		case e.key === 'shiftKey' && ( hasClass('body','has_warning') || hasClass('body','has_help') ):										// Shift Key
			if (e.key !== 'Enter' && e.key !== 'Tab') { e.preventDefault(); return false; }											break;
		case ( /ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ): { arrowKeyFunctions(e,false,el); }							break;	// arrowKeyFunctions(e,bool,selected_el.id); id for dblclick iframe items
		case e.key === ' ': 																												// Key = Space
			switch(true) { 																															// Play/pause media (space bar)
				case hasClass('#content_pane','has_audio') || getContentPaneData() === 'has_video':
					e.preventDefault(); e.stopPropagation(); playPauseMedia(); break;
				case !isTopWindow() && document.querySelector('.directory_item.audio_loaded') !== null:
					e.preventDefault(); e.stopPropagation(); sendMessage('top','iframe_play_pause_media'); break;
			}
			break;
		case e.key === 'Enter': 																											// Key = Enter/Return
			switch(true) {
				case hasClass('body','focus_content') && /has_font/.test(getContentPaneData()) && document.activeElement.contentEditable !== true && document.querySelector('#content_font .selected') !== null:
					if ( /has_font|has_glyph/.test(getContentPaneData() ) ) { showFontGlyph( document.querySelector('#content_font .selected').id); }
					break;	// show selected font specimen glyph
				case hasClass('body','has_warning') || hasClass('body','has_help'):		e.preventDefault();
					if (document.querySelectorAll('button.focus, button:focus') !== null ) { document.querySelectorAll('button.focus, button:focus')[0].click(); } // click focused warning button in #top or #iframe
					break;
				case !isTopWindow(): 																									// if iframe...
					switch(true) {
						case hasClass('body','has_menu'): sendMessage('top','clickMenu');																					break; // close main menu
						case document.querySelector('.directory_item.audio.selected') !== null && !hasClass('.directory_item.audio.selected','audio_loaded'):		console.log("B");
							iframedoubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
							break;
						case document.querySelector('.directory_item.selected') !== null && cmdKey(e):
							iframedoubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
							break; // webloc or url file
						case document.querySelector('.directory_item.audio_loaded') !== null && !hasClass('.directory_item.selected','audio_loaded'):
																										e.preventDefault(); e.stopPropagation(); playPauseMedia();			break;	// play/pause media
					}
					break;
				case hasClass('body','has_menu'):	e.preventDefault(); clickMenu(); sendMessage('iframe','close_menu');													break;	// click selected menu item
				case $selected.classList.contains('app') && $settings.apps_as_dirs === false:																				break;	// don't open app folders
				default:
					switch(true) {
						case $selected.classList.contains('.disabled'):	case getContentPaneData() === 'has_text_editor':													break;	// no nothing for disabled or default behavior
						case $selected.classList.contains('audio') && !$selected.classList.contains('audio_loaded'):
																								showAudio( document.querySelector('.directory_item.audio.selected').id );	break;	// show selected audio file
						case $selected.classList.contains('media'):	e.preventDefault(); e.stopPropagation(); playPauseMedia();												break;	// else play/pause playing media
						case ( /dir|link|playlist/.test($selected.classList) ) && cmdKey(e):
						doubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
//						$selected.querySelector('a').dblclick();
						break;	// open dirs, links, playlists
						case ( /dir|link/.test($selected.classList) ):																										// nobreak
						default:																		$selected.click();															// default: click selected
					}
			}
			break;
		case e.key === 'd' && cmdKey(e) && !hasClass('body','has_warning'): e.preventDefault(); document.querySelector('#show_details').click();	break;		// Cmd/Ctrl + D: Toggle Details
		case e.key === 'e': 																												// Cmd/Ctrl + E: Toggle Main Menu or Text Editor
			switch(true) {
				case hasClass('body','has_warning'): break;
				case cmdShiftKey(e):
					e.preventDefault();
					if ( !isTopWindow() ) { sendMessage('top','toggle_text_editor'); } else { document.querySelector('#show_text_editor a').click(); }
					addClass('body','faded');
					break;	// toggle text editor
				case cmdKey(e):	e.preventDefault();	if ( !isTopWindow() ) { sendMessage('top','toggle_menu'); } else { showMenus('menu_container'); }	break;	// toggle main menu
			}
			break;
		case e.key === 'g' && cmdKey(e) && ( hasClass('#top','has_images') || hasClass('#top','has_fonts') ): e.preventDefault(); document.querySelector('#show_grid').click();	break;		// Cmd/Ctrl + G: Show image Grid
		case e.key === 'i' && cmdKey(e): if ( !isTopWindow() ) { sendMessage('top','toggle_invisibles'); } else { document.querySelector('#show_invisibles_container input').click(); }	break;	// toggle invisibles
		case e.key === 'o' && cmdShiftKey(e):		window.open( getAttribute('.directory_item.selected a','href') );						break;	// Cmd/Ctrl + Shift + O: Open selected item in new window
		case e.key === 'r': 																														// Cmd/Ctrl + Shift + R: Refresh
			switch(true) {
				case cmdKey(e) && hasClass('#iframe_body','edited'): e.preventDefault(); showWarning('reloadContent'); 						break;	// warn before reloading edited iframe text files from textarea
				case cmdKey(e) && !isTopWindow():					 e.preventDefault(); sendMessage('top','reload');	  					break;	// send reload message to top
				case cmdKey(e): if ( getContentPaneData() === 'has_null' && !hasClass('#content_pane','has_audio') ) { return true; }				// reload window if no content open...
								else { e.preventDefault(); document.getElementById('reload_btn').click(); }									break;	// else click reload/reset button
			}
			break;
		case e.key === 'w' && cmdKey(e): 																									// KEY = W && Cmd/Ctrl: close content
			switch(true) {
				case hasClass('#iframe_body','edited'): 			e.preventDefault(); showWarning('closeContent');						break;	// warn before closing edited iframe text files from textarea
				case !isTopWindow():								e.preventDefault(); sendMessage('top','close');							break;	// send close message to top
				case hasClass('#content_pane','has_audio') && getContentPaneData() === 'has_null': e.preventDefault(); closeMedia('audio');	break;	// close audio
				case hasClass('body','has_playlist'):	case hasClass('body','has_filelist'):														// nobreak; close playlists/filelists
				case hasClass('body','iframe_edited'): 																								// nobreak; close edited iframe file
				case getContentPaneData() !== 'has_null':			e.preventDefault(); document.getElementById('close_btn').click();		break;	// click close button
				default: return true; 																												// else close window (or normal behavior)
				}
			break;
		case e.key === '=' && cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test(getContentPaneData()): 	e.preventDefault();	scaleButtons(e,'increase'); break;	// Cmd/Ctrl + equals sign: scale larger
		case e.key === '-' && cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test(getContentPaneData()): 	e.preventDefault(); scaleButtons(e,'decrease'); break;	// Cmd/Ctrl + hyphen: scale smaller
		case e.key === '\\': 																												// KEY = \ BACKSLASH
			switch(true) {
				case cmdShiftKey(e): 																												// Cmd Shift + \ : toggle split
					switch(true) {
						case isTopWindow() && getContentPaneData() === 'has_iframe': 		sendMessage('iframe','text_editor_split_view'); 			break;	// send toggle split message to top
						case document.querySelector('#text_editor_split_view').height > 0: 	document.querySelector('#text_editor_split_view').click();	break;	// if split view visible...click toggle split
					}
					break;
				case cmdKey(e): 																													// Cmd + \ : toggle sidebar
					if ( !isTopWindow() ) { sendMessage('top','hide_sidebar'); } else { document.querySelector('#hide_sidebar').click(); }
					break;
			}
			break;
		case e.key === 'Tab':																												// KEY = TAB
			e.preventDefault();
			switch(true) {
				case hasClass('body','has_warning'):												navigateWarningButtons(e);		 		break;	// focus warning buttons
				case document.activeElement.id === 'content_image':									focusSidebar(); 						break;	// focus sidebar from image
				case getContentPaneData() === 'has_text_editor':									focusTextEditorPanes(); 				break;	// focus text editor
				case getContentPaneData() === 'has_font' && document.activeElement.classList.contains('specimen'):
				case !isTopWindow() && /\.html|\.htm/.test(window.location.pathname) && document.querySelector('a,button,input,select,textarea,div[contenteditable]') !== null:
																									focusFocusableEls(e); 					break;
				case (/has_markdown|has_text|has_code|has_htm/.test(getContentPaneData()) && isTopWindow() ):
																									addClass('body','focus_content'); document.getElementById('content_iframe').focus();
																									sendMessage('iframe','focus_iframe');	break;	// focus iframe text editor or htm files from top
				case ( /text_editor_preview_pane|text_editor_html_pane|text_editor_raw_pane/.test(document.activeElement.id) ):	toggleTextEditorPanes();	break;	// toggle focused text editor panes
				case document.activeElement.closest('#text_editor_preview_pane') !== null:			focusFocusableEls(e,'#text_editor_preview_pane');	break;	// focus focusable elements in text preview
				case document.activeElement.closest('#font_specimen') !== null:						focusFocusableEls(e,'#font_specimen');	break;	// focus focusable elements in font specimen
//				case document.activeElement.hasAttribute('contenteditable'):
				case !isTopWindow(): 																									 // If iframe is focused, focus sidebar, dim iframe dir_list.selected
					e.preventDefault();
					switch(true) {
						case document.querySelector('#directory_wrapper.iframe') !== null:
						case !/a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ): 								// allow form elements to be focused
							addClass('#iframe_body,.directory_item.selected','is_blurred'); 															// dim iframe dir_list
							sendMessage('top','tab'); 																						break;	// focus sidebar
						case document.querySelector('a,button,input,select,textarea') !== null: 													// allow tabbing to form elements and textareas (e.g., in html pages)
							if ( hasClass('body','has_text_editor_UI') ) { focusFocusableEls(e,'#text_container'); } else { focusFocusableEls(e); }
																																			break;
					}
					break;
				case isTopWindow(): // Either focus sidebar or refocus content pane (after clicking menu, for example)
					switch(true) {
						case hasClass('body','focus_content') && hasClass('body','has_menu'): case !hasClass('body','focus_content'):	focusContent('',e);		break;
						case hasClass('body','focus_content'): case hasClass('body','has_menu'): focusSidebar();							break;
					}
					break;
			}
			break;
		case e.key === 'Escape': // remove text selections, close warnings or help, close menus (and refocus content), or focus sidebar.
			if ( getAttribute('#content_pane','data-loaded') !== 'loaded' ) { removeAttribute('#content_pane',['data-loaded']); removeAttribute('#content_iframe',['src']); } // close loading iframe
			if ( !isTopWindow() ) { addClass('#iframe_body','is_blurred'); sendMessage('top','escape'); } else { focusSidebar(); }
			window.getSelection().removeAllRanges();
			removeClass('#content_playlist, #content_audio_playlist','has_content');
    		if ( document.querySelector('#warning_btn_cancel,#close_help') !== null ) { document.querySelector('#warning_btn_cancel,#close_help').click(); }
    		document.onmousemove = null;
    		removeClass('body','has_overlay');
			break;
		case e.key === '.' && cmdKey(e):	if ( hasClass('body','has_warning') ) {	e.preventDefault();	document.querySelector('#warning_btn_cancel,#close_help').click(); } 	break;	// click cancel button
		} // end switch
	});
	// ***** END EVENT LISTENER INITIALIZATION
	//============================//
	function setLocation(link) { window.location = link; }
	function changeLocation(args) { // args[0] === href, args[1] === 'external'
		switch(true) {
			case args[0][1] === 'external': window.open(args[0][0]);																		break;	// open external menu links: about, coffee, contact
			case hasClass('body','has_playlist'): 	case hasClass('body','has_filelist'):	showWarning('closePlaylist',[true,args[0]]);	break;	// show playlist warning
			case ( /^file:\/\//.test(args[0]) && $protocol !== 'file:'):	addClass('body','has_warning');	addClass('#warnings_container','warning_local_bookmark');	document.getElementById('warning_btn_ok').focus();	break;
			default: showWarning( 'setLocation',args.toString() );
		}
	}
	function showMenus(id) {																												// ===> SHOW MENUS
		sendMessage('iframe','top_has_menu');
		removeClass('body','hide_sidebar');
		if ( hasClass('body','has_stats') ) { removeClass('body','has_stats'); }
		let position = document.getElementById('directory_header').offsetHeight - document.getElementById('directory_buttons').offsetHeight - document.getElementById('sorting').offsetHeight - document.getElementById('show_text_editor').offsetHeight + 1;
		document.querySelector('#'+ id +' > ul').style.top = position + 'px';
		switch(id) {
			case 'parents_dir_menu':
				switch(true) {
					case 		hasClass('body','has_menu_parents'): removeClass('body','has_menu_parents'); break;
					default: 	addClass('body','has_menu_parents'); removeClass('body','has_menu');
				}
				break;
			case 'menu_container':
				switch(true) {
					case 		hasClass('body','has_menu'): removeClass('body','has_menu'); 			sendMessage('iframe','top_closed_menu'); 				break;
					default: 	addClass('body','has_menu'); removeClass('body','has_menu_parents'); 	sendMessage('iframe','top_has_menu');
				}
		}
		document.querySelectorAll('#directory_menus a').forEach( el => el.onclick =  function(e) { e.stopPropagation(); e.preventDefault();								// init menu links click
			switch(true) {
				case ( /about_link|donate_link|contact_link/.test(el.id) ):								showWarning('changeLocation',[el.href,'external']);		break;	// external menu links
				default: 																				showWarning('changeLocation',[el.href]); 							// parents menu items
			}
		});
	}
	function clickMenu() {																													// ===> CLICK MENU
		document.querySelector('#menu .selected:not(.hovered)').querySelectorAll('a,span,label')[0].click();
		Array.from(document.querySelectorAll('#menu li')).forEach(function(el) {
			el.classList.remove('selected','selected_submenu_item','hovered','has_open_submenu');
		});
		removeClass('body','has_menu has_open_submenu');
		if ( hasClass('body','focus_content') ) { sendMessage('iframe','close_menu'); document.getElementById('content_iframe').focus(); }
		if ( hasClass('body','focus_content') ) { focusContent(); }
	}
	function closeMenus() {																													// ===> CLOSE MENUS
		switch(true) {
			// case !hasClass('body','has_menu') && !hasClass('body','has_menu_parents') && !hasClass('body','has_stats'): break; // why is this needed?
			default:
				removeClass('body','has_menu has_menu_parents has_open_submenu faded has_stats has_help');
				removeClass('#menu .selected, #menu .hovered','selected hovered');
		}
	}
	function showStats() {																													// ===> SHOW STATS
		updateDurations();
		addClass('body','has_stats'); removeClass('body','has_menu has_menu_parents');
		document.getElementById('stats_details').style.height = document.getElementById('stats_container').height - document.getElementById('stats_summary_detailed').height - 4;
	}
	function getHoveredStatsListClass(el) {																							// ===> GET HOVERED STATS CLASS
		el.classList.remove('file','not_media','media','stats_list_item','hover_bold');
		let this_class = '.'+ Array.from(el.classList).join('.');
		switch(true) {
			case el.id === 'stats_summary_detailed_dirs':	this_class = '.dir';	break;
			case el.id === 'stats_summary_detailed_files':	this_class = '.file';	break;
			// case /_/.test(this_class): 							this_class = this_class.replace('_','.'); break;
			case this_class === '.dir':								this_class = '.dir:not(.ignored):not(.invisible):not(.app)'; break;
			case this_class === '.dir.app':							this_class = '.dir.app:not(.ignored):not(.invisible)'; break;
		}
		return this_class;
	}
	//============================//
	function toggleUIPref(pref_ID) {																										// ===> TOGGLE UI PREFS: and update searchParams
		let message_target = ( isTopWindow() ? 'iframe' : 'top' );
		let bodyClassNames = ( message_target === 'iframe' ? document.getElementById('top').className.split(' ') : null );
			pref_ID = pref_ID.toString();
		if ( pref_ID === 'theme' && hasClass('body','theme_dark') ) { pref_ID = 'theme_light'; } else if ( pref_ID === 'theme' && hasClass('body','theme_light') ) 	{ pref_ID = 'theme_dark'; }
		switch(true) {																																	// Text editor prefs
			case ( /text_editor_theme_default/.test(pref_ID) ):	pref_ID = 'text_editor_'+ bodyClassNames.find( el => el.startsWith('theme') );			// nobreak
			case ( /text_editor_/.test(pref_ID) ):				toggleTextEditorPrefs(pref_ID);	if ( isTopWindow() ) { sendMessage('iframe',pref_ID); }	break;	// Text Editor Preferences
			case ( /enable_text_editing/.test(pref_ID) ):																toggleTextEditing(pref_ID);		break;	// why here and not in case above? // Enable/Disable Text editing
			case ( /sort_by_/.test(pref_ID) ):																 			toggleSort(pref_ID);			break;	// toggle sorting
			case ( /theme_light|theme_dark/.test(pref_ID) ):															toggleTheme(pref_ID);			break;	// toggle light/dark theme
			case ( /show_image_thumbnails/.test(pref_ID) ):	document.body.classList.toggle('show_image_thumbnails'); 	showImageThumbnails();			break;
			default:
				if ( /button|label|select|input/.test(document.activeElement.tagName.toLowerCase() ) ) { document.activeElement.blur(); document.body.focus(); } // blur any focused form elements
 				document.body.classList.toggle(pref_ID);	// toggle body class
																															// toggleSearchParam
				break;
		}
		if ( isTopWindow() ) { toggleSearchParam(pref_ID); sendMessage('iframe','toggleUIPref','',pref_ID); }
		if ( /show_invisibles|hide_ignored_items/.test(pref_ID) ) { updateStats(); }																// update stats if necessary
		if ( isTopWindow() && document.querySelector('.directory_item.selected') !== null ) { scrollThis('#directory_list','.selected',false); }	// scroll to selected dirlist item
		if ( hasClass('body','focus_content') ) { focusContent(); }																					// focus content if necessary
	}
	function toggleTextEditing(pref_ID) {
		document.body.classList.toggle('enable_text_editing');
		document.body.classList.toggle('disable_text_editing');
		if ( document.querySelectorAll('.selected.text, .selected.markdown, .selected.code')[0] !== undefined ) {
			showThis( document.querySelectorAll('.selected.text, .selected.markdown, .selected.code')[0].id );
		}
	}
	function toggleSort(pref_ID) {
		let has_dir = false, sorted, sort_direction, iframe_src, iframe_url, iframe_params, items_html_arr = [];
		if ( isTopWindow() && document.getElementById('content_pane').dataset.content === 'has_dir' ) { has_dir = true; iframe_src = document.getElementById('content_iframe').src; }
		switch(true) {
			case document.body.classList.contains(pref_ID):																					// if pref_ID = current sort, reverse sort direction
				if ( isTopWindow() ) {
					sort_direction = ( getSearchParam('sort_direction') === 'sort_ascending' ? 'sort_descending' : 'sort_ascending' );		// define new sort direction
					toggleSearchParam( sort_direction );
				} else {
					sort_direction = ( hasClass('body','sort_ascending') ? 'sort_descending' : 'sort_ascending' );							// toggle sort direction search param
				}
				document.body.classList.remove('sort_descending','sort_ascending');															// remove sort direction body class
				document.body.classList.add(sort_direction);																		break;	// add new sort direction body class
			default:																														// else if select different sort
				removeClass('body','has_menu sort_by_name sort_by_default sort_by_duration sort_by_size sort_by_date sort_by_kind sort_by_ext sort_descending sort_ascending'); // remove all sorting body classes
				sort_direction = $settings.sort_direction;																					// set default sort direction
				if (isTopWindow() ) { setSearchParam('sort_direction', sort_direction); }										// set default sort direction searchparam
				document.body.classList.add(pref_ID,$settings.sort_direction);																// add new sorting body classes
		}
		Array.from(document.querySelectorAll('.directory_item')).forEach(el => items_html_arr.push(el.outerHTML.replace(/border_bottom |border_top /g,'')));	// get elements for new sort
		sorted = sortDirList( Array.from(items_html_arr), pref_ID, sort_direction );														// sort the items
		document.getElementById('directory_list').innerHTML = sorted;																		// insert sorted items into dir_list
		initDirListEventListeners(); 																										// re-initialize dir_list event listeners
		if ( !isTopWindow() ) { initIframeEventListeners(); }
		switch(true) {
			case hasClass('#content_pane','has_font_grid'):							showGrid('show_font_grid');						break;	// sort grids --> change this to actual sort, not reload
			case hasClass('#content_pane','has_image_grid'):						showGrid('show_image_grid'); 					break;	// sort grids --> change this to actual sort, not reload
			case getContentPaneData() === 'has_grid':								showGrid('show_grid'); 							break;	// sort grids --> change this to actual sort, not reload
			case has_dir === true:																											// re-sort iframe directory
				 showThis(document.querySelector('.directory_item.selected').id);															// show the selected directory
				 iframe_url = new URL(iframe_src);																							// create url obj
				 iframe_params = new URLSearchParams(iframe_url.search)																		// create url search params
				 iframe_params.set('sort_by',pref_ID.slice(pref_ID.lastIndexOf('_') + 1));													// set sort_by
				 iframe_params.set('sort_direction',sort_direction);																		// set sort_direction params
				 iframe_url.search = iframe_params.toString();																				// update url search params
				 document.getElementById('content_iframe').src = iframe_url.href;													break;	// reload the iframe with new src url
			case document.querySelector('.directory_item.selected') !== null: showThis(document.querySelector('.directory_item.selected').id); break; 	// after sort, show selected item; don't autoloadcoverart
		}
	}
	function toggleTheme(pref_ID) {
		removeClass('body','theme_dark theme_light');
		addClass('body',pref_ID);
		switch(true) {
			case hasClass('body','text_editor_theme_default'):	removeClass('body','text_editor_theme_light text_editor_theme_dark'); addClass('body','text_editor_'+ pref_ID);	break;
			default:						 					toggleUIPref('text_editor_theme');
		}
		if ( isTopWindow() ) 									{ sendMessage('iframe',pref_ID); }									// send message iframe
	}
	function toggleTextEditorPrefs(pref_ID) {																								// ===> TOGGLE TEXT EDITOR PREFERENCES (from menus or toolbar buttons)
		let message_target = ( isTopWindow() ? 'iframe' : 'top' ), sync_scroll_input = document.querySelector('#text_editor_sync_scroll input');
		switch(true) {
			case ( /text_editor_theme/.test(pref_ID) ): 										// toggle text editor theme
				switch(true) {
					case pref_ID === 'text_editor_theme':
						if ( document.body.classList.contains('text_editor_theme_light') ) {
							document.body.classList.remove('text_editor_theme_light','text_editor_theme_default'); document.body.classList.add('text_editor_theme_dark');
						} else {
							document.body.classList.add('text_editor_theme_light','text_editor_theme_default'); document.body.classList.remove('text_editor_theme_dark');
						}
						pref_ID = ( hasClass('body','text_editor_theme_light') ? 'text_editor_theme_light' : 'text_editor_theme_dark' );			// toggle editor theme pref_ID
						break;
					case pref_ID === 'text_editor_theme_default':
						pref_ID = ( hasClass('body','text_editor_theme_light') ? 'text_editor_theme_light' : 'text_editor_theme_dark' ); 			// arg sent to iframe to set editor theme
						addClass('body','text_editor_theme_default'); removeClass('body','text_editor_theme_light text_editor_theme_dark');
						break;
					case pref_ID === 'text_editor_theme_light':		addClass('body','text_editor_theme_light');	removeClass('body','text_editor_theme_default text_editor_theme_dark');	break;
					case pref_ID === 'text_editor_theme_dark':		addClass('body','text_editor_theme_dark');	removeClass('body','text_editor_theme_default text_editor_theme_light');	break;
				}
				break;
			case pref_ID === 'text_editor_raw':					addClass('body','text_editor_raw');	toggleUIPref('text_editor_split_view');	break;	// text_editor_raw: toggle text_editor_split_view
			case ( /text_editor_preview|text_editor_html/.test(pref_ID) ): 																			// toggle text_editor_preview/text_editor_html panes
					if ( hasClass('body',pref_ID) ) { toggleUIPref('text_editor_split_view'); }														// toggle split, hiding text_editor_raw pane
					removeClass('body','text_editor_raw text_editor_preview text_editor_html');														// remove body classes
					addClass('body',pref_ID);																										// add pref_ID class
				break;
			case ( /text_editor_split_view/.test(pref_ID) ):	document.body.classList.toggle(pref_ID); 											// text_editor_split_view
					if ( hasClass('body','text_editor_split_view') ) { document.querySelector('#text_editor_raw_pane').focus(); }			break;	// focus #text_editor_raw
			case ( /text_editor_sync_scroll/.test(pref_ID) ):	document.body.classList.toggle(pref_ID);											// text_editor_sync_scroll
				if ( sync_scroll_input.checked === true ) { sync_scroll_input.checked = false; } else { sync_scroll_input.checked = true; }
																																			break;
		}
		switch(true) { //pref_ID += '_'+ getSearchParam('text_editor_theme');
			// case pref_ID === 'text_editor_theme': sendMessage('top','toggle_text_text_editor_theme','',pref_ID); break;
			// case ( /text_editor_theme_default|text_editor_theme_light|text_editor_theme_dark/.test(pref_ID) ): sendMessage('iframe','text_editor_toolbar_button','',pref_ID); break;
			default: sendMessage(message_target,'text_editor_toolbar_button','',pref_ID);
		}
	}
	function toggleUIPrefOnClick(e,id) { e.stopPropagation(); closeMenus(); showWarning('toggleUIPref',document.getElementById(id).getAttribute('data-ui_pref') ); }	// ===> CLICK TOGGLE UI PREF ELEMENTS
	//============================//
	function resizeSidebar(e) {																												// ===> RESIZE SIDEBAR/Content Pane
		e.stopPropagation();
		closeMenus();
		let $directory_wrapper = document.getElementById('directory_wrapper'), startX = e.pageX, window_width = window.innerWidth, sidebar_width = $directory_wrapper.offsetWidth;
		addClass('body','has_overlay'); 																											// prevent interference from the rest of ui
		document.onmousemove = function(f) {
			f.stopPropagation(); f.preventDefault();
			let deltaX = f.pageX - startX;
			if ( f.pageX > 230 && f.pageX < window_width - 200 ) { $directory_wrapper.style.width = ( sidebar_width + deltaX ) + 'px'; }
			scrollThis('#directory_list','.selected',false); 																						// true = instant scroll
		};
		document.onmouseup = function() {
			removeClass('body','has_overlay'); 																										// remove the overlay
			document.onmousemove = null; 																											// remove eventlistener
			setSearchParam('width',$directory_wrapper.offsetWidth); 																				// set the sidebar width query
			if ( hasClass('body','focus_content') ) { focusContent(); } 																			// refocus content if necessary
		};
	}
	//============================//
	// ***** BASIC UI FUNCTIONS ***** //
	function scrollThis(container_ID, el, bool) {																							// ===> SCROLL to Selected Item
		let container = document.querySelector(container_ID);
		if ( container.height === 0 ) { return; } 																									// don't scroll hidden elements
		let scroll_el = container.querySelector(el);
		let scroll_behavior = ( ( bool !== undefined || bool === true || getContentPaneData() === 'has_grid' ) ? 'instant' : 'smooth' ); 			// instant allows sidebar & grid to scroll simultaneously
		let scroll_block = ( hasClass('body','is_gecko') ? 'start' : 'nearest' );
		if ( scroll_el !== null ) { scroll_el.scrollIntoView({ behavior:scroll_behavior, block:scroll_block, inline:'nearest' }); }
	}
	function mouseMove(e,id,startX,startY,elOffsetLeft,elOffsetTop) {																			// Init events to allow glyphs to be dragged into view
		let scale = ( id === '#specimen_glyph' ? 2 : 1 );
		document.querySelector(id).style.left = elOffsetLeft + (e.pageX - startX)*scale + 'px'; document.querySelector(id).style.top = elOffsetTop + (e.pageY - startY)*scale +'px';
	}
	//============================//
	// ***** SORTING ***** //
	function addSortingBorders(sorted) {																							// ===> ADD SORTING BORDERS
		let item_kind;
		for ( let i = 0; i < sorted.length - 1; i++ ) {
			item_kind = sorted[i].match(/data-kind=\"\w+?\" /)[0];
			if ( sorted[i + 1].indexOf(item_kind) === -1 ) { sorted[i] = sorted[i].replace(/class=\"/,'class="border_bottom '); } // add border class
		}
		return sorted;
	}
	function sortIndex(items_html_arr,sort_type,sort_direction) { 																			// ===> SORT INDEX ITEMS
		let sort_id = sort_type.split('_').reverse()[0];
		const new_sort = new Intl.Collator( undefined, { numeric: true, sensitivity: 'base' } );
		  let sorted = [], aName, bName, aData, bData; 																								// aLevel, bLevel, aKind, bKind;
			  sorted = items_html_arr.sort( (a, b) => {																								// sorted items
				// aLevel = a.dataset.level;										bLevel = b.dataset.level; // subdirectory level
				if ( !/data-name/.test(a) || !/data-name/.test(b) ) { null; } else { aName = a.replace(/(.+?)data-name="([^"]+?)"(.+)/g,'$2');	bName = b.replace(/(.+?)data-name="([^"]+?)"(.+)/g,'$2'); }// get data-name
				switch(true) { 																														// aData, bData = size, date, kind, ext, time
					case !( new RegExp('data-'+ sort_id) ).test(a) || !( new RegExp('data-'+ sort_id) ).test(b):	break;
					default:	aData = a.replace( ( new RegExp( '.+?data-'+ sort_id +'="([^"]+?)".+') ),'$1' );	bData = b.replace( ( new RegExp( '.+?data-'+ sort_id +'="([^"]+?)".+') ),'$1' );
				}
				switch(true) { 																														// sort 'em!
					case sort_direction === 'sort_ascending':	return ( new_sort.compare(aData, bData) === 0 ? new_sort.compare(aName, bName) : new_sort.compare(aData, bData) );	// A-Z
					case sort_direction === 'sort_descending':	return ( new_sort.compare(bData, aData) === 0 ? new_sort.compare(bName, aName) : new_sort.compare(bData, aData) );	// Z-A
				}
			});
			return sorted;																															// return sorted items
	}
	function sortDirList(items_html_arr, sort_type, sort_direction) {																		// ===> SORT DIR LIST on click
		let  sorted = [], sort_all = items_html_arr, sort_dirs = items_html_arr.filter( item => ( /data-kind=\"dir\"/.test(item) ) ), sort_files = items_html_arr.filter( item => ( !/data-kind=\"dir\"/.test(item) ) );
		switch(true) {
			case ( /sort_by_size|sort_by_date/.test(sort_type) ) && (hasClass('body','has_filelist') || hasClass('body','has_filelist has_playlist')):	return items_html_arr.join('\n');	// don't sort playlists by size/date
			case sort_type === 'sort_by_default' || ( sort_type !== 'sort_by_name' && $settings.dirs_on_top === true ):								// if sort default or by name sort with dirs_on_top)
				const sorted_dirs = sortIndex( sort_dirs, sort_type, sort_direction ), sorted_files = sortIndex( sort_files, sort_type, sort_direction );	// ...sort dirs and files separately
				switch(true) {
					case sort_direction === 'sort_ascending':																						// if sort ascending...
						if ( sorted_files[0] !== undefined && sorted_dirs[0] !== undefined && !/sort_by_kind|sort_by_ext/.test(sort_type) ) {
							sorted_files[0] = sorted_files[0].replace(/class=\"/,'class="border_top '); 											// add border class
						}
						sorted = sorted_dirs.concat(sorted_files);																					// ...dirs before files
						break;
					default:																														// sort descending...
						if ( sorted_dirs[0] !== undefined && !/sort_by_kind|sort_by_ext/.test(sort_type) ) { sorted_dirs[0] = sorted_dirs[0].replace(/class=\"/,'class="border_top '); } // add border class
						sorted = sorted_files.concat(sorted_dirs);																					// ...else files before dirs
				}
				break;
			default:	sorted = sortIndex( sort_all, sort_type, sort_direction );																	// other sorts (name, size, date): files and dirs together
			}
		if ( /sort_by_kind|sort_by_ext/.test(sort_type) ) { sorted = addSortingBorders(sorted); }											// add borders for sort by kind and ext
		return sorted.join('\n');
	}
	// ***** END BASIC UI FUNCTIONS ***** //
	//============================//
	// ***** CONTENT PANE ***** //
	function focusSidebar() {																												// ===> FOCUS SIDEBAR
		document.activeElement.blur();
		closeMenus();
		removeClass('body','faded focus_content');
		document.getElementById('directory_nav').focus();
		sendMessage('iframe','blur_iframe');
	}
	function focusContent(id,e) {																											// ===> FOCUS CONTENT
		let activeElementID = document.activeElement.id, $content_pane_data = getContentPaneData();
		document.activeElement.blur();
		closeMenus();
		addClass('body','focus_content');
		switch(true) {
			case id === 'content_iframe' && cmdKey(e) && e.key === 'ArrowDown' && isTopWindow() && hasClass('body','focus_content'):
				sendMessage('iframe','iframe_arrow_navigation','','ArrowDown');
				document.getElementById(id).focus();
				break;
			case $content_pane_data === 'has_font':
				if ( document.getElementById('font_specimen_grid').children.length > 0 ) { null } else { document.getElementById('specimen').focus(); }	break;
			case activeElementID.toLowerCase() === 'text_editor_preview_pane' && getFocusableEls('#text_editor_preview_pane').length > 0:		getFocusableEls('#text_editor_preview_pane')[0].focus();	break;
			case $content_pane_data === 'has_text_editor':	focusTextEditorPanes();							break;									// focus text editor
			case ( /has_text|has_markdown|has_code|has_htm|has_dir|has_app/.test( $content_pane_data ) ):											// no break; focus iframe text editor
			case hasClass('#content_iframe','has_content'): 																						// focus iframe and/or iframe text editor
				document.getElementById('content_iframe').focus(); if ( e !== undefined && e.shiftKey ) { sendMessage('iframe','shift_focus_iframe'); } else { sendMessage('iframe','focus_iframe'); }	break;
			case ( /has_video|has_pdf/.test( $content_pane_data ) ):							 													// no break; video, pdf
			case $content_pane_data === undefined:  																								// no break; data-content undefined
			case hasClass('body','has_playlist'):		removeClass('body','focus_content');				break;									// don't focus content in this and the above two cases
			case ( /has_font_file|has_glyph/.test($content_pane_data) ):	document.getElementById('content_font').focus();	break;				// font files and glyphs
			case $content_pane_data === 'has_grid':		document.getElementById('content_grid').focus();	break;									// grids
			case $content_pane_data === 'has_image':	document.getElementById('content_image').focus();	break;									// images
			case id !== undefined:						document.getElementById(id).focus();				break;
		}
	}
	function getFocusableEls(id) {																											// ===> GET FOCUSABLE ELEMENTS
		let focusableEls = ( id !== undefined ? Array.from(document.getElementById(id).querySelectorAll('a,button,input,select,textarea,div[contenteditable]')).filter( item => item.offsetWidth > 0 ) : Array.from(document.querySelectorAll('a,button,input,select,textarea,div[contenteditable]')).filter( item => item.offsetWidth > 0 ) );
		return focusableEls;
	}
	function focusFocusableEls(e,id) {																										// ===> FOCUS FOCUSABLE ELEMENTS (iframe files or text editor preview)
		e.preventDefault();
		let els = getFocusableEls(id);
		switch(true) {
			case e.shiftKey: 																														// tab + shift: focus prev
				switch(true) {
					case els.indexOf(document.activeElement) === 0: 	sendMessage('top','focus_sidebar'); break;
					case !els.includes(document.activeElement): 		els[els.length - 1].focus(); 			break; 								// if nothing focused...focus last el
					default: els[els.indexOf(document.activeElement) - 1].focus(); 																	// else focus previous
				}
				break;
			case document.activeElement === els[els.length - 1]:
				if ( !isTopWindow() ) { sendMessage('top','focus_sidebar'); } else { focusSidebar(); } break;							// if last focussable el, focus sidebar
			default: els[els.indexOf(document.activeElement) + 1].focus(); 																			// tab: select next focusable element
		}
	}
	function focusButton(id) { let el = document.getElementById(id); el.classList.add('focus'); el.focus(); }								// ===> FOCUS BUTTON (for warnings)
	//============================//
	// ===> SHOW INDIVIDUAL CONTENT TYPES
	//============================// MEDIA
	function showAudio(id,iframe_audio_link,bool) {																							// ===> SHOW AUDIO; bool from
		closeMedia('video');
		document.activeElement.blur();
		document.body.focus();
		let link, title;
		switch(true) {
			case id === 'content_iframe_file': 																										// clicked iframe audio files
				link = decodeURIComponentSafe(iframe_audio_link);
				title = link.slice(link.lastIndexOf('/') + 1);
				addClass('#content_pane','has_audio has_iframe_audio');
				setAttribute('#content_iframe_utility','src',link.slice(0,-4)); 																	// load cue sheet in utility iframe
 				break;
			default: 																																// dir_list audio files
				switch(true) {
					case hasClass('#'+ id,'local'): break;
					case bool === 'true': 																						// bool !== undefined: if from autoLoadFile, just select file (don't add .audio_loaded class)
						if ( document.querySelector('.dir.content_loaded') !== undefined ) { addRemoveClassSiblings('.dir.content_loaded','selected'); } // select dir.selected instead of media
						addClass('#'+ id,'selected');
						break;
					default: addRemoveClassSiblings('#'+ id,'audio_loaded selected'); 																// otherwise select loaded media
				}
				link = document.getElementById(id).querySelector('a').getAttribute('href');
				title = document.getElementById(id).querySelector('a').innerText;
				addClass('#content_pane','has_audio'); removeClass('#content_pane','has_iframe_audio');
				getCueSheet(id,link,'audio');
		}
		setAttribute('#audio','src', link );
		document.querySelector('#content_audio_title span').innerText = title;
		removeClass('#content_audio_playlist','has_content');
	}
	function showVideo(id,link) { closeMedia('audio'); getCueSheet(id,link,'video'); document.activeElement.blur(); document.getElementById('content_video').focus(); } // ===> SHOW VIDEO; rest of processing is done in showContent();
	//============================// MEDIA PLAYBACK
	function updateTrackList() { 																											// ===> UPDATE TRACKLIST (for shuffle play)
		let playlist = [];
		Array.from(document.querySelectorAll('.directory_item.media:not(.unchecked,.disabled,.audio_loaded,.content_loaded,.selected)')).forEach( el => playlist.push( el.id ) ); // don't include currently selected item
		return playlist;
	}
	function shuffleArray(array) { 																											// ===> SHUFFLE ARRAY: Randomize Shuffle List
		for ( let i = array.length - 1; i > 0; i-- ) { const j = Math.floor(Math.random() * (i + 1));  [array[i], array[j]] = [array[j], array[i]]; }  return array;
	}
	function updateShuffleList(id) {																										// ===> UPDATE SHUFFLE LIST
		let shuffle_list;
		switch(true) {
			case !hasClass('body','shuffle_media'): return; 																						// don't shuffle if normal playback
			case id !== undefined: 																													// handle checked and unchecked media items: id = checked/unchecked item.id
				shuffle_list = document.getElementById('content_audio').dataset.shufflelist.split(',');												// get the dataset.shufflelist and convert to array
				switch(true) {
					case hasClass('#'+ id,'unchecked') || ( /audio_loaded|content_loaded/.test(document.getElementById(id).classList.value) ):		// remove items unchecked since shuffle option first selected
						shuffle_list.splice(shuffle_list.indexOf(id), 1);  break;
					default: shuffle_list.push(id); shuffle_list = shuffleArray( shuffle_list ); 													// else add re-checked items to shufflelist
				}
				break;
			default:	shuffle_list = shuffleArray( updateTrackList() ); 																			// init shufflelist when shuffle option selected
		}
		document.getElementById('content_audio').dataset.shufflelist = shuffle_list;
	}
	function getNextShuffledItem() {																										// ===> GET NEXT SHUFFLED ITEM
		let shuffle_list = document.getElementById('content_audio').dataset.shufflelist.split(',');
		let shuffled_item_id = shuffle_list.pop(); 																									// get last item in shufflelist for playback
		document.getElementById('content_audio').dataset.shufflelist = shuffle_list;
		return shuffled_item_id;
	}
	function toggleChecked(e,id) {																											// ===> TOGGLE CHECKED single media checkboxes and update shufflelist
		e.stopPropagation(); if ( hasClass('body','is_gecko') ) { e.preventDefault(); } 															// because Firefox sucks
		document.getElementById(id).blur(); document.getElementById(id).classList.toggle('unchecked'); updateShuffleList(id);
	}
	function toggleAllChecked(e) { e.stopPropagation(); document.querySelectorAll('.directory_item.media input').forEach( el => el.click() ); updateShuffleList(); }	// ===> TOGGLE ALL CHECKBOXES; update shufflelist
	function isPlaying(id) {																												// ===> IS PLAYING; returns true if all conditions are true
		switch(true) {
			case !isTopWindow():
				return ( hasClass('body','is_playing') ? true : false );
			default: return ( id !== undefined && document.getElementById(id).currentTime > 0 && !document.getElementById(id).paused && !document.getElementById(id).ended );
		}
	}
	function mediaSkip(e,args) {																											// ===> MEDIA SKIP +/-10/30 seconds
		let factor, skip;
		switch(true) {
			case e !== undefined: 		factor = ( e.key   === 'ArrowLeft' ? -1 : 1 ); 	skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); break; // from top
			case args !== undefined:	factor = ( args[0] === 'ArrowLeft' ? -1 : 1 );	skip = args[1] || 0; break; 								// from iframe
		}
		const $player = ( getContentPaneData() === 'has_video' ? document.getElementById('content_video') : document.getElementById('audio') );
		const time = $player.currentTime; 																											// get current time
		$player.currentTime = time + factor*(skip); 																								// set new time
	}
	function playPauseMedia(task) {																											// ===> PLAY/PAUSE MEDIA
		let $player = ( getContentPaneData() === 'has_video' ? document.getElementById('content_video') : document.getElementById('audio') );
		switch(true) {
			case $player === null: break;
			case hasClass('#content_pane','has_iframe_audio'):
				if (isPlaying( $player.id )) { $player.pause(); } else { $player.play(); }		addClass('body','is_playing'); removeClass('body','is_paused'); sendMessage('iframe','setIframePlayerStatus','',task); break;
			case isPlaying( $player.id ): 	try { $player.pause(); } catch(error) { null; } 	addClass('body','is_paused'); removeClass('body','is_playing'); break;
			case task === 'play':
			default:						try { $player.play(); } catch(error) { return; } 	addClass('body','is_playing'); removeClass('body','is_paused');
		}
	}
	function audioPlaybackOptions(id) {																										// ===> AUDIO PLAYBACK OPTIONS (shuffle, loop)
		let $shuffle = document.getElementById('shuffle'), $loop = document.getElementById('loop');
		switch(true) {
			case id === 'shuffle':	case id === 'shuffle_media_files':	document.body.classList.toggle('shuffle_media');  updateShuffleList(); break;
			case id === 'loop':		case id === 'loop_media_files': 	document.body.classList.toggle('loop_media'); break;
		}
		switch(true) { 																																// change audio checkboxes prop
			case id === 'shuffle_media_files':	( $shuffle.checked === true ? $shuffle.checked = false : $shuffle.checked = true ); break;
			case id === 'loop_media_files':		( $loop.checked === true ? $loop.checked = false : $loop.checked = true ); break;
		}
	}
	function getMediaTimeRemaining() {																										// ===> GET MEDIA REMAINING TIME !!!Disabled: need to subtract time of already played
		if ( !isTopWindow() || hasClass('#content_pane','has_iframe_audio') || document.getElementById('total_duration') === null ) { return; }		// don't calculate time remaining for iframe media --> check later if this can/should be fixed
		let total_time = document.getElementById('total_duration').dataset.total_duration, remaining_time = getFormattedDuration(total_time - document.getElementById('audio').currentTime);
		document.querySelector('#total_duration').dataset.time_remaining = ' (Time Remaining: '+ remaining_time +')';
		document.querySelector('#stats_details li.audio span.audio').dataset.audio_duration = ' (Time Remaining: '+ remaining_time +')';
	}
	//============================// PLAYLISTS/FILELISTS
	function openPlaylist(file_name,reader,data) { 	// files & reader = open .m3u file; data = m3u.txtfile content// 						===> OPEN PLAYLIST
		if ( !hasClass('body','has_playlist') && !hasClass('body','has_filelist') ) { 																// if body does not already have playlist or filelist...
			let body_classes = document.body.classList; 																			// ...store original dir_list and body "has_"classes as data
			document.getElementById('directory_list').dataset.dir_list = document.getElementById('directory_list').innerHTML;						// store the original dir_list
			document.getElementById('directory_list').dataset.data_classes = body_classes.value;													// store the original body classes
			body_classes.forEach( function(body_class) { if ( body_class.startsWith('has_') ) { document.body.classList.remove( body_class ); } });	// remove media and other body classes
		}
		removeClass('body','has_playlist has_filelist');
		closeMenus();
		file_name = ( file_name !== '' ? file_name : document.querySelector('.directory_item.selected.playlist .directory_item_name_a').innerText ); // get the file name for the title and current_dir_path
		closeContent();
		if ( !data.startsWith('#EXTM3U') ) { return; }																								// prevent reading non-playlist files
		let new_index = buildNewIndex( '', convertPlaylist(data),'','playlist' );																	// build the new dir_list
		// body classes
		if ( new_index[1].split(' ').filter(function(e) { return e !== 'has_media' && e !== 'has_audio' && e !== 'has_video' && e !==''; }).length > 0 || document.querySelectorAll('.directory_item.dir') !== undefined ) {
			addClass('body','has_filelist');												 														// if playlist contains non-media files or dirs
		} else {
			addClass('body','has_playlist has_media'); 																								// else normal media playlist
		}
		if ( /file:/.test(new_index) && !/file:/.test($protocol) ) { 																				// show warning about local files on non-local page
			addClass('body','has_warning');
			addClass('#directory_list','local');
			showWarning('warning_local_playlist');
		}
		// if ( !hasClass('#sort_by_name','','selected' ) ) { document.getElementById('sort_by_name').click(); } // sort by name
		document.getElementById('directory_list').innerHTML = new_index[0];																		// replace dir_list with prepared playlist
		initEventListeners();
		updateStats();
		if ( hasClass('body','autoload_media') ) { autoLoadFile(); } 																				// autoload media
		scrollThis('#directory_list','.selected',false);
		document.title = 'Playlist: '+ file_name;
		document.querySelector('#current_dir_path span').innerHTML = file_name;
		document.getElementById('open_playlist').value = ''; 																						// clear form to allow new list to be loaded
 	}
	function makePlaylist() {																												// ===> MAKE PLAYLIST
		let items, playlist = [];
		let playlist_type = document.querySelector('#make_playlist_form input:checked').id;
		switch(playlist_type) {																														// get playlist items according to selected type
			case 'media_files_only':	items = document.querySelectorAll('.directory_item.media:not(.unchecked)');	break;
			case 'audio_files_only':	items = document.querySelectorAll('.directory_item.audio:not(.unchecked)');	break;
			case 'video_files_only':	items = document.querySelectorAll('.directory_item.video:not(.unchecked)');	break;
			case 'all_non_media_files':	items = document.querySelectorAll('.directory_item.not_media');				break;
			case 'all_items':			items = document.querySelectorAll('.directory_item'); 						break;
			case 'directories_only':	items = document.querySelectorAll('.directory_item.dir');					break;
			case 'files_only':			items = document.querySelectorAll('.directory_item.file');					break;
		}
		switch(true) {
			case items.length === 0:	showWarning('warning_no_playlist');											break;							// show warning if no qualifying items found
			default:
				items.forEach(function(item) { playlist.push( makePlaylistEntry(item.id,true) ); });												// make playlist entry for each item
				playlist = '#EXTM3U\n'+ playlist.join('\n'); 																						// add playlist header id
				saveFile( playlist,'audio/mpeg-url',(document.getElementById('current_dir_path').innerText).split('/').reverse()[1] +'.m3u' );		// save m3u with default name = current dir name
				closeWarning();																														// close warning
		}
	}
	// Get playlist entry for display in title bar
	function makePlaylistEntry(id,bool) { //*** id = 'content_title' or 'content_audio_title', bool = true --> from makePlaylist(), otherwise from showPlaylistEntry(); ***//
		let title, link, duration, full_path = window.location.protocol + window.location.hostname + window.location.pathname;
 		title = ( getContentPaneData() === 'has_grid' && id === 'content_title' ? 'Files from: '+ full_path : bool === true ? document.getElementById(id).dataset.name : document.getElementById(id).innerText ); // get title txt
 		switch(true) {																																											// Get link
 			case bool === true: 				link = document.getElementById(id).querySelector('a').href;																				break;	// get item link for filelists
 			case getContentPaneData() === 'has_grid' && id === 'content_title':		link = full_path;																					break;	// grid link = dir path
 			case id === 'content_title': 		link = ( getContentPaneData() === 'has_image' ? getAttribute('#content_image','src') : getAttribute('.content.has_content','src') );	break;	// content link
 			case id === 'content_audio_title':	link = getLinkInfo( getAttribute('#audio','src') )[0].trim(); 																			break;	// audio link
 		}
		link = ( link.startsWith('/') ? window.location.protocol +'//'+ link : link );																// fix links without protocols (local files)
		link = new URL(link);																														// make new URL from link
		link = link.protocol +'//'+ link.hostname + link.pathname;																					// compose link
		duration = ( getContentPaneData() === 'has_video' ? Number.parseInt(document.getElementById('video').duration) : id === 'content_audio_title' ? Number.parseInt(document.getElementById('audio').duration) : '' );
		return '#EXTINF:'+ duration +','+ title +'\n'+ link +'\n';																					// return composed playlist entry
	}
	// Show Playlist Entry
	function showPlaylistEntry(id) {																										// ===> SHOW PLAYLIST ENTRY
		let el_id = ( id === 'content_title' ? 'content_playlist' : id === 'content_audio_title' ? 'content_audio_playlist' : '' );
		document.getElementById(el_id).classList.toggle('has_content');
		document.getElementById(el_id).querySelector('textarea').value = makePlaylistEntry(id);
		selectTextareaContent(document.getElementById(el_id).querySelector('textarea').id); 														// add entry to the textarea
		document.getElementById(el_id).querySelector('textarea').focus();
	}
	//============================// CUESHEETS
	function getCueSheet(id,link,kind) { // id = 'content_iframe_file' or 'dir_list.media.id', link = selected.href, kind = audio/video 		// ===> GET CUE SHEET
		if ( id === 'content_iframe_file' ) { return; } 																							// prevent error for iframe files
		removeClass('.cue_sheet_track_list_container','has_cue_sheet');																				// reset cuesheet container
		document.querySelector('#cue_sheet_track_list_audio').innerHTML = ''; 																		// empty existing cue sheet track list
		let media_file_name = document.getElementById(id).dataset.name;
		let cue_file_name = media_file_name.slice(0,media_file_name.lastIndexOf('.')) + '.cuetxt';
		let cue_file = document.querySelector('.directory_item.code[data-name="'+ cue_file_name +'"'); 												// get the cuesheet id
		if ( cue_file !== null ) {
			cue_file = document.getElementById( cue_file.id );
			setAttribute('#content_iframe_utility','src',(cue_file.querySelector('a').href).trim() ); 													// load cuetxt in utility iframe for processing
			switch(kind) { 																																// get durations
				case 'audio': setAttribute('#cue_sheet_track_list_container_audio','data-duration',document.getElementById('audio').duration); 			break;
				case 'video': setAttribute('#cue_sheet_track_list_container_video','data-duration',document.getElementById('content_video').duration);	break;
			}
		}
	}
	function processCueSheet(cue_sheet_text) {																								// ===> PROCESS CUE SHEET
		let cue_sheet_tracks, track, command, prepped_track_list = [], track_id, display_time, index, index_sec;
			cue_sheet_text = cue_sheet_text.replace(/\t/g,' ');
		if ( !cue_sheet_text.startsWith('TRACK') ) { // let cue_sheet_info = cue_sheet_text.slice(0,cue_sheet_text.indexOf('TRACK ')); 				// if there is a file info header, remove it;
			cue_sheet_tracks = cue_sheet_text.slice(cue_sheet_text.indexOf('TRACK ')).split('TRACK ').reverse();
		} else {
			cue_sheet_tracks = cue_sheet_text.split('TRACK ').reverse();
		}
		for ( track of cue_sheet_tracks ) { 																										// for each track in the cue sheet...
			let prepped_track = [];
				track = track.trim().split(/[\n\r]/);
				track_id = track.shift().split(' ')[0];
				for ( command of track ) { 																											// for each cue command (e.g.: PERFORMER "Artist")...
					command = command.replace(/\"$/m,'').trim();																					// ...trim and split into command and value
					switch(true) {
						//case ( /^PERFORMER\s+/.test(command) ):	prepped_track[1] = '<span class="cue_performer">'+ command.replace(/^PERFORMER\s+\&quot;/,'') +'</span>'; 	break;	// artist name -- omit;already in titlebar
						case ( /^TITLE\s+/.test(command) ): 	prepped_track[2] = '<span class="cue_title">'+ track_id +' '+ command.replace(/^TITLE\s*\&quot;/,'').replace(/\&quot;$/,'') +'</span>'; 			break;	// track title
						case ( /^INDEX\s+01\s+/.test(command) ): 																													// time index
							display_time = command.replace(/INDEX\s+\d+\s+/,'');
							index = display_time.split(':').reverse(); 																				// N.B.: cue time format is mm:ss:ff (ff = frames @ 75 frames/sec)
							index_sec = index[0]/75 + index[1]*1 + index[2]*60; 																	// convert index to seconds
							prepped_track[3] = '<span class="cue_index align_right">'+ display_time +'</span></li>';
							break;
					}
				}
				prepped_track[0] = '<li id="track_'+ track_id +'" class="cue_sheet_track background_grey_85 pointer padding_4_6" data-duration="'+ index_sec +'">';
				//if ( prepped_track[1] === undefined || ( /cue_performer"><\//.test(prepped_track[1]) ) ) 	{ prepped_track[1] = '<span class="cue_performer">[missing]</span>'; }
				if ( prepped_track[2] === undefined || ( /cue_title"><\//.test(prepped_track[2]) ) ) 		{ prepped_track[2] = '<span class="cue_title">[missing]</span>'; }
				if ( prepped_track.length > 3 ) { prepped_track_list.push(prepped_track.join('')); } 												// prepped_track.length > 3 to prevent adding empty track
		}
		prepped_track_list = prepped_track_list.reverse().join('');
		switch(true) {
			case document.querySelector('.directory_item.audio_loaded') !== null:
			case hasClass('#content_pane','has_iframe_audio'):
				addClass('#cue_sheet_track_list_container_audio','has_cue_sheet');
// TEMPPPPPPPPPPPP testing
				//document.getElementById('cue_sheet_track_list_container_audio').insertAdjacentHTML('beforeend',prepped_track_list); // add cue sheet track list to menu
//document.getElementById('cue_sheet_track_list_audio').insertAdjacentHTML('afterbegin',`<li id="cue_sheet_title" class="cue_sheet_track display_flex header background_grey_85 border_top"><span>${ getLinkInfo( getAttribute('#content_iframe_utility','src') )[1] }</span></li>`); 																				// add cue sheet track list title to menu
				document.getElementById('cue_sheet_track_list_audio').insertAdjacentHTML('beforeend',prepped_track_list); 							// add cue sheet track list to menu
				break;

			case document.querySelector('.directory_item.video.content_loaded') !== null:															// nobreak
			case hasClass('#content_pane','has_iframe_file'):
				addClass('#cue_sheet_track_list_container_video','has_cue_sheet');
				document.getElementById('cue_sheet_track_list_container_video').insertAdjacentHTML('afterbegin',prepped_track_list); 				// add cue sheet track list to menu
				break;
		}
		initCueSheetListEventListeners();																											// init cuesheet event listeners
		if ( document.querySelectorAll('.cue_sheet_track_list_container') !== null ) {
			document.querySelectorAll('.cue_sheet_track_list_container').onmouseenter = function() { 												// add selected class to first track whose time is less than currentTime
				if ( this.id === 'cue_sheet_track_list_container_video' ) { this.querySelector('.cue_sheet_track_list').style.top = this.clientHeight; }
				let currentTime = document.getElementById('audio').currentTime;
				setAttribute( this.querySelector('.cue_sheet_track_list'),'data-duration',currentTime ); 											// set duration attr of cue sheet track list
				this.querySelector('.cue_sheet_track_list').style.height = document.getElementById('content_container').height + document.getElementById('content_title_container').height; // set height of cue sheet track list
				let currentTrack = this.querySelector('.cue_sheet_track_list li').filter(function() { return parseInt( this.dataset.duration ) <= Math.round(currentTime); }).last();
					currentTrack.parentElement.children.classList.remove('selected');
					currentTrack.classList.add('selected');
			};
		}
	}
	//============================// IMAGES
	function getImageDimensions(link, callback) {
		if ( link !== undefined ) { let img = new Image();  img.src = link;  img.onload = function() { callback( this.width, this.height ); }; img = null; }
	}
	function setImageDimensions() {																											// ===> SET IMAGE DIMENSIONS
		switch(true) {
			case !isTopWindow():		 																						break;	// ignore if iframe
			case getContentPaneData() === 'has_image':
				getImageDimensions( getAttribute('#content_image','src'), function( width,height ) {												// getImageDimensions()
					let percentage = (( document.getElementById('content_image').width/width ) * 100 ).toFixed(1);									// define percentage
						setAttribute('#content_title span','data-after',' ('+ width +'px × '+ height +'px) ('+ percentage +'%)' ); 					// set dataset.after for content_title
				});																															break;
			default: removeAttribute('#content_title span',['data-after']); 																		// remove image dimensions
		}
	}
	function showImageThumbnails() {
		let image_files = document.querySelectorAll('.directory_item.image'), image_url, current_background_image;									// Add/remove image thumbnails as background icons
		if ( image_files !== null ) {
			Array.from(image_files).forEach( function(image) {
				current_background_image = image.querySelector('a .has_icon_before_before').style.backgroundImage;
				switch(true) {																														// toggle thumbnail display
					case !hasClass('body','show_image_thumbnails'):																					// show default icon, don't remove existing thumbnail
						image.querySelector('a .has_icon_before_before').style.backgroundImage = get_SVG_UI_File_Icon('file_icon_image') +','+ current_background_image;	// only first background image is visible
						break;
					default:																														// remove default image icon or load image thumbnail
						image_url = ( current_background_image.indexOf('\\),\surl') ? current_background_image.replace(/^.+?\),\surl/m,'url') : 'url("'+ image.querySelector('a').href +'")' );
						image.querySelector('a .has_icon_before_before').style.backgroundImage = 'url("'+ image.querySelector('a').href +'")';
				}
			});
		}
	}
	//============================// FONTS
	function setContentFontSource(id,bool,font_grid,link,i,font_files_length) { 															// ===> SHOW FONT (and create font items) row
			// bool = true if for show font grid, id from fontGridItems(); link = from previewed directory
		let font_styles = document.getElementById('font_styles'), border_class = '', last_item_class = '';
		let font_family =	( link !== '' ? link.slice(link.lastIndexOf('/') + 1,link.lastIndexOf('.')) : document.getElementById(id).dataset.name ); font_family = decodeURIComponentSafe(font_family);
		let font_url =	( link !== '' ? link : getAttribute('#'+ id +' a','href') );		font_url = decodeURIComponentSafe(font_url);
		switch(true) {
			case bool === false: 																													// just show selected font
				if ( font_styles.innerHTML !== '' ) { font_styles.innerHTML = ''; } 																// delete previous @font-face rule
				font_styles.insertAdjacentHTML( 'beforeend',`@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }` );
				if ( document.getElementById('content_font') !== null ) { document.getElementById('font_specimen').style.fontFamily = '"'+font_family+'"'; }	// set content font style
				break;
			case font_grid === 'font_grid': { 																										// else make font grid items
				const display_name = font_family;
				if ( i === font_files_length - 1 ) { last_item_class = 'border_bottom_x'; }
				if ( i > 0 ) { border_class = 'border_top_x'; }
				  let font_grid_item_info = `<p class="margin_0 text_color_default">${ display_name.toUpperCase() }</p><h2 style=\'font-family: "${ font_family }"\'; class="margin_0"><a class="text_color_default" href="${ font_url }">${ display_name.slice(0,font_family.lastIndexOf(".")) }</a></h2>`;
				  let $font_grid_item_el = `<div class="font_grid_item ${border_class} ${last_item_class} background_grey_95" data-id="${ id }" data-kind="font">${ font_grid_item_info }</div>`;
				document.getElementById('font_grid_styles').insertAdjacentHTML('beforeend', `@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }`);
				return $font_grid_item_el;
				}
		}
	}
	function hexToDecimal(d) { return parseInt(d, 16); }

	function decimalToHex(d, padding) { var hex = Number(d).toString(16); 	  hex = "000000".substr(0, padding - hex.length) + hex;	return hex; }
	function unicodeChars(char_block,bool) {
		let chars = '';
		let Char_Ranges = {
BMP_Range_01: {start:'0000',end:'0FFF'}, BMP_Range_02: {start:'1000',end:'1FFF'}, BMP_Range_03: {start:'2000',end:'2FFF'}, BMP_Range_04: {start:'3000',end:'3FFF'}, BMP_Range_05: {start:'4000',end:'4FFF'}, BMP_Range_06: {start:'5000',end:'5FFF'}, BMP_Range_07: {start:'6000',end:'6FFF'}, BMP_Range_08: {start:'7000',end:'7FFF'}, BMP_Range_09: {start:'8000',end:'8FFF'}, BMP_Range_10: {start:'9000',end:'9FFF'}, BMP_Range_11: {start:'A000',end:'AFFF'}, BMP_Range_12: {start:'B000',end:'BFFF'}, BMP_Range_13: {start:'C000',end:'CFFF'}, BMP_Range_14: {start:'D000',end:'DFFF'}, BMP_Range_15: {start:'E000',end:'EFFF'}, BMP_Range_16: {start:'F000',end:'FFFF'}, SMP_Range_01: {start:'10000',end:'10FFF'}, SMP_Range_02: {start:'11000',end:'11FFF'}, SMP_Range_03: {start:'12000',end:'12FFF'}, SMP_Range_04: {start:'13000',end:'13FFF'}, SMP_Range_05: {start:'14000',end:'14FFF'}, SMP_Range_06: {start:'15000',end:'15FFF'}, SMP_Range_07: {start:'16000',end:'16FFF'}, SMP_Range_08: {start:'17000',end:'17FFF'}, SMP_Range_09: {start:'18000',end:'18FFF'}, SMP_Range_10: {start:'19000',end:'19FFF'}, SMP_Range_11: {start:'1A000',end:'1AFFF'}, SMP_Range_12: {start:'1B000',end:'1BFFF'}, SMP_Range_13: {start:'1C000',end:'1CFFF'}, SMP_Range_14: {start:'1D000',end:'1DFFF'}, SMP_Range_15: {start:'1E000',end:'1EFFF'}, SMP_Range_16: {start:'1F000',end:'1FFFF'}, SIP_Range_01: {start:'20000',end:'20FFF'}, SIP_Range_02: {start:'21000',end:'21FFF'}, SIP_Range_03: {start:'22000',end:'22FFF'}, SIP_Range_04: {start:'23000',end:'23FFF'}, SIP_Range_05: {start:'24000',end:'24FFF'}, SIP_Range_06: {start:'25000',end:'25FFF'}, SIP_Range_07: {start:'26000',end:'26FFF'}, SIP_Range_08: {start:'27000',end:'27FFF'}, SIP_Range_09: {start:'28000',end:'28FFF'}, SIP_Range_10: {start:'29000',end:'29FFF'}, SIP_Range_11: {start:'2A000',end:'2AFFF'}, SIP_Range_12: {start:'2B000',end:'2BFFF'}, SIP_Range_13: {start:'2C000',end:'2CFFF'}, SIP_Range_14: {start:'2D000',end:'2DFFF'}, SIP_Range_15: {start:'2E000',end:'2EFFF'}, SIP_Range_16: {start:'2F000',end:'2FFFF'}, TIP_Range_01: {start:'30000',end:'30FFF'}, TIP_Range_02: {start:'31000',end:'31FFF'}, SSP_Range_01: {start:'E0000',end:'E0FFF'}
		}
		// let ligs = ['42802','42803','198','230','42804','42805','42806','42807','42808','42809','42810','42811','42812','42813','42830','42831','42848','42849','64256','64257','64258','64259','64260','64261','64262','64263','64264','64265','64266','64267','64268','64269','64272','64273','64274','405','8468','7930','7931','306','307','338','339','43874','7838','223','42792','42793','7531','43875'];
		switch(char_block) {
			case '':		document.getElementById('font_specimen_grid').innerHTML = '';	break;
			// case 'ligs': ligs.forEach(lig => chars += '<div id="glyph_specimen_'+lig+'" data-char="'+lig+'" data-unicode_hex="U+'+decimalToHex(lig,4)+'" class="border_right_x border_bottom_x background_grey_95">&#'+lig+';</div>' );									break;
			default:
				for ( let i = hexToDecimal(Char_Ranges[char_block].start); i < hexToDecimal(Char_Ranges[char_block].end) + 1; i++ ) {
					chars += '<div id="glyph_specimen_'+i+'" data-char="'+i+'" data-unicode_hex="U+'+decimalToHex(i,4)+'" class="border_right_x border_bottom_x background_grey_95">&#'+i+';</div>';
				}
		}
		if (bool) { return chars; } else if ( chars !== '' ) {
			closeGlyph('specimen');
			document.getElementById('font_specimen_grid').innerHTML = chars.trim(); focusContent();
			document.querySelectorAll('#font_specimen_grid div').forEach( el => el.onclick = function(e) 	{ e.stopPropagation(); showFontGlyph(el.id); });
			document.querySelector('#specimen_glyph').onmousedown = function(e) { e.stopPropagation();
				let startX = e.pageX, startY = e.pageY, elOffsetLeft = document.querySelector('#specimen_glyph').offsetLeft, elOffsetTop = document.querySelector('#specimen_glyph').offsetTop;
				document.onmousemove = function(e) { mouseMove(e,'#specimen_glyph',startX,startY,elOffsetLeft,elOffsetTop) }
			};
		}
	}
	function resetFontFeatureSettings(id) {																											// REMOVE FONT FEATURES AND VARIANTS ( if id === undefined > remove all )
		let el_ids = ['font_variant_select','font_tag_textarea','font_stylistic_set_select'], el;
		document.getElementById('font_specimen').style.removeProperty('font-feature-settings');
		el_ids.forEach(function(el_id) { if ( el_id === id ) { return } else { el = document.getElementById(el_id); if ( el.tagName.toLowerCase() === 'select' ) { el.selectedIndex = 0 } else {  el.value = '' } } });
	}
	function setFontFeatureSettings(value,id) {
		let font_specimen = document.getElementById('font_specimen');
		if ( id === 'font_tag_textarea' && value.length !== 4 ) { return; } // else { document.activeElement.blur(); }
		resetFontFeatureSettings(id);
		switch(true) {
			case (/cv01/.test(value)) && id !== 'font_tag_textarea': document.getElementById('font_tag_textarea').value = value; document.getElementById('font_tag_textarea').focus();	break;
			case (/ss01/.test(value)) && id !== 'font_tag_textarea': document.getElementById('font_stylistic_set_select').value = value; document.getElementById('font_stylistic_set_select').focus();	break;
			default: font_specimen.style.setProperty('font-feature-settings','\"'+value+'\"');
		}
	}
	function openFontFile(files,reader) {																									// ===> OPEN FONT FILE
		closeContent();		hideGrid();																												// close content & hide grids
		setAttribute('#content_pane','data-content','has_font_file');
		addClass('#content_font','has_content');
		document.querySelector('#glyph_viewer').dataset.scale = 1;
		parseFont(reader.result);																													// parseFont()
		document.querySelector('#content_title span').innerHTML = files.name;
		document.getElementById('open_font').value = ''; 																							// reset input to allow same font to be reopened immediately after closing.
		document.querySelectorAll('.glyph_container').forEach( el => el.addEventListener('click', () => showFontGlyph(el.id))) ;					// init click listener for each glyph
		focusContent();																																// focus content
	}
	function parseFont(fontblob) {																											// ===> PARSE FONT (req opentype.js)
		let font = window.opentype.parse(fontblob);
		let font_file_grid = document.getElementById('font_file_grid');
		let glyph_container, glyph_canvas_el, glyph_info, glyph, glyph_width, glyph_height, context_X, bounding_box, glyph_unicode, this_glyph, context;
		let glyph_path, glyph_SVG, viewer_height = document.getElementById('content_font').offsetHeight;
			document.getElementById('content_font').font_glyphs = font.glyphs; 																		// store glyphs data in $content_font as property font_glyphs
			document.getElementById('glyph_viewer').dataset.font_name = font.names.fullName.en;														// add font name to glyph viewer dataset
		// Draw glyphs
		let font_glyphs = font.glyphs, glyphs = '';
		for ( let i = 0; i < font_glyphs.length; i++ ) {
			glyph = font_glyphs.glyphs[i]; 																											// get glyph
			glyph_unicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode ); 													// glyph unicode info
			glyph_info =		`<div class="glyph_info text_color_333 display_flex"><span>${glyph.index}</span><span>${glyph.name}</span><span>${glyph_unicode}</span></div>`;					// glyph info
			glyph_path = glyph.getPath(0,100,72);
			glyph_SVG = 		'<svg xmlns=\'http://www.w3.org/2000/svg\' class="invert" x=\'0px\' y=\'0px\' viewBox=\'-40 20 140 140\' xml:space=\'preserve\' preserveAspectRatio=\'xMidYMid meet\'><g>'+ glyph_path.toSVG().replace(/"/g,'\'')  +'</g></svg>';
			glyph_container =	`<div id="glyph_container_${glyph.index}" class="glyph_container flex_justify_center background_grey_95 border_right_x border_bottom_x position_relative" data-id="glyph_container_${glyph.index}">${glyph_SVG}${glyph_info}</div>`; 																			// glyph container
			font_file_grid.insertAdjacentHTML('beforeend',glyph_container);																			// insert glyph into grid
		}
		font_file_grid.insertAdjacentHTML('beforeend',glyphs);																						// insert glyph into grid
		getFontInfo(font);																															// get font info
	}
	function getFontInfo(font) {																											// ===> GET FONT INFO
		let font_names = font.names, font_info_details = '';
		for ( let name in font_names ) {
			let value = font_names[name].en;
			if ( name.endsWith('URL') ) {
				let href = ( !value.startsWith('http') ? 'http://'+ value : value);  																// in case url without protocol is used
				value = '<a class="bold" href="'+ href +'" target="_blank">'+ value +'</a>';
			}
			font_info_details += `<li class="display_none"><span class="col_1 font_info_name align_right"> ${name}: </span><span class="font_info_value">${value}</span></li>`;
		}
		let num_glyphs = font.numGlyphs; 																											// number of glyphs in font
		let $font_info = `<ul id="font_info" class="info_list text_color_default background_grey_80 position_fixed display_none">
			<li class="info_list_header center bold"><span>FONT INFO:${font.names.fullName.en.toUpperCase()}</span></li>
			${font_info_details}
			<li class="display_none"><span class="col_1 font_info_name align_right">numGlyphs: </span><span class="font_info_value">${num_glyphs}</span></li>
		</ul>`;
		document.getElementById('font_file_viewer').insertAdjacentHTML('beforeend',$font_info);
	}
	function showFontGlyph(id) {																											// ===> SHOW INDIVIDUAL GLYPH
		switch(true) {
			case getContentPaneData() === 'has_font': {
				let this_glyph = document.getElementById(id), toolbar = document.getElementById('font_toolbar'), specimen_glyph = document.getElementById('specimen_glyph');
				removeClass('#font_specimen_grid .selected','selected');	document.getElementById(id).classList.add('selected');							// remove/add selected class to glyphs
				specimen_glyph.innerText = String.fromCodePoint(this_glyph.dataset.char);																		// add glyph to specimen glyph
				toolbar.dataset.char = 'Unicode Dec: '+this_glyph.dataset.char;	toolbar.dataset.unicode_hex = 'Unicode Hex: '+ this_glyph.dataset.unicode_hex;	// add data to toolbar for glyph info display
				break;
				}
			case ( /has_font_file|has_glyph/.test(getContentPaneData()) ): {
				addRemoveClassSiblings('#'+ id,'selected');
				let $glyph_viewer = document.getElementById('glyph_viewer');
				let glyph_width = document.getElementById(id).dataset.glyph_width, glyph_height = document.getElementById(id).dataset.glyph_height;
				let viewer_width, viewer_height = document.getElementById('content_font').offsetHeight;
				let	glyphID = id.slice(id.lastIndexOf('_') + 1);																							// convert id to number only
				let glyphs_data = document.getElementById('content_font').font_glyphs;																		// get glyphs from stored data
				let glyph = glyphs_data.glyphs[glyphID];																									// get glyph by id
				let glyph_path = glyph.getPath(0,viewer_height/14,72);																						// get glyph path
				let data_glyph_SVG = glyph_path.toSVG().replace(/"/g,'\'');																					// get glyph SVG data
				let glyph_scale = document.querySelector('#glyph_viewer').dataset.scale;																	// get current glyph scale factor
					glyph_scale = 'style="transform:scale('+ glyph_scale +')"';																				// format glyph scale style
				if ( document.querySelector('#glyph_container') !== null ) { document.querySelector('#glyph_container').remove(); }							// remove any existing svg element
				let SVG_prefix = `<div id="glyph_container" class="flex_justify_center"><div id="svg_container" class="flex_justify_center position_relative"><svg xmlns=\'http://www.w3.org/2000/svg\' x=\'0px\' y=\'0px\' viewBox=\'0 20 40 40\' xml:space=\'preserve\' preserveAspectRatio=\'xMidYMid meet\'><g ${glyph_scale}>`;
				$glyph_viewer.insertAdjacentHTML('beforeend',SVG_prefix + data_glyph_SVG +'</g></svg></div></div>');										// add the svg glyph to glyph_viewer
				$glyph_viewer.dataset.raw_svg = data_glyph_SVG;																								// store the raw svg data for saving svg to file
				$glyph_viewer.dataset.glyph_name = glyph.name;																								// store the glyph name for saving svg to file
				document.querySelector('#glyph_viewer_info div').innerText = glyphID +': '+ glyph.name +', #'+ glyph.unicode;								// add glyph info to glyph viewer title bar
				setAttribute('#content_pane','data-content','has_glyph');
				document.querySelector('#svg_container').onmousedown = function(e) { e.stopPropagation();
					let startX = e.pageX, startY = e.pageY, elOffsetLeft = document.querySelector('#svg_container').offsetLeft, elOffsetTop = document.querySelector('#svg_container').offsetTop;
					document.onmousemove = function(e) { mouseMove(e,'#svg_container',startX,startY,elOffsetLeft,elOffsetTop) }
				};
				break;
				}
			}
	}
	function saveGlyph() {																													// ===> SAVE GLYPH SVG
		let $glyph_viewer = document.getElementById('glyph_viewer');
		let file_name =  $glyph_viewer.dataset.font_name +'—'+ $glyph_viewer.dataset.glyph_name +'.svg';
		let data_prefix = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 120 120" xml:space="preserve" preserveAspectRatio=\'xMidYMin meet\'><g>';
		let data_suffix = '</g></svg>';
		let data = data_prefix +  document.getElementById('glyph_viewer').dataset.raw_svg + data_suffix;
		saveFile(data,'image/svg+xml',file_name);
	}
	//============================// GRIDS
	function showGrid(id) {																													// ===> SHOW GRID
		switch(true) {
			case !isTopWindow():																									// IFRAME GRIDS
				switch(true) {
					case hasClass('body','has_grid'): 	removeClass('body','has_grid'); 														break;
					default:							addClass('body','has_grid');	showImageThumbnails();
					}
				break;
			default:
				if ( id !== undefined ) { makeGrids(id); }																						// initial make grid items; otherwise, just unhide existing grid (see below)
				closeContent();
				let selected_ID;
				if ( document.querySelector('.directory_item.selected') !== null ) { selected_ID = document.querySelector('.directory_item.selected').id; }
				addClass('#show_grid','has_grid');
				setAttribute('#content_pane','data-content','has_grid');
				removeClass('#content_pane','has_hidden_grid');
				removeClass('#content_pane div','selected hovered');
				addClass('#content_pane div[data-id="'+ selected_ID +'"]','selected');
				focusContent();
		}
	}
	function hideGrid() { if ( getContentPaneData() === 'has_grid' ) { removeAttribute('#content_pane',['data-content']); addClass('#content_pane','has_hidden_grid'); } }	// ===> HIDE GRID
	function closeGrid() {																													// ===> CLOSE GRID (remove grid elements, etc.)
		removeClass('#show_grid','has_grid');
		if ( getContentPaneData() === 'has_grid' ) { removeAttribute('#content_pane',['data-content']); }
		removeClass('#content_pane','has_image_grid has_font_grid');
		removeAttribute('#content_grid',['style']);
		document.getElementById('content_grid').innerHTML = '';
		if ( document.querySelector('.directory_item.selected') !== null ) { showThis(document.querySelector('.directory_item.selected').id); }
	}
	function showGridItem(e,id,src,kind) { e.preventDefault();  																					// ===> SHOW GRID ITEM
		hideGrid();
		switch(true) {
			case document.getElementById(id) !== null:	addRemoveClassSiblings('#'+ id,'selected','selected');  document.getElementById(id).click();	break;	// normal grid item display
			case document.getElementById(id) === null:	showContent('', [src,kind]);																	break;	// show grid items from closed subdirectory
		}
	}
	// ***** IMAGE/FONT GRID SETUP
	function fontGridItems() {																												// ===> FONT GRID ITEMS
		let $font_grid_items = '', $font_files = document.querySelectorAll('.directory_item.font'), new_grid_item, font_files_length = $font_files.length;
		document.getElementById('font_grid_styles').innerHTML = '';
		for ( let i = 0; i < font_files_length; i++ ) {																								// for each font...
			new_grid_item = setContentFontSource( $font_files[i].id, true, 'font_grid','',i,font_files_length );									// make new font_grid_item
			$font_grid_items += new_grid_item;																										// add it to the font_grid_items
		}
		return $font_grid_items;																													// return font_grid_items
	}
	function imageGridItems() {																												// ===> IMAGE GRID ITEMS
		let $image_grid_items = '', $image_files = document.querySelectorAll('.directory_item.image:not(.ignored)'), classes = 'image_grid_item flex_justify_center border_right_x border_bottom_x', image_files_length = $image_files.length;
		for ( let i = 0; i < image_files_length; i++ ) {
			const id = $image_files[i].id;
			const this_link = $image_files[i].querySelector('a').href;
			const exts = $item_kind.image.filter( ext => !$row_settings.ignored.includes(ext) ); 													// decide which image files can be displayed
			const title_name = this_link.slice(this_link.lastIndexOf('/') + 1);
			if ( exts.includes($image_files[i].dataset.ext) ) { 																					// if item ext is in the image extension array...
				let item = `<div class="${ classes } background_grey_90" data-ID="${ id }" data-index="${ i }" data-kind="image"><a href="${ this_link }"><img src="${ this_link }" title="${ title_name }" loading="lazy" /></a></div>`;																																		// make new image_grid_item
				$image_grid_items += item;																											// ...add it to the image_grid_items
			}
		}
		return $image_grid_items;																													// return image_grid_items
	}
	function makeGrids(id) {																												// ===> MAKE GRIDS
		document.querySelector('#content_grid').innerHTML = ''; 																					// remove previous grid items
		removeClass('#content_pane','has_hidden_grid has_image_grid has_font_grid'); 																// reset content_pane grid classes
		switch(true) {																																// determine which grid type to make
			case id === 'show_font_grid': 	addClass('#content_pane','has_font_grid');	document.getElementById('content_grid').innerHTML = fontGridItems(); break; // only show font grid
			case id === 'show_image_grid': 	addClass('#content_pane','has_image_grid');	document.getElementById('content_grid').innerHTML = imageGridItems(); break; // only show image grid
			default: document.getElementById('content_grid').innerHTML = imageGridItems() + fontGridItems(); 										// show grid of both images and fonts
		}
		initGridItemEventListeners();																												// register event watchers for added grid elements
	}
	// ***** IMAGE/FONT/GLYPH SCALE
	const getFontSize = function(el) { return parseFloat(window.getComputedStyle(el).fontSize); };
	function scaleFonts(e, incr, id) {																										// ===> SCALE FONTS
		let $content_grid = document.getElementById('content_grid'), $content_font = document.getElementById('content_font');
		const font_size = parseInt(getComputedStyle(document.body).fontSize); 																		// pts/em
		if ( id === 'decrease' ) { incr = 1/incr; }
		if ( getContentPaneData() === 'has_grid' ) { $content_grid.style.fontSize = ( getFontSize($content_grid)/font_size * incr ) +'em'; return; }
		if ( getContentPaneData() === 'has_font' ) { $content_font.style.fontSize = ( getFontSize($content_font)/font_size * incr ) +'em'; return; }
		scrollThis('#content_container','#content_font');
	}
	function scaleGlyphs(e, incr, id) {																										// ===> SCALE GLYPHS
		if ( id === 'decrease' ) { incr = 1/incr; }
		let $glyph_viewer, current_scale;
		switch(true) {
			case document.getElementById('specimen_glyph').innerText !== '':
				document.getElementById('specimen_glyph').style.fontSize = ( getFontSize(document.getElementById('specimen_glyph')) * incr) +'px';		break;
			default:
				$glyph_viewer = document.getElementById('glyph_viewer'), current_scale = ( $glyph_viewer.dataset.scale === undefined ? incr : incr * $glyph_viewer.dataset.scale );
				$glyph_viewer.querySelector('svg g').style.transform = 'scale('+ current_scale * 100 +'%)';
				$glyph_viewer.dataset.scale = current_scale.toFixed(2);
			}
	}
	function scaleGridImages(incr,id) {																										// ===> SCALE IMAGE GRID ITEMS
		if ( id === 'decrease' ) { incr = 1/incr; }
		const $image_grid_item = document.querySelectorAll('.image_grid_item img');
		let image_grid_item_width = 		Number.parseFloat( $image_grid_item[0].offsetWidth,10) * incr;
		let image_grid_item_height =		Number.parseFloat( $image_grid_item[0].offsetHeight,10) * incr;
		let image_grid_item_max_width =		Number.parseFloat( $image_grid_item[0].style.maxWidth,10) * incr;
		let image_grid_item_max_height =	Number.parseFloat( $image_grid_item[0].style.maxHeight,10) * incr;
		// prevent reducing grid image size on first scale click:
		if ( image_grid_item_width < image_grid_item_max_width ) { image_grid_item_width = image_grid_item_max_width; }
		if ( image_grid_item_height < image_grid_item_max_height ) { image_grid_item_height = image_grid_item_max_height; }
		// set grid properties
		document.getElementById('content_grid').style.gridTemplateColumns = 'repeat(auto-fill, minmax('+ (image_grid_item_width + 16) +'px, auto ) )';
		Array.from($image_grid_item).forEach( function(el) { el.style.maxWidth = image_grid_item_width +'px'; el.style.maxHeight = image_grid_item_height +'px'; });
		return;
	}
	function scaleImages(e,incr,id) {																										// ===> ZOOM IMAGES ON CLICK
		const $content_container = ( document.getElementById('iframe_body') !== null && document.guerySelector('#iframe_body > img') !== null ? document.getElementById('iframe_body') : document.getElementById('content_container') );
		let $this_image = ( document.querySelector('#iframe_body > img') !== null ? document.querySelector('#iframe_body > img') : document.getElementById('content_image') );			// define $this_image
		let CC_width = function() { return Math.round($content_container.offsetWidth); },	CC_height = function() { return Math.round($content_container.offsetHeight); };				// content_container dimensions
		let this_width = Math.round($this_image.offsetWidth),								this_height = Math.round($this_image.offsetHeight);											// $this_image dimensions
		const iframe_delta =  ( document.querySelectorAll('#iframe_body > img').length === 1 ? Number.parseInt(document.getElementById('iframe_body').style.padding) : 0 );				//
		switch(true) {
			case getContentPaneData() === 'has_grid': scaleGridImages(incr,id); break;																// scale grid images
			default:															 																	// scale single images
				getImageDimensions( $this_image.src, function( width, height ) {
				switch(true) {
					case incr !== undefined && id !== undefined: 																					// scale images by increment
						addClass('#content_pane','has_scaled_image'); removeClass('#content_pane','has_zoom_image');								// remove zoom classes in case window resized after zoom
						switch(true) {
							case id === 'increase':												$this_image.style.cssText = `width:${this_width * incr}px; height:auto; max-width:none; max-height:none;`;	break;
							case id === 'decrease' && ( this_width >= 1 && this_height >= 1 ):	$this_image.style.cssText = `width:${this_width / incr}px; height:auto; max-width:none; max-height:none;`;	break;
						}
																																					// keep images centered when scaling
						if ( Math.round($this_image.offsetWidth) >= CC_width() ) { document.getElementById('content_image_container').scrollLeft = ( Math.round( $this_image.offsetWidth ) - CC_width() )/2; }
						if ( Math.round($this_image.offsetHeight) <= CC_height() ) {
							document.getElementById('content_image_container').scrollTop = ( CC_height() - Math.round( $this_image.offsetHeight ) )/2;
						} else {
							document.getElementById('content_image_container').scrollTop = ( Math.round($this_image.offsetHeight) - CC_height() )/2;
						}
						break;
					default: { 																														// else zoom single image on click
						$this_image.removeAttribute('style');
						if ( width <= CC_width() && height <= CC_height() ) { removeClass('#content_pane','has_zoom_image has_scaled_image'); return; } // don't zoom small images, else zoom image:
						const CC_offset = $content_container.getBoundingClientRect();
						const img_offset = $this_image.getBoundingClientRect();
						const percentX = (e.pageX - img_offset.left)/this_width; const percentY = (e.pageY - img_offset.top)/this_height;			// x,y coordinates of zoom click as percentage of image width/height
						const scrollX = (width * percentX) - e.pageX + CC_offset.left - (iframe_delta * width / this_width);						// calculate clicked x-coordinates for full-size image
						const scrollY = (height * percentY) - e.pageY + CC_offset.top - (iframe_delta * height / this_height);						// calculate clicked y-coordinate for full-size image
						removeClass('#content_pane','has_scaled_image'); 																			// in case image scaled already
						document.getElementById('content_pane').classList.toggle('has_zoom_image');
						document.getElementById('content_image_container').scrollTo(scrollX,scrollY);												// scroll to clicked position
					}
				}
			});
			setImageDimensions();																													// set image dimensions
		}
	}
	function scaleButtons(e,id) {																											// ===> SCALE BUTTONS
		e.preventDefault(); e.stopPropagation();
		switch(true) {
			case document.getElementById('specimen_glyph').innerText !== '':
			case getContentPaneData() === 'has_glyph': scaleGlyphs(e, 1.125, id);																	break;
			default: scaleImages(e, 1.125, id); scaleFonts(e, 1.125, id);	// scale glyphs or images and fonts
		}
		addClass('#reload_btn','reset');																											// add reset class to reload button
		if ( hasClass('body','focus_content') ) { focusContent(); }																					// focus content
	}
	//============================// SHOW TEXT EDITOR
	function showTextEditor() {																												// ===> SHOW TEXT EDITOR
		switch(true) {
			case getContentPaneData() === 'has_text_editor':	hideTextEditor(true);														break;	// hide open text editor
			case !hasClass('body','has_text_editor_UI'):		setUpTextEditorUI();																// no break; add the text editor UI if loading text editor for first time
			default: 																																// show editor
				document.getElementById('content_video').pause;
				removeClass('#content_pane','has_hidden_text_editor');
				if ( getContentPaneData() === 'has_font_file' ) { addClass('#content_pane','has_hidden_font_file'); }
				setAttribute('#content_pane','data-content','has_text_editor'); 																	// empty title
				addClass('body','has_text_editor');
				setContentTitle();
				focusContent();
		}
	}
	function hideTextEditor(bool) {																											// ===> HIDE TEXT EDITOR
		if ( getContentPaneData() === 'has_text_editor' ) {
			removeClass('body','has_text_editor');
			removeAttribute('#content_pane',['data-content']);
			switch(true) {
				case hasClass('#content_pane','has_hidden_font_file' ): removeClass('#content_pane','has_hidden_font_file'); setAttribute('#content_pane','data-content','has_font_file'); break;
				case bool === true: addClass('#content_pane','has_hidden_text_editor'); break;
				case document.querySelector('.directory_item.not_audio.selected') !== null:
					showThis( document.querySelector('.directory_item.selected').id );
				default: document.querySelector('#content_pane').dataset.content = 'has_null';
			}
			focusSidebar();
		}
	}
	//============================// PDF
	function setupContentPDF() {																											// ===> SET UP CONTENT_PDF
		let $embed = '<embed id="content_pdf" class="content" tabindex="0" data-kind="pdf">';														// define new content_pdf
		document.getElementById('content_pdf').remove(); 																							// remove existing content_pdf el
		document.getElementById('content_image_container').insertAdjacentHTML('afterend',$embed); 													// add new content_pdf el
	}
	//============================// LINK FILES
	function openLinkFile(e) {																													// ===> OPEN LINK FILES on dblclick (webloc, url)
		e.preventDefault(); e.stopPropagation();
		let link = document.querySelector('.directory_item.selected').dataset.link_file_link;	const regex = /URL\=(.+?)$|<key>URL<\/key>\s*<string\>(.+?)<\/string\>/im;
		// regex for finding link
		let url = ( link.match(regex)[1] || link.match(regex)[2] );
		switch(true) {
			case url === undefined:																										break;
			case $protocol === 'file:' && url.startsWith('file'): 																					// if local link files with local link location...
				switch(true) {																														// open link files clicked in iframes in iframe
					case !isTopWindow(): 	window.location = url;															break;
					default: showContent('open_link_file',[url,'link']);																				// open local link files to local items in content_iframe
				}
				break;
			default:	window.open(url);																											// else open link file links in new window
		}
	}
	//============================// IFRAME CONTENT
	function showIframeContent(args) {																										// ===> SHOW IFRAME CONTENT called when iframe loads
		let link = ( /inetloc|url|webloc|view_directory_source/.test(args[0]) ? getAttribute('#content_iframe','src') : getAttribute('#content_iframe','data-content_name') );
		let kind = getAttribute('#content_iframe','data-kind');
		switch(true) {																																// set content_pane.dataset.content
			case ( /app|dir/.test(kind) ):			setAttribute('#content_pane','data-content','has_dir'); 		break;							//
			default: 								setAttribute('#content_pane','data-content','has_'+ kind);
		}
		if ( document.querySelector('.directory_item.content_loaded.htm') !== null ) { document.querySelector('.directory_item.htm.selected').dataset.html_content = args[1]; }
		if ( !/subdirectory=true/.test(args[0]) ) {	setAttribute('#content_pane','data-loaded','loaded');	setContentTitle(link,kind); }			// show iframe, hide loading spinner, set title
		if ( hasClass('body','focus_content') )   { focusContent(); }																				// focus content
	}
	//============================// DIRECTORY SOURCE
	function showDirectorySource() {																										// ===> SHOW DIRECTORY SOURCE
		switch(true) {
				case hasClass('body','has_directory_source'):				document.querySelector('#close_btn').click(); break; 					// close if open
			default: addClass('body','has_directory_source');				showContent('view_directory_source',[current_location]);				// show directory source
		}
	}
	function openSidebarInContentPane() { showContent('open_sidebar_in_content_pane',[current_location,'dir']); focusContent(); }			// ===> OPEN SIDEBAR IN CONTENT PANE
	function openInTextEditor() {																											// ===> OPEN IN TEXT EDITOR
		let scroll_script = '<script id="scroll_script">window.onscroll = function(){ window.parent.postMessage( { "messageContent":"scroll_iframe","functionName":"","arguments":window.scrollY },"*"); }</script><style>body{margin:0;padding:0;}</style>';
		let html = document.querySelector('.directory_item.htm.content_loaded').dataset.html_content;
		showTextEditor();
		// showWarning(); // if texteditor edited, show warning
		if ( html !== undefined ) {  																												// set previewed text
			document.getElementById('text_editor_raw_pane').value = html;
			setAttribute('#text_editor_preview_pane','srcdoc',scroll_script + html);
			document.getElementById('text_editor_html_pane').value = html;
		}
	}
	// END SHOW INDIVIDUAL CONTENT TYPES
	//============================//
	// ***** MAIN SHOW CONTENT FUNCTIONS ***** //
	function setContentClasses(id, kind, content_el) {																						// ===> SET CONTENT PANE CLASSES
		switch(true) { 																																// add content loaded classes
			case id === '':																											break;
			case kind === 'audio':	removeClass('.audio:not(#'+ id +')','audio_loaded selected');	addClass('#'+ id,'selected');	break;			// audio classes
			default:				addRemoveClassSiblings('#'+ id,'content_loaded'); 																// non-audio items
		}
		switch(true) {																																// set content_pane classes
			case kind === 'audio':																													// audio
				switch(true) {
					case id === 'content_iframe_file':	addClass('#content_pane','has_iframe_audio');	break;										// iframe_audio
					default: 							addClass('#content_pane','has_audio');														// sidebar audio
				}
				break;
			case ( /app|dir/.test(kind) ):				setAttribute('#content_pane','data-content','has_dir');							 			// apps and directories
				switch(true) {
					case id === 'content_iframe_file':	addClass('#content_pane','has_iframe_dir'); 	break;										// iframe_dir
					default: 							addClass('#content_pane','has_dir'); removeClass('#content_pane','has_iframe_dir has_iframe_file');	// sidebar dir
				}
				break;
			default:			 						setAttribute('#content_pane','data-content','has_'+ kind);									// other content
				switch(true) {
					case id === 'content_iframe_file':	addClass('#content_pane','has_iframe_file'); removeClass('#content_pane','has_dir'); break;
					default:							removeClass('#content_pane','has_dir has_iframe_dir has_iframe_file');
				}
		}
		addClass('#content_'+ content_el,'has_content');
	}
	// LINKS, SEARCH PARAMS, AND QUERIES
	function makeContentLinkSearchParams(kind) {																							// ===> GET LINK QUERIES
		let query_str, params;
		switch(true) {
			case kind === 'pdf':	query_str = '#view=fitB&scrollbar=1&toolbar=1&navpanes=1';												break;	// compose query_str for pdfs
			case ( /text|markdown|code/.test(kind) ): 																								// editable text files
				params = ['text_editor_split_view','theme','text_editor_sync_scroll'];																// define array of required params
				query_str = '?'+ makeSearchParams(params).toString() +"&text_editor_theme="+ ( getSearchParam('text_editor_theme') === 'default' ? getSearchParam('theme') : getSearchParam('text_editor_theme') === 'light' ? 'light' : 'dark') + "&enable_text_editing="+ ( hasClass('body','enable_text_editing') ? 'true' : 'false' ) +"&text_editor_default_view="+ getSearchParam('text_editor_default_view');													break;	// compose query_str
			case ( /app|dir/.test(kind) ): 																											// apps and directories
				params = ['sort_by','sort_direction','show_details','show_image_thumbnails','show_numbers','use_custom_icons','show_invisibles','hide_ignored_items','ignore_ignored_items','alternate_background','theme','play_all_media'];	// define array of params
				query_str = '?'+ makeSearchParams(params).toString();																		break;	// compose query_str
		}
		return query_str;																															// return query_str
	}
	function setContentSources(id, kind, link, content_el) {																				// ===> SET CONTENT SOURCES
		if ( kind === 'font' ) { setContentFontSource(id,false,'',link); }																			// make source link for content elements; default is link
		document.getElementById('content_'+ content_el).setAttribute('src',link); 																	// set source for content element
	}
	function setContentTitle(link) { //*** kind = dir or files ***//																		// ===> SET CONTENT TITLE
		let title_text = '';
		let $selected = document.querySelector('.directory_item.selected'), selected_link, selected_title, content_link;
			selected_link =		( $selected !== null ? decodeURIComponentSafe( $selected.querySelector('a').pathname ).trim() : '' );				// selected sidebar item; link = pathname
			selected_title =	( $selected !== null ? $selected.querySelector('.directory_item_name_a').innerText : '' ); 							// title from stored data
		let content_link_info = getLinkInfo(link); 													// actual loaded content src; if iframe, link is from iframe data-content_name = full path of current item w/o queries
			content_link =		( content_link_info !== undefined ? decodeURIComponentSafe( content_link_info[0] ).trim() : '' );
		switch(true) {
			case selected_link !== content_link: 	title_text = content_link; 	break;																// unsynced iframe items
			default: 								title_text = selected_title; 																	// synced iframe items
		}
		title_text = title_text.split('/').join('/<wbr>').split('_').join('_<wbr>'); 																// allow nice line breaks in title
		document.querySelector('#content_title span').innerHTML = title_text; 																		// set title text
	}
	//============================// SUBDIRECTORIES
	function openSubdirectory(parent_id) { 																									// ===> OPEN SUDIRECTORY
		let $parent = document.getElementById(parent_id), parent_link = $parent.getElementsByTagName('a')[0].href, level = Number($parent.getAttribute('data-level')) + 1, body_id = document.body.id;	// define subdir level
		let content_iframe_utility_src = parent_link + makeContentLinkSearchParams('dir') + '&parent_id='+ parent_id +'&subdirectory=true&level='+ level +'&body_id='+ body_id;		// assemble src link for utility_iframe
		setAttribute('#content_iframe_utility','src',content_iframe_utility_src);																	// set src for utility_iframe (which processes dir & sends it back to top)
		$parent.classList.add('dir_list_subdir_loading'); 																							// removed when iframe_utility sends loaded message with subdir data
	}
	function closeSubdirectory(sub_dir_id) {																								// ===> CLOSE SUBDIRECTOY
		switch(true) {
			case sub_dir_id === undefined: 																											// close all subdirectories
				if ( document.querySelectorAll('.directory_item.has_subdirectory') !== null ) { closeContent(); }									// if subdir exists, closeContent
				removeClass('.directory_item.dir','has_subdirectory dir_list_subdir_loading');														// remove has_subdir and subdir_loading classes
				document.querySelectorAll('.directory_item[id*="_"]').forEach( el => el.remove() ); 													// remove any item whose id contains an underscore (= subdirectory row)
				break;
			default:
				document.getElementById(sub_dir_id).classList.remove('has_subdirectory');															// remove has_subdir class
				document.querySelectorAll('.directory_item').forEach(function(el) { if ( el.id.startsWith(sub_dir_id + '_') ) { el.remove(); } });	// remove any row whose id begins with subdirectory parent id
		}
		let classes = {font:'has_fonts',image:'has_images',media:'has_media'};
		for ( const key in classes ) { if ( document.querySelector('#directory_list li.'+key) === null ) { removeClass('body',classes[key]); } }	// remove subdir only body classes
		updateStats();
	}
	function openCloseSubdirectory(e,id) {																									// ===> OPEN CLOSE SUBDIRECTORY
		e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
		let $parent = document.getElementById(id);
		removeClass('body','has_stats');
		switch(true) {
			case ( $parent.querySelector('.name') !== null && /\.trashes|\.temporaryitems|\.spotlight-v\d+/.test($parent.querySelector('.name').dataset.name ) ):
				$parent.classList.remove('dir_list_subdir_loading'); break;
			default:
				selectThis(id);
				if ( $parent.classList.contains('has_subdirectory') ) { closeSubdirectory( id ); } else { openSubdirectory( id ); }
				if ( isTopWindow() ) 	{ focusSidebar(); } else { document.activeElement.blur(); document.getElementsByTagName('body')[0].focus(); }
		}
	}
	//============================//
	// ***** IFRAME CONTENT ***** //
	function setUpIframeDirectoryUI(link,agent) { 																							// ===> SET UP IFRAME DIR LIST UI, with utility iframe for subdirectories
			link = link.split('/').reverse(); let removed = link.splice(1,1); removed.pop();
		let parent_link = link.reverse().join('/'), query_str = new URLSearchParams(window.location.search.toString().slice(1)), subdirectory = query_str.get('subdirectory') || null, body_id = query_str.get('body_id');
		window.addEventListener('message',receiveMessage,false);																					// init receive message event listener
		switch(true) {
			case ( query_str.get('view_directory_source') || query_str.get('is_error') ) === 'true':										break;	// do nothing when viewing directory source or if error page...
			default: {																																// ...else set up iframe directory:
				let iframe_directory, iframe_head = document.getElementsByTagName('head')[0],  iframe_body = document.getElementsByTagName('body')[0], iframe_dir_list = '', gecko_styles = '';
				let iframe_utility_iframe = '<iframe id="content_iframe_utility" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" style="display:none;"></iframe>';
				let body_classes = ['show_details']; 																								// define body classes
				for ( let key of query_str.keys() ) {																								// add various body_classes...
					switch(true) {
						case ( /\s/g.test( getSearchParam(key)) ): 																			break;	// ignore search params with spaces in them (e.g., from "history" "+" signs)
						case key === 'sort_direction': 			body_classes.push(getSearchParam(key));										break;	// add sort_direction
						case query_str.get(key) === 'true':		body_classes.push(key);														break;	// add body classes for boolean params
						case query_str.get(key) !== 'false':	body_classes.push(key+'_'+getSearchParam(key));								break;	// non-boolean params (theme, sort, sort_direction)
					}
				}
				if ( agent === 'is_gecko' ) { gecko_styles = ('<style id="gecko_style_rules">'+ gecko_style_rules +'</style>'); }
				iframe_head.querySelectorAll('style, link[rel="stylesheet"], link[href$="css"]').forEach( el => el.remove() ); 					// remove any existing directory index styles
				iframe_head.insertAdjacentHTML('beforeend',`<style id="iframe_dir_styles">${iframe_dir_styles }</style><style id="sidebar_styles">${ sidebar_styles }</style>
					<style id="directory_nav_styles">${ directory_nav_styles }</style><style id="color_and_background_styles">${color_and_background_styles}</style>${gecko_styles}`);	// assemble the iframe head
				const new_index = makeNewIndex(iframe_body, query_str.get('sort_by'),'',body_id); 											// make new index
				const make_new_index = new_index[0];
				const prepped_index = make_new_index[0];											 												// define prepped index
				let additional_classes = (new_index[0][1].trim().split(/\s+/)).concat(body_classes);											 	// define additional body classes
				switch(true) {																														// Assemble content_iframe and utility_iframe content
					case subdirectory === 'true':	iframe_dir_list = `<div id="directory_list_outer"><ol id="directory_list" class="border_bottom text_color_default">${ prepped_index }</ol></div>`;	break;
					default:
						iframe_directory = Directory_Elements('iframe',parent_link);																// create iframe directory elements
						iframe_dir_list = iframe_directory.replace(/insert_prepped_index/,prepped_index).replace(/insert_stats/,make_new_index[2]);	// assemble iframe directory
						iframe_body.removeAttribute('style');																						// remove any body inline styles
						iframe_body.classList.add(...additional_classes);																			// add body styles
				}
				iframe_body.innerHTML = iframe_dir_list;																							// append iframe_dir_list to iframe
				if ( subdirectory === null ) { iframe_body.insertAdjacentHTML('beforeend',iframe_utility_iframe); initIframeEventListeners(); } 	// don't add utility_iframe to utility_iframe; init iframe event listeners
				if ( subdirectory === 'true' ) { sendMessage('top','dir_list_subdir_loaded','',[document.getElementById('directory_list').innerHTML,make_new_index[1]] ); }	// send prepped subdir to parent window
			}
		}
	}
	function setUpIframeUI(agent) { 																										// ===> SET UP IFRAME UI for dir lists, text files, cuetxt, link files, html files
		let message = 'iframe_loaded', iframe_location = decodeURIComponentSafe(window.location.href), content = '';
		setAttribute('body','id','iframe_body'); 																									// add iframe body id
		let text_files = $item_kind.markdown.concat($item_kind.text, $item_kind.code), link_files = new RegExp('.'+ $item_kind.link.join('$|.') +'\$','m');	// define file types
		switch(true) {																																// determine UI type
			case window.location.pathname.indexOf('/?') > 0: case window.location.pathname.endsWith('/'): setUpIframeDirectoryUI(iframe_location,agent);	break;	// if dir, set up iframe dir list UI
			case text_files.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ):	setUpTextEditorUI();					// if text file, set up iframe text editor
				if ( iframe_location.endsWith('.cuetxt') ) {				content = document.getElementById('text_editor_raw_pane').value; }				break;	// if .cuetxt file, get text for processing
			case ( /\.html|\.htm$/m.test(iframe_location) ):	initIframeHtmlFileEventListeners();	content = document.querySelector('html').outerHTML;		break;	// html files
			case ( link_files.test(iframe_location) ):	content = document.body.innerText; message = 'link_file_link'; 										break; 	// send link file content to top
			default: initIframeHtmlFileEventListeners();																									break;	// all other iframe content (e.g. html)
		}
		sendMessage('top',message,'',[iframe_location,content]);																					// send iframe_loaded message (unless link file), with args
	}
	// IFRAME CLICK AND SELECT FUNCITONS
	function iframeSelectItem(row_id) {																										// ===> IFRAME SELECT ROW
		if ( document.querySelector('.directory_item.selected') !== null ) { removeClass('.directory_item.selected','selected'); }
		addClass('#'+ row_id,'selected');
		if ( document.getElementById(row_id).classList.contains('audio_loaded') ) { sendMessage('top','iframe_play_pause_media'); } 				// play/pause media
	}
	function iframeDirectoryClick(e,id) {																										// ===> IFRAME DIR LIST CLICK
		e.preventDefault(); e.stopImmediatePropagation();
		removeClass('body','is_blurred');
		removeClass('#iframe_body','has_stats');
		iframeSelectItem(id);
		sendMessage('top','iframe_click');
	}
	function iframeClickLink(e,link,id) {																									// ===> IFRAME CLICK LINKS from html files (combine with iframedoubleClickThis()?)
		let url, kind;
        if ( !link.startsWith('#') ) { e.preventDefault(); url = newURL(link); } 																	// if link is not a link fragment
  		switch(true) {
			case link.startsWith('#'):	case url.href.startsWith('file:///?'):	case url === undefined:	 									break;	// allow default link fragment behavior
			case id === 'tbody': 		e.preventDefault();	window.location = link + '?&view_directory_source=true';						break;	//
			case id === 'iframe_parent_link': sendMessage('top','show_iframe_parent','',[document.getElementById('iframe_parent_link').href,'dir','iframe_parent']);	break; // send message "show_iframe_parent"
			case url.protocol === 'file:' && window.location.protocol !== 'file:':	sendMessage('top','local_link');						break;	// show warning when attempting to open local links from non-local pages
			case url.protocol !== 'file:' && window.location.protocol === 'file:':	window.open(link,'_blank');								break;	// open remote link from local page in new tab/window
			case url.protocol === 'file:' && window.location.protocol === 'file:':																	// nobreak; open local links on local files in iframe
			case url.protocol === 'about:':										 																	// nobreak; document #link fragments
			case RegExp(url.hostname).test(window.location.hostname): 																				// nobreak; same origin links (might not include TLD) (just covering bases)
			case RegExp(window.location.hostname).test(url.hostname): 																				// no break; same origin links (might not include TLD) (just covering bases)
				kind = getLinkInfo(url.href)[3];
 				if ( /dir|app/.test(kind) ) { sendMessage('top','show_iframe_dir','',[link,kind] ); } else { sendMessage('top','show_iframe_file','',[link,kind] ); }	break;
			default: window.open(link,'_blank'); 																							break;	// else open external document links in new tab
		}
	}
	function iframedoubleClickThis(e,row_id,href) {																	// ===> IFRAME DOUBLECLICK THIS iframe dir_list items (files and dirs)
		e.preventDefault(); e.stopPropagation();
		let kind = document.getElementById(row_id).getAttribute('data-kind'); 											// get item kind
		if ( kind === 'audio' ) {
			removeClass('.directory_item.audio','audio_loaded selected');
			document.getElementById(row_id).classList.add('audio_loaded','selected');									// iframe audio
		}
		switch(true) {
			case ( /dir|app/.test(kind) ):	sendMessage('top','show_iframe_dir','',[href,kind]); break;					// iframe dirs: tell top what to open; ignore ignored files when dblclicked
			default: 						sendMessage('top','show_iframe_file','',[href,kind]);						// iframe files: tell top what to open; ignore ignored files when dblclicked
		}
	}
	//============================//
	// SHOW CONTENT
	function showContent(id, args) { // args = [link,kind]																					// ===> SHOW CONTENT
		let row, link_info, link, kind;
		if ( getAttribute('#content_pane',['data-loaded']) === 'unloaded' ) { removeAttribute('#content_pane',['data-loaded']); }
		switch(true) { 																																// set vars & content_iframe attributes for iframe content or sidebar content
			case ( /show_grid|text_editor|show_image_grid|show_font_grid/.test(id) ): 														break;	// ignore these ids (i.e, don't get links)
			case id === 'view_directory_source': 																							break;	// directory source
			case ( /content_iframe_parent|content_iframe_dir|open_sidebar_in_content_pane|open_link_file/.test(id) ):								// prep for iframe dirs
				addClass('body','focus_content');
				link_info = getLinkInfo(args[0]);																									// get link info
				link = link_info[0];	link += makeContentLinkSearchParams('dir');		kind = link_info[3]; 										// set links for default (chrome) and gecko browsers
				addClass('#content_pane','has_iframe_dir'); removeClass('#content_pane','has_iframe_file');
				removeAttribute('#content_iframe',['data-iframe_file_src']); 																		// because it's not needed after opening a dir
				focusContent();																												break;
			case id === 'content_iframe_file': 																										// prep for iframe files setContentClasses
				addClass('body','focus_content');
				link_info = getLinkInfo(args[0]);  link = link_info[0];  kind = link_info[3]; 														// set links for default (chrome) and gecko browsers
				switch(true) {
					case kind === 'audio': addClass('#content_pane','has_iframe_audio');													break;
					default:	addClass('#content_pane','has_iframe_file');  setAttribute('#content_iframe','data-iframe_file_src',getAttribute('#content_iframe','src')); // remember iframe file parent directory
				}																															break;
			case id === '': link = args[0]; kind = args[1];																					break;	// id = '' typically when grid items remain after closing subdirectory
			case id !== '': 																														// show content from sidebar or iframe items (from above) and get link, kind
				row = document.getElementById(id); 																									// link = getFormattedLink(row.guerySelector('a').href,row.dataset.kind);
				link_info = getLinkInfo( getAttribute('#'+ id +' a','href') );
				link = link_info[0];	kind = link_info[3]; 																						// set links for default (chrome) and gecko browsers
				switch(true) {
					case kind === 'dir': case kind === 'app': case ( /code|text|markdown/.test(kind) ): 											// add link queries for dirs and text
						link = link + makeContentLinkSearchParams(kind); 																	break;
					case ( /text|markdown|code/.test(kind) ):		// sendMessage('iframe','get_text_selection','',[row.attr('data-selection_start'),row.attr('data-selection_end')]);
																																			break;
				}
																																			break;
		}
		let content_el = ( ['audio','font','image','pdf','video'].includes(kind) ? kind : 'iframe' );
		switch(true) {
			case !isTopWindow():
				if ( id === 'show_grid' ) { showGrid(); }																					break;	// don't attempt to show content for iframe items when navigating
			case row !== undefined && hasClass('#'+ id,'ignored') && getSearchParam('ignore_ignored_items') === 'false':							// try to open ignored files if they are not to be ignored
				window.location = row.querySelector('a').href;																				break;
			case row !== undefined && hasClass('#'+ id,'ignored') && getSearchParam('ignore_ignored_items') === 'true': 							// don't open ignored files if they are to be ignored
				closeContent();		setContentTitle( getAttribute('.directory_item.selected a','href'));	setAttribute('#content_pane','data-content','has_ignored');
																																			break;
			case kind === 'link':
				closeContent();
				addClass('#content_iframe','has_content');
				setAttribute('#content_pane','data-loaded','loaded');
				setAttribute('#content_iframe','src',link);
				setAttribute('#content_iframe','data-kind',kind);
				break;
			case ( /show_grid|show_image_grid|show_font_grid/.test(id) ):	showGrid(id);													break;	// show grid
			case id === 'text_editor' || id === 'show_text_editor':			showTextEditor();												break;	// show the text editor
			case kind === 'audio': 											showAudio(id,link); 											break;	// show audio
			case kind === 'video':											showVideo(id,link);														// no break; close audio when showing video, test if needed
			case ['font','image','pdf','video'].includes(kind):
				closeContent();										 																				// Close all open content
				setContentSources(id, kind, link, content_el); 																						// Set the src attribute for the content element
				setContentClasses(id, kind, content_el); 																							// Add "data=has_<kind>" to content_pane; test id for content_iframe
				setContentTitle( getAttribute('#content_container .has_content:not(#content_iframe)','src')); 										// no break; Set the title for non-iframe content
				if ( kind === 'font' ) { initFontPreviewEventListeners(); }																			// init font preview event listeners
				if ( kind === 'image' ) { setImageDimensions(); }										 									break;	// set image dimensions
			case id === 'view_directory_source': kind = 'view_directory_source'; link = current_location + '?&view_directory_source=true'; 			// nobreak;
			case kind === 'dir' && id !== 'content_iframe_dir': 																					// nobreak; dirs
			case kind === 'app' && id !== 'content_iframe_dir': 																					// nobreak; apps
			case content_el === 'iframe': 																											// set iframe source and hide until iframe loaded
				closeContent(); 																													// Close all open content
				addClass('#content_iframe','has_content');
				setAttribute('#content_pane','data-loaded','unloaded');																				// hide iframe until loaded, show loading spinner
				setAttribute('#content_iframe','src',link);
				setAttribute('#content_iframe','data-kind',kind);																					// set iframe source, data-kind
				if ( id !== 'view_directory_source' ) { setAttribute('#content_iframe','data-content_name',link_info[0]); }
																									break;
		}
		if ( kind !== 'audio' && id !== '' ) { addRemoveClassSiblings('#'+ id,'content_loaded','content_loaded'); }												// don't remove content_loaded class for siblings if kind = audio
		if ( isTopWindow() && ( hasClass('body','focus_content') || id === 'content_iframe' ) ) { sendMessage('iframe','focus_iframe'); }
	}
	//============================// AUTOLOAD CONTENT
	function autoLoadFile() {																												// ===> AUTOLOAD FILE: index files or files from the file shortcut list
		let $selected = document.querySelector('.directory_item.dir[id="rowid-'+ Number(getSearchParam("selected")) +'"]');
		if ( $selected !== null && isTopWindow() && getSearchParams().has('selected') ) {
			$selected.classList.add('selected');																									// add selected class
		}																									// remove selected searchParam if item not found
		switch(true) {
			case $selected !== null && $selected.classList.contains('local'):	case !isTopWindow():												// do nothing for local files or iframes
				break;
			case getSearchParams().has('file'): { 																									// load files (from bookmark or url)
					let file_name = decodeURIComponentSafe(getSearchParam('file'));
					let $file = Array.from(document.querySelectorAll('.directory_item.file')).filter ( el => el.dataset.name === file_name );
					if ( $file[0] !== undefined ) { showThis( $file[0].id ); }
					removeSearchParam('file');
				}
				break;
			case hasClass('body','has_audio') && getSearchParam('autoload_media') === 'true': 														// audio; must be above next case
				document.querySelector('.directory_item.audio').classList.add('selected');															// add selected class
				if ( document.querySelector('.directory_item.audio') !== null ) { showAudio( document.querySelector('.directory_item.audio').id ); }	// load audio
				autoLoadCoverArt(); 																												// load cover mage
				break;
			case hasClass('body','has_video') && getSearchParam('autoload_media') === 'true':														// video
				document.querySelector('.directory_item.video').classList.add('selected');															// add selected class
				if ( document.querySelector('.directory_item.video') !== null ) { showThis( document.querySelector('.directory_item.video').id ); }	break;		// load video (if no audio)
			case hasClass('body','has_playlist'): case hasClass('body','has_filelist'):		break; 													// do nothing for playlists and filelists ... but what about autoload media?
			case $selected !== null: showContent( $selected.id ); 							break;
		}
		if ( $selected !== null ) { showThis( $selected.id); }																						// click selected history item --> replaces auto-loaded cover art
		if ( getSearchParam('autoload_index_files') !== 'false' && document.querySelector('.directory_item.file.htm a[href*="/index."]') !== null ) { // else load index file
			showThis( document.querySelector('.directory_item.file.htm a[href*="/index."]').closest('.directory_item').id );
		}
		if ( $selected !== null ) { scrollThis('#directory_list','.selected'); }
	}
	function getImageNames() {																												// ===> GET IMAGE NAMES (for cover art)
		let images = document.querySelectorAll('.directory_item.image'), image_names = [], image_name;
		for ( let i = 0; i < images.length; i++ ) {
			image_name = images[i].dataset.name;
			image_name = image_name.slice(0,image_name.lastIndexOf('.') ); 																			// remove extension
			image_names.push( {'id':images[i].id,'name':image_name} ); 																				// add id and name
		}
		return image_names;
	}
	function getCoverArtID() {																												// ===> GET COVER ART ID
		const cover_names = ['cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes'];
		const image_names = getImageNames();
		let id, exact_match, match, cover_name;
		for ( cover_name of cover_names ) { 																										// test available image names against cover names
			exact_match = image_names.filter( el => el.name === cover_name ); 																		// check for exact match (w/o extension)
			match = image_names.filter( el => el.name.indexOf(cover_name) > -1 ); 																	// check for partial match
			if ( exact_match.length > 0 ) { return id = exact_match[0].id; }
			else if ( match.length > 0 ) { return id = match[0].id; }
		}
		if (id === undefined ) { return id = image_names[0].id; } 																					// if no matches, return first image id
	}
	function autoLoadCoverArt() { 																											// ===> AUTOLOAD COVER ART if dir contains audio & images
		if ( !isTopWindow() || !hasClass('body','has_images') || !hasClass('body','has_audio') ) { return; } 							// do nothing if no audio or images found
		let cover_ID = getCoverArtID();
		if ( cover_ID !== undefined ) {
			addClass('#'+ cover_ID,'content_loaded');
			setAttribute('#content_pane','data-content','has_image');
			addClass('#content_image','has_content');
			setAttribute( '#content_image','src',getAttribute('#'+ cover_ID +' a','href') );
			document.querySelector('#content_title span').innerHTML = decodeURIComponentSafe( document.getElementById(cover_ID).querySelector('a').innerText ); // name
			setImageDimensions();
		}
		let selected_ID = ( getSearchParam('selected').length > 0 ? 'rowid-'+ getSearchParam('selected') : undefined);
		if ( selected_ID !== undefined ) { removeClass('.directory_item.media','selected'); document.getElementById(selected_ID).classList.add('selected'); }

	}
	//============================//
	// ===> CLOSE CONTENT (Close button or Cmd/Ctrl + W)
	function closeMedia(kind) { // type === audio || video																					// ===> CLOSE MEDIA
		let $media_el = ( kind === 'audio' ? document.getElementById('audio') : document.getElementById('content_video') );
			$media_el.pause();
			$media_el.removeAttribute('src');
		switch(true) {
			case kind === 'audio':
				document.querySelector('#content_audio_title span').innerHTML = '';
				removeClass('body','is_playing is_paused');
				removeClass('.directory_item.audio_loaded','audio_loaded');
				removeClass('#content_pane','has_audio has_iframe_audio');
				removeClass('#content_audio_playlist','has_content');
				sendMessage('iframe','close_iframe_audio');
				break;
			case kind === 'video' && getContentPaneData() === 'has_video':
				removeClass('.directory_item.video.content_loaded','content_loaded');
				removeClass('#content_video','has_content');
				removeAttribute('#content_pane',['data-content']);
				break;
		}
	}
	function closePlaylist() {																												// ===> CLOSE PLAYLIST
		closeContent();	closeMedia('audio');
		removeClass('body');																														// remove all body classes
		addClass('body',document.getElementById('directory_list').dataset.data_classes);			 											// restore orignal body classes
		document.querySelector('head title').innerText = 'Index of: '+ current_location; 															// restore window title
		document.querySelector('#current_dir_path span').innerHTML = current_dir_path;																// restore current_dir_path tilte
		document.getElementById('directory_list').innerHTML = document.getElementById('directory_list').dataset.dir_list;					// restore original dir_list
		if ( document.querySelector('.directory_item.selected') !== null ) { showThis(document.querySelector('.directory_item.selected').id); }		// reopen the selected dirlist item
		updateStats();																																// update stats
		initEventListeners()																														// reinit event listeners
	}
	function closeIndexSource() {																											// ===> CLOSE INDEX SOURCE PREVIEW
		removeClass('body','has_directory_source');
		removeAttribute('#content_pane',['data-content','data-loaded']);
		removeAttribute('#content_iframe',['src']);
		removeClass('#content_iframe','has_content'); 																								// remove iframe src
		document.querySelector('#content_title span').innerHTML = ''; 																				// empty content title
		if ( document.querySelector('.directory_item.content_loaded') !== null ) { showThis( document.querySelector('.directory_item.content_loaded').id ); }
		focusSidebar();
	}
	function closeFontFile() {																												// ===> CLOSE OPENED FONT FILE
		document.body.classList.remove('faded','focus_content');
		removeAttribute('#content_pane',['data-content']);
		removeClass('#content_font','has_content');
		document.getElementById('font_file_grid').innerHTML = '';
		document.querySelector('#content_title span').innerHTML = '';
		document.getElementById('font_file_viewer').remove();
		document.getElementById('content_font').font_glyphs = null; 																				// remove glyphs data from $content_font
		document.getElementById('content_font').insertAdjacentHTML( 'beforeend', Content_Font_Viewer );
		if (document.querySelector('.directory_item.content_loaded') !== null ) { showThis(document.querySelector('.directory_item.content_loaded').id); }
	}
	function closeGlyph(type) {																													// ===> CLOSE GLYPH
		switch(true) {
			case type === 'specimen':
				removeAttribute('#font_toolbar',['data-char','data-unicode_hex']);
				document.getElementById('specimen_glyph').innerText = '';
				break;
			default:
				document.querySelector('#glyph_viewer').dataset.scale = '1';
				document.querySelector('#glyph_viewer svg path').style = '';
				setAttribute('#content_pane','data-content','has_font_file');
				addClass('#content_font','has_content');
				scrollThis('#font_file_grid','.selected');
				document.getElementById('glyph_viewer');
		}
	}
	function closeContent() { // Close all .content elements before opening any new .content from sidebar.									// ===> CLOSE CONTENT
		switch(true) {
			case !isTopWindow(): 												sendMessage('top','close');	break;						// close iframe content
			default: 																																// remove sources, data-content, styles, and classes from #content_pane
				removeAttribute('.directory_item.htm',['data-html_content']);
				removeAttribute('#content_title span',['data-after']);																				// remove content_title data
				document.querySelector('#content_title span').innerHTML = ''; 																		// empty content title
				hideGrid(); 																														// hide grid
				hideTextEditor(true); 																												// hide texteditor
				setAttribute('#content_pane','data-content','has_null'); 																			// remove content_pane classes and data attributes
				removeClass('#content_pane','has_zoom_image has_scaled_image');																		// remove content pane image classes
				removeAttribute('#content_pane .has_content',['src','style']); 																		// remove data and style attributes
				removeAttribute('#content_pane',['data-loaded']);
				removeClass('#content_pane .has_content','has_content');																			// remove .content classes
				removeAttribute('#content_iframe_utility',['src']);																					// remove utility iframe source
				if ( document.getElementById('font_file_viewer') !== null ) { document.getElementById('font_file_viewer').remove(); }							// remove font_file_viewer el
				if ( document.getElementById('content_font') !== null ) { document.getElementById('content_font').insertAdjacentHTML( 'beforeend', Content_Font_Viewer ); } // append font_file_viewer --> must reinit eventlisteners?
				closeMedia('video');																												// close video
				removeClass('#cue_sheet_track_list_container_video','has_cue_sheet');	document.getElementById('cue_sheet_track_list_container_video').innerHTML = '';	// close video cue sheet menu
				removeClass('.playlist_entry_container','has_content');					document.querySelector('.playlist_entry_container textarea').value = '';		// close playlist_entry_container
				setupContentPDF();																													// reset content_pdf
		}
	}
	function closeButton() {																												// ===> CLOSE BUTTON
		let $content_pane_data = getContentPaneData();
		switch(true) {
			case hasClass('#content_pane','has_audio') && $content_pane_data === 'has_null':				closeMedia('audio'); focusSidebar();	break;	// close audio
			case hasClass('body','edited'):	 showWarning('closeButton','edited');																break;	// close edited text editor
			case $content_pane_data === 'has_text_editor':	hideTextEditor(true);																	break; 	// hide text editor
			case hasClass('body','iframe_edited'):			sendMessage('iframe','unloading','closeContent','iframe_edited');						break;	// show close iframe_edited warning
			case $content_pane_data === 'has_grid':			closeGrid();																			break;	// close grid
			// close files and directories
			case hasClass('body','has_directory_source'):	closeIndexSource();																		break;	// close directory source
			case hasClass('#content_pane','has_iframe_file') && hasClass('#content_pane','has_iframe_dir'): 												// close iframe file and reopen navigated container directory
				showContent('content_iframe_dir',[ getAttribute('#content_iframe','data-iframe_file_src'),'source_dir'] );  sendMessage('iframe','focus_iframe');	break;
			case hasClass('#content_pane','has_iframe_file') && !hasClass('#content_pane','has_iframe_dir'): // close iframe file opened from previewed sidebar dir (not navigated directory) and reopen selected sidebar dir
				removeClass('#content_pane','has_iframe_file');	removeAttribute('#content_pane',['data-content']);											// no break;
				showContent('content_iframe_dir',[ getAttribute('#content_iframe','data-iframe_file_src'),'source_dir'] );									// reopen source directory
			case hasClass('#content_pane','has_iframe_dir'):																								// close navigated iframe directory; reopen selected sidebar dir
				removeClass('#content_pane','has_iframe_dir');  showThis( document.querySelector('.directory_item.selected').id );  sendMessage('iframe','focus_iframe');  // click selected sidebar item & focus iframe
																																break;
			case $content_pane_data === 'has_dir': 																											// close selected sidebar directory
				removeClass('.directory_item.selected:not(.audio),.directory_item.content_loaded:not(.audio)','selected content_loaded');
				removeAttribute('#content_pane',['data-loaded']);
				removeAttribute('#content_iframe',['src']); 																								// remove iframe src
				removeClass('#content_iframe','has_content');
				setAttribute('#content_pane','data-content','has_null');
				document.querySelector('#content_title span').innerText = ''; 																				// empty content title has_font
				focusSidebar();																														break;
			// close fonts & glyphs
			case $content_pane_data === 'has_glyph':		showWarning( 'closeGlyph' );															break;	// close glyph
			case $content_pane_data === 'has_font_file':	showWarning( 'closeFontFile' );															break;	// close font file preview
			case document.getElementById('specimen_glyph').innerText !== '':	removeAttribute('#font_toolbar',['data-char','data-unicode_hex']);	document.getElementById('specimen_glyph').innerText = '';	break;
			case document.getElementById('font_specimen_grid').innerText !== '':	document.getElementById('font_specimen_grid').innerText = '';	document.getElementById('unicode_char_ranges_select').value = '' ; resetFontFeatureSettings(); 	break;
			// close all other content except audio
			case $content_pane_data === 'has_ignored': 																										// nobreak; close ignored content
			case $content_pane_data !== 'has_null':																											// nobreak; close all other content
				removeClass('.directory_item.selected, .directory_item.content_loaded','content_loaded');									// don't remove selected in order to allow sidebar navigation to continue from closed file
				removeAttribute('#content_iframe',['data-iframe_file_src','data-kind']);
				closeContent();
				focusSidebar();																														break;
			// close playlists
			case hasClass('body','has_playlist'): case hasClass('body','has_filelist'): showWarning( 'closePlaylist',[true] );						break;	// close playlist/filelist
		}
		if ( hasClass('#content_pane','has_hidden_grid') ) { showGrid(); }																					// show hidden texteditor or grid
	}
	//============================//
	// ===> RESET CONTENT (Reset button or Cmd/Ctrl + R)
	function reloadImage() {																												// ===> RELOAD IMAGE
		closeContent();
		if ( document.querySelector('.image.selected') !== null || document.querySelector('.image.content_loaded') !== null ) { showThis(document.querySelector('.directory_item.image.content_loaded').id); }
	}
	function reloadContent() {																												// ===> RELOAD CONTENT
		let $content_pane_data = getContentPaneData();
		switch(true) {
			case getContentPaneData() === 'has_null' && !hasClass('#content_pane','has_audio'): location.reload();							break;	// reload window if no content visible
			case hasClass('#content_pane','has_audio'): 																							// nobreak; reset media time
				document.getElementById('audio').currentTime = 0;			document.getElementById('audio').pause();								// pause audio, reset time to 0
			case $content_pane_data === 'has_video':
				document.getElementById('content_video').currentTime = 0;	document.getElementById('content_video').pause();						// pause video, reset time to 0
																																			break;
			}
		switch(true) {																																// reset other content
			case $content_pane_data === 'has_text_editor':	case hasClass('body','has_playlist'):											break;	// don't do anything else for audio, video, text editor, playlist content.
			case $content_pane_data === 'has_grid':			showGrid();																		break;	// show grid
			case $content_pane_data === 'has_glyph':		document.querySelector('#glyph_viewer g').removeAttribute('style');	setAttribute('#glyph_viewer','data-scale',1);	break;
			case $content_pane_data === 'has_font':			document.getElementById('content_font').style.fontSize = '1em'; document.getElementById('specimen_glyph').style.fontSize = '64vw';
															resetFontFeatureSettings();	removeClass('#font_specimen_grid .selected','selected');
															document.getElementById('font_specimen_grid').innerHTML = '';
																																			break;	// reset font previews
			case $content_pane_data === 'has_image':		reloadImage();																	break;	// reload image
			case ( /has_text|has_markdown|has_htm|has_iframe|has_dir/.test( $content_pane_data) ):
				switch(true) {
					case hasClass('body','iframe_edited'): sendMessage('iframe','reloading','reloadContent'); 								break;
					default: showThis(document.querySelector('.directory_item.content_loaded').id);
				}
				// if ( hasClass('body','iframe_edited') ) { sendMessage('iframe','reloading','reloadContent'); } else { showThis(document.querySelector('.directory_item.content_loaded').id); } break;
			case hasClass('#content_pane','has_audio'):		case hasClass('#content_pane','has_video'):										break;	// don't do anything else for audio, video, text editor, playlist content.
			case ( /has_ignored|undefined/.test( $content_pane_data ) ):	window.location = window.location.href;							break;	// reload page
		}
		delete document.getElementById('content_pane').dataset.loaded;																				// remove dataset.loaded in case file can't be read by utility iframe
	}
	//**********************// ===> NAVIGATION
	function getNavigationType() {																											// ===> GET NAVIGATION TYPE
		let content_pane_data_content = ( document.getElementById('content_pane') !== null ? document.getElementById('content_pane').dataset.content : 'iframe'), nav_type;
		switch(true) {
			case document.body.classList.contains('has_open_submenu'):			nav_type = '#menu li.has_open_submenu ul';	break;	// submenu
			case document.body.classList.contains('has_menu'):					nav_type = '#menu';							break;	// menu
			case content_pane_data_content === 'iframe' && document.querySelector('#directory_list') !== null: nav_type = '#directory_list';	break;	// iframe dir_list
			case ( /has_font_file|has_glyph/.test(content_pane_data_content) ):					nav_type = '#font_file_grid';			break; 	// font file glyphs grid
			case document.getElementById('font_specimen_grid').children.length > 0 && hasClass('body','focus_content') && !hasClass('body','has_menu') && document.activeElement.contentEditable !== true:
			 																					nav_type = '#font_specimen_grid';		break;	// font specimen grid
			case ( /has_image|has_font/.test(content_pane_data_content) && hasClass('#content_pane','has_hidden_grid') && hasClass('body','focus_content') ):	// grids
			case content_pane_data_content === 'has_grid' && hasClass('body','focus_content'):														// no break
																								nav_type = '#content_grid';					break;
			default:																			nav_type = '#directory_list'; 					// default: dir_list
		}
		return nav_type; 																															// = selector of container of items to be navigated
	}
	function getNextNavigatedItem(key,bool) { 																								// ===> GET NEXT NAVIGATED ITEM; bool === true for autoplay media
		let nav_type = getNavigationType(), selected_el_kind, navigated_el;
		let selected_el = ( bool === true ? ( document.querySelectorAll('.audio_loaded,.media.content_loaded')[0] || document.querySelector('.media.selected') || document.querySelector('.media') ) : document.querySelector(nav_type).querySelector('.selected') );	// Get the currently selected item (if any)
		if ( selected_el !== null ) {																												// If there is a selected item...
			selected_el.classList.remove('selected');																								// ...remove its selected class
			if ( !isTopWindow() ) { removeClass('.content_loaded','content_loaded'); }													// for iframe dirs with audio and video files
			if ( !/warning_buttons|menu/.test(nav_type) ) 										{
				if ( nav_type === '#content_grid' && document.querySelector('#content_grid .image_grid_item') !== null && document.querySelector('#content_grid .font_grid_item') !== null ) {
					selected_el_kind = new RegExp(/image|font/);																					// get both images and fonts from mixed grids for L/R navigation
				} else {
					selected_el_kind = new RegExp(selected_el.dataset.kind);																		// get selected_el kind (for L/R navigation)
				}
			}
			if ( /audio|video/.test(selected_el_kind) && hasClass('body','play_all_media') ) 	{ selected_el_kind = /audio|video/; }				// but if play_all_media, get both media kinds
		}
		let els = Array.from(document.querySelector(nav_type).children).filter( function(el) {					 									// Get navigable elements and filter console
			if ( nav_type === '#font_file_grid' || el.offsetWidth > 0 && el.offsetHeight > 0 ) {												// only return visible items (or glyphs grid items)
				if ( selected_el !== null && /ArrowLeft|ArrowRight/.test(key) && !/warning_buttons|menu/.test(nav_type) ) {							// if L/R arrow and not menu or warning, and selected_el !== null...
					return selected_el_kind.test(el.dataset.kind) && !el.classList.contains('unchecked');											// ...return all unchecked items of same kind as selected_el
				} else {
					return true;																													// else return all items
				}
			}
		});
		let selected_el_index = ( selected_el === null ? -1 : els.indexOf(selected_el) );					 										// get index of selected item from filtered els or -1 if null
		switch(true) {																																// get next navigated element
			case hasClass('body','has_directory_source'): 																							// if viewing directory source, arrows will reopen selected sidebar item
				if ( document.querySelector('.directory_item.content_loaded') !== null ) { showThis( document.querySelector('.directory_item.content_loaded').id ); } else { showThis( selected_el.id ); }
				return;
			case key === 'ArrowUp':		case key === 'ArrowLeft':																					// ArrowUp / ArrowDown
				switch(true) {
					case ( /audio|video/.test(selected_el_kind) && key === 'ArrowLeft' ):	navigated_el = mediaLeftRightNavigation(els,selected_el,selected_el_index,key);			break;	// play prev media
					case ( /grid|font_specimen_grid/.test(nav_type) && !hasClass('body','has_menu')):
						navigated_el = els[gridNavigation(selected_el_index,els.length,nav_type,key)]; 	break;	// els[grid_item_index];
					case ( selected_el === null || ( selected_el_index === 0 && !key === 'ArrowLeft' ) ):						navigated_el = els[els.length - 1];					break;	// select last if nothing selected
					case ( /menu/.test(nav_type) && selected_el.classList.contains('is_submenu') && key === 'ArrowLeft' ):		navigated_el = subMenuNavigation(selected_el,key);	break;	// go to parent menu
					case ( selected_el_index === 0 ): 																			navigated_el = els[els.length - 1];					break;	// additional case for menus
					default: 																									navigated_el = els[selected_el_index - 1];					// default dir_list and menu items
				}
				break;
			case key === 'ArrowDown':	case key === 'ArrowRight':																					// ArrowLest / ArrowRight
				switch(true) {
					case ( /audio|video/.test(selected_el_kind) && key === 'ArrowRight' ):	navigated_el = mediaLeftRightNavigation(els,selected_el,selected_el_index,key);  		break;	// play next media
					case ( /grid|font_specimen_grid/.test(nav_type) && !hasClass('body','has_menu') ):
						navigated_el = els[gridNavigation(selected_el_index,els.length,nav_type,key)];	break;	// els[grid_item_index];
					case ( selected_el === null || selected_el_index === els.length - 1 ):	navigated_el = els[0];																	break;	// select first if nothing selected
					case ( /menu/.test(nav_type) && selected_el.classList.contains('has_submenu') && key === 'ArrowRight' ):	navigated_el = subMenuNavigation(selected_el,key);	break;	// open submenu
					default: 																									navigated_el = els[selected_el_index + 1];					// default dir_list and menu items
				}
				break;
		}
		switch(true) {																																// what to do with navigated element:
			case ( /grid|menu|font_specimen_grid/.test(nav_type) ): 																									// for grids and menus
				navigated_el.classList.add('selected');																								// ...add selected class to navigated_el
				switch(true) {
					case ( /font_specimen_grid/.test(nav_type) ):
						if ( document.getElementById('specimen_glyph').innerText === '' ) { addRemoveClassSiblings(navigated_el.id,'selected'); } else { showFontGlyph(navigated_el.id); }
						scrollThis('#font_specimen_grid','.selected'); 																		break;	// show navigated font glyph
					case ( /has_glyph/.test( getContentPaneData() ) ): showFontGlyph(navigated_el.id); 	break;	// show the navigated font file glyph
					case ( /grid/.test(nav_type) ):
						if ( document.querySelector('#directory_list .selected') !== null ) { document.querySelector('#directory_list .selected').scrollIntoView({block:"nearest"}); }	// scroll dir_list item into view
						document.querySelector('#content_pane .selected').scrollIntoView({behavior:"smooth",block:"nearest"});												// scroll grid item into view
						break;
				}																															break;
			case ( /ArrowUp|ArrowDown/.test(key) && navigated_el.dataset.kind === 'audio' ):		selectThis(navigated_el.id);			break;	// only select audio on U/D arrow
			case ( /ArrowLeft|ArrowRight/.test(key) && navigated_el.classList.contains('media') ):	showThis(navigated_el.id);						// load and play media on L/R arrow
				if ( selected_el_index === els.length - 1 && !hasClass('body','loop_media') && bool === true ) { null; } else { playPauseMedia('play'); }	// if last item and not loop => just select first item, else play
				break;
			default: 																				showThis(navigated_el.id);						// default: show navigated item
		}
	}
	function gridNavigation(selected_el_index,els_length,nav_type,key) {																	// ===> GRID NAVIGATION
		let grid_col_count, grid_row_count, grid_item_index;
		if ( /ArrowUp|ArrowDown/.test(key) ) { 																										// calculate number of grid rows and columns
			grid_col_count = ( Math.round( document.querySelector( nav_type ).offsetWidth / document.querySelector( nav_type +' > div').offsetWidth ) );	// number of grid items per row
			grid_row_count = Math.floor(els_length / grid_col_count);																						// number of full grid rows
		}
		switch(true) {
			case key === 'ArrowUp':																													// ArrowUp
				switch(true) {
					case selected_el_index === -1: 														grid_item_index = els_length - 1; 	break; 	// if nothing selected
					case selected_el_index < grid_col_count: 																						// if selected el is in first grid row...
						switch(true) {																												// ...and if it is in a column to the right of last item in last row get...
							case (grid_col_count * grid_row_count) + selected_el_index >= els_length:	grid_item_index = selected_el_index + (grid_col_count * (grid_row_count - 1)); break; // last in penultimate col or
							default: 																	grid_item_index = selected_el_index + (grid_col_count * grid_row_count); 	// ...last in last row
						}
						break;
					default: grid_item_index = selected_el_index - grid_col_count;																	// default: grid_item_index = selected_el_index - length of grid row
				}																															break;
			case key === 'ArrowDown':																												// ArrowDown
				switch(true) {
					case selected_el_index === -1: 								grid_item_index = 0;										break;	// if nothing selected, get first item
					case selected_el_index + 1 + grid_col_count > els_length:	grid_item_index = ( selected_el_index - (grid_col_count * ( grid_row_count - 1)) ) % grid_col_count; break; // if selected is last in column
					default:													grid_item_index = selected_el_index + grid_col_count;				// default: index = selected_el_index plus the length of the grid row
				}																															break;
			case key === 'ArrowLeft': 	grid_item_index = ( ( selected_el_index === -1 || selected_el_index === 0 ) ? els_length - 1 : selected_el_index - 1 ); break;	// if first or nothing selected, get last or prev
			case key === 'ArrowRight':	grid_item_index = ( ( selected_el_index === -1 || selected_el_index + 1 === els_length ) ? 0 : selected_el_index + 1 ); break;	// if last or nothing selected, get first or next
			}
		// selected class to corresponding dir_list item
		if ( !/has_font_file|has_glyph/.test(getContentPaneData()) && nav_type !== '#font_specimen_grid' ) {
			removeClass('.directory_item.selected','selected');		removeClass('.directory_item.content_loaded','content_loaded'); 						// remove classes from dir_list items
			if ( document.querySelector('#'+ document.querySelector(nav_type).querySelectorAll('div')[grid_item_index].dataset.id) !== null ) {
				document.querySelector('#'+ document.querySelector(nav_type).querySelectorAll('div')[grid_item_index].dataset.id).classList.add('selected');	// select corresponding dir_list item
				document.querySelector('.directory_item.selected').scrollIntoView({behavior:"smooth",block:"nearest"}); 										// scroll corresponding dir_list item into view
			}
		}
		return grid_item_index;
	}
	function subMenuNavigation(selected_el,key) {																							// ===> SUBMENU NAVIGATION
		let navigated_el;
		switch(key) {
			case 'ArrowLeft':
				document.body.classList.remove('has_open_submenu'); selected_el.classList.remove('selected','selected_submenu_item');
				navigated_el = selected_el.parentElement.parentElement;				 navigated_el.classList.remove('hovered','has_open_submenu');
				// let hovered_el = document.querySelector('li:hover');
				break;
			case 'ArrowRight':
				document.body.classList.add('has_open_submenu');	 selected_el.classList.add('selected','has_open_submenu','hovered');
				navigated_el = selected_el.querySelector('ul li');					 navigated_el.classList.add('selected_submenu_item');	break;
		}
		return navigated_el;
	}
	function mediaLeftRightNavigation(els,selected_el,selected_el_index,key) {																// ===> MEDIA LEFT/RIGHT NAVIGATION (Audio)
		let navigated_el, navigated_el_id;
		switch(true) {
			case hasClass('body','shuffle_media'):																									// if shuffle play enabled...
				navigated_el_id = getNextShuffledItem();																							// ...get the next shuffled item id
				switch(true) { 								// but if all shuffled items have been played (i.e., navigated_el_id === ''): if loop, update shufflelist, get next item; else get first item.
					case navigated_el_id === '': navigated_el = ( hasClass('body','loop_media') ? ( updateShuffleList(), navigated_el = document.getElementById(getNextShuffledItem()) ) : navigated_el = els[0] );	break;
					default: 					 navigated_el = document.getElementById( navigated_el_id );											// else get next item in the shufflelist
				}
				break;
			case !selected_el.classList.contains('audio_loaded') && selected_el.classList.contains('audio') && isTopWindow(): navigated_el = selected_el;	break;	// if selected audio item not loaded, select it
			case key === 'ArrowRight': navigated_el = ( selected_el_index + 1 < els.length ? els[selected_el_index + 1] : navigated_el = els[0] );			break;	// if selected not last, select next, else select first
			case key === 'ArrowLeft':  navigated_el = ( selected_el_index - 1 !== -1 ? els[selected_el_index - 1] : navigated_el = els[els.length - 1] );	break;	// if selected not first, select prev item, else last
		}
		selected_el.classList.remove('audio_loaded','content_loaded');																				// deselect currently selected media item class
		return navigated_el;
	}
	function playPrevNextMediaTrack() { 											      													// ===> PLAY PREV/NEXT MEDIA TRACK
		switch(true) {
			case !isTopWindow(): 																													// iframe audio files
				document.querySelector('.directory_item.selected.media a').trigger('dblclick'); 													// select and send message to top
				sendMessage('top','iframe_play_pause_media');
				break; 																																// show iframe audio
		}
 	}
	function navigateWarningButtons(e) {																									// ===> NAVIGATE WARNING BUTTONS
		let $buttons = getVisibleElsBySelector('#warning_buttons button');
		let $focused_button = getVisibleElsBySelector('#warning_buttons :focus,#warning_buttons .focus')[0], focused_btn_index = $buttons.indexOf($focused_button);
			removeClass('#warning_buttons button','focus');
		switch(true) {
			case e.shiftKey:
				switch(true) {
					case $focused_button === null || $buttons.indexOf($focused_button) === 0:
						  $buttons[$buttons.length - 1].focus(); $buttons[$buttons.length - 1].classList.add('focus');						break;	// focus last button
					default: $buttons[focused_btn_index - 1].classList.add('focus'); $buttons[focused_btn_index - 1].focus(); 						// else focus previous button
				}
				break;
			default: // e.Tab
				switch(true) {
					case $focused_button === null || $buttons.indexOf($focused_button) === $buttons.length - 1:
						  $buttons[0].focus(); $buttons[0].classList.add('focus'); 															break;  // focus first button
					default: $buttons[focused_btn_index + 1].classList.add('focus'); $buttons[focused_btn_index + 1].focus(); 						// else focus next button
				}
		}
	}
	function arrowKeyModifierFunctions(e,id) {																								// ===> ARROW KEY MODIFIER FUNCTIONS
		let args = [e.key];
		if ( cmdAltKey(e) && ( /ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ) ) { return; } else { e.preventDefault(); }					// prevents starting audio play when changing tabs; allows browser tab cycling
		switch(true) {
			case ( /ArrowLeft|ArrowRight/.test(e.key) && ( altKey(e) || altShiftKey(e) ) ): 														// alt/shift + L/R => mediaSkip(e)
				if ( e.shiftKey ) { args.push(30); } else { args.push(10); }
				if (!isTopWindow()) { sendMessage('top','mediaSkip','mediaSkip',args); } else { mediaSkip(e); }
				break;
			case cmdKey(e) && e.key === 'ArrowUp': 																									// Cmd/Ctrl + Up
				switch(true) {
					case !isTopWindow(): 													iframeClickLink(e,'','iframe_parent_link');		break;	// go to iframe parent
					case isTopWindow() && hasClass('body','focus_content') && hasClass('#content_iframe','has_content'):
																							sendMessage('iframe','open_iframe_parent_dir'); break;	// fallback for go to iframe parent in case top is incorrectly focused
					default:	e.preventDefault(); changeLocation([document.querySelector('#parent_dir_nav a').href]); 					break;	// go to parent (with warning for playlists/fonts/edited text)
				}
				break;
			case cmdKey(e) && e.key === 'ArrowDown': 																								// Cmd/Ctrl + Down
				switch(true) {
					case document.querySelector('.selected') === null:																		break;	// do nothing if nothing selected
					case !isTopWindow() && document.querySelector('#iframe_body #directory_list') !== null && cmdKey(e): iframedoubleClickThis( e,id, document.getElementById(id).querySelector('a').href );	break;
					case hasClass('.directory_item.selected','link'): 				openLinkFile(e);										break;	// open webloc or url files
					case hasClass('.directory_item.selected','playlist'): openPlaylist('','',document.querySelector('.directory_item.selected.playlist').dataset.playlist);	break;	// open playlist or filelist
					case isTopWindow() && hasClass('body','focus_content'):			focusContent('content_iframe',e); 						break;	// select first item if nothing selected in iframe
					case isTopWindow() && hasClass('.directory_item.selected','file') && !hasClass('.directory_item.selected','link'): 		break;	// ? do nothing for link files
					case isTopWindow() && hasClass('.directory_item.selected','dir') && hasClass('.directory_item.selected','app') && $settings.apps_as_dirs === false: break; // break if not viewing apps as dirs
					default: dblclick( document.querySelector('.directory_item.selected'),showWarning('doubleClickThis', [getAttribute('.directory_item.selected','id'), getAttribute('.directory_item.selected a','href')] ) );
																																					// else double-click directories and all iframe items to open them
				}
				break;
			case cmdKey(e) && e.key === 'ArrowLeft': 																								// Cmd/Ctrl + Left
				switch(true) {
					case !hasClass('.directory_item.selected','has_subdirectory') && document.querySelector('.directory_item.has_subdirectory') !== null:	{	// if selected item is in subdirectory...
						let $selected = document.querySelector('.directory_item.selected');
						while ( !($selected.previousElementSibling.classList.contains('has_subdirectory')) && $selected.previousElementSibling !== null ) { $selected = $selected.previousElementSibling; } // ...find parent
						showThis($selected.previousElementSibling.id);																				// select and show parent dir
					    }
					    break;
					case hasClass('.directory_item.selected','has_subdirectory'):	closeSubdirectory( document.querySelector('.directory_item.dir.selected').id ); 	break;	// close selected subdirectory
					default: showThis( getVisibleElsBySelector('.directory_item')[0].id); 									   						// select first visible item
				}
				break;
			case cmdKey(e) && e.key === 'ArrowRight': 																								// Cmd/Ctrl + Right
				if ( hasClass('.directory_item.selected','dir') && !hasClass('.directory_item.selected','has_subdirectory') ) { openSubdirectory(document.querySelector('.directory_item.dir.selected').id); }	// open selected subdirectory
				break;
		}
	}
	function arrowKeyNavigation(args) { getNextNavigatedItem(args[0],args[1]); } // args[0] = key, args[1] = bool (for autoplay media)		// ===> ARROW KEY NAVIGATION
	function arrowKeyFunctions(e,bool,el) { // 'e' = keyboardEvent or string (e.g. 'ArrowLeft/Right' from clickPrevNextButtons()			// ===> ARROW KEY FUNCTIONS
		let id;		if ( el.querySelector('.selected') !== null ) { id = el.querySelector('.selected').id; }
		switch(true) {
			case ( /a|input|select|textarea|img|embed/.test(document.activeElement.tagName.toLowerCase())) && !cmdKey(e):							// no break // needed to allow for normal arrow key function
			case ( /text_editor_preview_pane|text_editor_html_pane|content_iframe_pane/.test(document.activeElement.id)) && !cmdKey(e):				// no break //  "  "
			case document.activeElement.hasAttribute('contentEditable') && !cmdKey(e):																// no break //  "  "
			case !isTopWindow() && document.querySelector('#iframe_body #directory_list') === null && !cmdKey(e):							return;	// iframe is not a dir_list
			case isTopWindow() && hasClass('body','iframe_edited'):
																		 	e.preventDefault();	sendMessage('iframe','unloading','',['arrow_key_navigation',e.key]);	break;
			case isTopWindow() && hasClass('body','focus_content') && hasClass('#content_iframe','has_content'):						// needed e.g. after navigating to iframe dir_list parent directory
														 					e.preventDefault();	sendMessage('iframe','iframe_arrow_navigation','',e.key); focusContent('content_iframe',e);	break;
			case hasClass('body','has_menu') && !isTopWindow(): e.preventDefault();	sendMessage('top','arrow_key_navigation','arrowKeyNavigation',e);	break;	// menu navigation from focused iframe
			case e.altKey: case e.ctrlKey: case e.metaKey: case e.shiftKey: arrowKeyModifierFunctions(e,id);				 							break;	// arrow keys + modifiers
 			default:														e.preventDefault();	showWarning( 'arrowKeyNavigation',[e.key,bool] ); // normal L/R/U/D arrow key navigation, with warning
		}
	}
	function clickPrevNextButtons(e,id) {																									// ===> CLICK PREV/NEXT BUTTONS
		e.stopPropagation(); e.preventDefault();
		let key = ( /prev_btn|prev_track/.test(id) ? 'ArrowLeft' : /next_btn|next_track/.test(id) ? 'ArrowRight' : null );							// define arrowkey
		if ( hasClass('body','focus_content') ) { focusContent(); } else { document.getElementById(id).parentElement.blur(); }
		getNextNavigatedItem(key);																													// get the next track
	}
	let str = '';
	function timeoutID() { return window.setTimeout( function() { str = ''; }, 1500 ); }													// ===> TIMEOUT ID: reset typed string to '' after 1.5 sec.
	function alphaNav(e) { 																													// ===> ALPHA NAV
		switch(true) {
			case document.activeElement.tagName.toLowerCase() === 'textarea' || document.activeElement.getAttribute('contentEditable') === true: break;
			default:
				let timer = timeoutID();
				if ( typeof timer === 'number' ) { window.clearTimeout( timer ); timer = 0; }											 			// timeout id
				timeoutID();
				str += e.key.toLowerCase();
				if ( document.querySelector('.directory_item[data-name^="'+ str +'"]') !== null ) {													// if matching name found, show the item
					showThis(document.querySelector('.directory_item[data-name^="'+ str +'"]').id);
					scrollThis('#directory_list','.selected');
				// } else {
					// null; // replace this with some sort of fuzzy match function? TBD
				}
		}
	}
	// ===> END NAVIGATION
	//============================//
	function selectThis(row_ID) {																											// ===> SELECT THIS on click and set classes for $content_pane
		switch(true) {
			// case document.getElementById(row_ID).classList.contains('disabled'):															break;	// do nothing for disabled items
			case document.getElementById(row_ID).classList.contains('audio'):																		// audio: add/remove selected class
				removeClass('.audio','selected');	document.getElementById(row_ID).classList.add('selected');	break;
			default: 																																// select dir_list item
				removeClass('body','has_directory_source');
				addRemoveClassSiblings('#'+ row_ID,'selected','selected content_loaded hovered'); 													// remove classes from siblings, but leave .audio with playing
		}
		scrollThis('#directory_list','.selected');
	}
	function showThis(id) { if ( document.getElementById(id) !== null ) { selectThis(id); showContent(id); } }										// ===> SHOW THIS
	//============================//
	function clickThis(id) { let el = document.getElementById(id); if ( el !== null ) { el.querySelector('a').click(); } else { el.click(); } }	// ===> CLICK THIS by id
	function doubleClickThis(args) {																										// ===> DOUBLE-CLICK THIS (dirs only)
		let row = document.getElementById(args[0]);
		if ( row.classList.contains('dir') && row.classList.contains('invisible') && row.classList.contains('ignored') ) { return; } 				// don't attempt to open ignored invisible dirs (chiefly system dirs)
		let id = args[0].slice(args[0].indexOf('-') + 1), href = args[1];
		let query_str = decodeURIComponentSafe(window.location.search);
		if ( query_str === '' ) { query_str = '?'; }
		if ( query_str.indexOf('history') !== -1 ) {
			query_str = query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ id +'+');
		} else {
			query_str = query_str.replace(/&selected=\d+/,'') + '&history='+ id;
		}
		window.location = href + query_str;
	}
	function clickDirListItem(e,id) {																										// ===> CLICK DIR LIST ITEM
		e.preventDefault();  //e.stopPropagation();
		if ( !isTopWindow() ) { return false; }																							// do nothing for iframe items
		focusSidebar();																																// focus sidebar
		let el = document.getElementById(id);
		switch(true) {																																// show content or play/pause media
			case el.classList.contains('audio'): 	if ( el.classList.contains('selected') ) { playPauseMedia(); } else { showContent(id); }					break;
			case el.classList.contains('video'): 	if ( el.classList.contains('selected') ) { playPauseMedia(); } else { showWarning( 'showThis',[id] ); }		break;
			default:
				switch(true) {
					case hasClass('body','iframe_edited'):																							// if iframe edited && clicked item is not
						if ( !/image|pdf|font|media/.test(el.className) ) { sendMessage('iframe','unloading','',['showThis',id]); }	else { showWarning( 'showThis',[id] ); } break;
					default:						 showWarning( 'showThis',[id] );
				}
//				if ( hasClass('body','iframe_edited') ) { sendMessage('iframe','unloading','',['showThis',id]); } else { showWarning( 'showThis',[id] ); }
		}
	}
	//============================//
	// ===> TEXT EDITING
	function setUpTextEditorUI() {																											// ===> SETUP TEXT EDITOR
		let raw_markdown, body_classes = [], content;
		if ( !hasClass('body','has_text_editor_UI') ) { 																							// add classes, styles, and scripts; only add once
			document.querySelector('head').insertAdjacentHTML('beforeend','<style id="text_editor_styles">'+ text_editor_styles +'</style>');
			document.querySelector('head').insertAdjacentHTML('beforeend','<link id="github_markdown_css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
			if ( isTopWindow() ) { document.querySelector('#content_title span').innerHTML = ''; }
			body_classes.push('has_text_editor_UI', getSearchParam('text_editor_default_view'));
		}
		switch(true) { 																																// get source text and append UI elements
			case isTopWindow(): 																										// top level text editor
				if ( !hasClass('body','has_text_editor_UI') ) { document.getElementById('content_text').insertAdjacentHTML( 'beforeend', Text_Editing_UI_Elements ); } // only add text editor UI if it hasn't been added already
				break;
			case !isTopWindow(): 																										// iframe text editing UI
				window.addEventListener("message", receiveMessage, true);																			// add event listener for messages from top
				document.querySelector('head').insertAdjacentHTML('afterbegin','<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/plain; charset="utf-8">');
				document.querySelector('head').insertAdjacentHTML('beforeend','<style id="text_editor_styles">'+ global_styles +'</style>');
				document.querySelector('head').insertAdjacentHTML('beforeend','<style id="color_and_background_styles">'+ color_and_background_styles +'</style>');			// add iframe text editing styles
				document.querySelector('head').insertAdjacentHTML('beforeend','<style id="utilities_styles">'+ utilities_styles +'</style>');		// add iframe text editing styles
				raw_markdown = decodeURIComponentSafe( document.querySelector('body > pre').innerText ); 											// get source text and decode Unicode chars.
				document.body.innerHTML = '<div id="content_text" class="background_grey_90">'+ Text_Editing_UI_Elements +' </div>'+ Utilities_Elements('text_editor'); // add the UI
				document.getElementById('text_editor_raw_pane').value = raw_markdown; 																// set the source text value
				document.getElementById('text_editor_raw_pane').setSelectionRange(0,0); 															// set the insertion point to the beginning of the text
				if ( /^\#EXTM3U/m.test(document.getElementById('text_editor_raw_pane').value.trim()) ) { 											// playlists and filelists
					content = document.getElementById('text_editor_raw_pane').value.trim(); 														// get m3u.txt file for processing
					sendMessage('top','iframe_playlist','',content);
				}
				break;
		}
		switch(true) { 																																// assemble text editing body classes
			case getSearchParam('enable_text_editing') === 'false' && !isTopWindow(): 													// text editing disabled
				removeClass('body','text_editor_split_view text_editor_preview text_editor_html');
				body_classes.push('disable_text_editing text_editor_raw'); 																			// show the source text
				document.getElementById('text_editor_raw_pane').disabled = true; 																	// disable textarea editing
				break;
			default:
				if ( getSearchParam('text_editor_split_view') === 'true' ) 					{ body_classes.push('text_editor_split_view'); }
				if ( getSearchParam('text_editor_sync_scroll') === 'true' )					{ body_classes.push('text_editor_sync_scroll'); document.querySelector('#text_editor_sync_scroll input').checked = true; }
				if ( getSearchParam('text_editor_theme') === 'default' )
					{ body_classes.push('text_editor_theme_'+ getSearchParam('theme')); } else { body_classes.push( 'text_editor_theme_'+ getSearchParam('text_editor_theme') ); }
		}
		addClass('body',body_classes.join(' ')); 																									// add text editor body classes
		initTextEditorEventListeners();																												// init text editor event listeners
		focusTextEditorPanes();																														// focus text editor panes
		TextEditing(); 																																// call text editing functions
	}
	function TextEditing() {																												// ===> TEXT EDITING Function: create Markdown Preview
		let raw_markdown = ( document.getElementById('text_editor_raw_pane') !== null ? document.getElementById('text_editor_raw_pane').value.toString() : '' );
		MDmarkdown( raw_markdown, document.getElementById('text_editor_html_pane') );
		MDsetChecklistClass();																														// set checklist class in case any added
	}
	function focusTextEditorPanes() {																										// ===> FOCUS TEXT EDITOR PANES
		switch(true) {
			case hasClass('body','text_editor_split_view'): case hasClass('body','text_editor_raw'):	document.getElementById('text_editor_raw_pane').focus(); 		break;
			case hasClass('body','has_preview_html'): 									document.getElementById('text_editor_html_pane').focus();		break;
			case hasClass('body','text_editor_preview'):									document.getElementById('text_editor_preview_pane').focus();	break;
		}
	}
	function toggleTextEditorPanes() {																										// ===> TOGGLE TEXT EDITOR PANES (on tab)
		switch(true) {
			case document.activeElement.id === 'text_editor_preview_pane' && getFocusableEls('#text_editor_preview_pane').length > 0: 				// focus focusable elements in text preview
				getFocusableEls('#text_editor_preview_pane')[0].focus(); break;
			case ( /text_editor_preview_pane|text_editor_html_pane|text_editor_raw_pane]/.test(document.activeElement.id) && !hasClass('body','text_editor_split_view') ): 	// text editor: if not split view, focus sidebar
				sendMessage('top','focus_sidebar'); break;
			case hasClass('body','text_editor_split_view'):
				switch(true) {
					case document.activeElement.id === 'text_editor_raw_pane': 																		// text editor: if text source has focus with split, focus the other pane
						if ( hasClass('body','text_editor_html') ) { document.getElementById('text_editor_html_pane').focus(); } else { document.getElementById('text_editor_preview_pane').focus(); } break;
					case ( /text_editor_preview_pane|text_editor_html_pane/.test(document.activeElement.id) ): 										// text editor: if text preview has focus with split, focus text source
						document.getElementById('text_editor_raw_pane').focus(); break;
				}
				break;
		}
	}
	//============================//
	// MARKDOWN Functions
	function selectTextareaContent(id) { let $textarea = document.getElementById(id); $textarea.focus(); $textarea.select(); $textarea.scrollTop = 0; }		// ===> SELECT TEXTAREA CONTENT
	function clearText() {																													// ===> CLEAR TEXT
		if ( !isTopWindow() ) { sendMessage('top','iframe_edited'); addClass('body','edited'); }
		if ( document.getElementById('text_editor_raw_pane') !== null ) {
			document.getElementById('text_editor_raw_pane').value = '';
			document.getElementById('text_editor_raw_pane').style.width = '';
			document.getElementById('text_editor_raw_pane').focus();
		}
		removeAttribute('#text_editor_preview_pane',['srcdoc']);
		if ( document.getElementById('text_editor_html_pane') !== null ) { document.getElementById('text_editor_html_pane').value = ''; }
	}
	function saveBtn(id) {																													// ===> SAVE BUTTON
		let data, ext, file_name;
		switch(true) {
			case getContentPaneData() === 'has_text_editor': file_name = 'untitled'; break;
			default:	file_name = decodeURIComponentSafe(window.location.pathname.split('/').reverse()[0]);
						file_name = file_name.slice(0,file_name.lastIndexOf('.'));
		}
		switch(true) {
			case		id === 'save_text': data = document.getElementById('text_editor_raw_pane').value; ext = '.md'; break;
			case		id === 'save_HTML':	data = MDprepHTML( document.getElementById('text_editor_preview_pane').innerHTML ); ext = '.html';
		}
		saveMD( data, file_name + ext );
	}
	function MDprepHTML(data) {																												// ===> MD PREP HTML for saving
		const save_HTML_open = `<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title>
								<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>
								<style></style><script></script></head><body lang="en" class="markdown_body">`;
		const save_HTML_close = '</body></html>';
		data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
		return save_HTML_open + data + save_HTML_close;
	}
	function saveMD(data,file_name) { 																										// ===> SAVE MD
		if ( !isTopWindow() ) { sendMessage('top','save_text','',[data,file_name]); } else { saveFile(data,'text/plain',file_name); }	// #top must save text, else a new window opens containing blob content
		removeClass('body,#text_editor_raw_pane,#content_text','edited');
	}
	// MD Custom pre- and post-processing for text.
	function MDaddHeaderIDs(match, p1, p2, p3) { return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3; } 		// ===> MD ADD HEADER IDS (for Toc)
	function MDcustomPreProcess(src) { return src; } 																						// ===> MD CUSTOM PREPROCESS (we're not doing anything here just yet...)
	function MDcustomPostProcess(html) {																									// ===> MD CUSTOM POSTPROCESS
		html = html.replace(/<(p|li|dt|dd)>-*\s*\[\s*x\s*\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox" checked><label>$2</label></$3>') // checkboxes in p,li,dt,dd
			.replace(/<(p|li|dt|dd)>-*\s*\[\s{1,}\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox"><label>$2</label></$3>') // checkboxes
			//		.replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
			.replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) 																					// add header IDs;
			.replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
		return html;
	}
	function MDmarkdown(raw_markdown) { 																										// ===> MDMARKDOWN: Render markdown from processed source text
		const MDit = window.markdownit({linkify:false, typography:false, html:true}).use(window.markdownitMultimdTable, {enableMultilineRows: true})
			.use(window.markdownitSub).use(window.markdownitSup).use(window.markdownitFootnote).use(window.markdownitCentertext).use(window.markdownitDeflist).use(window.markdownitTocDoneRight);
		let MD_Preview = MDcustomPostProcess( MDit.render( MDcustomPreProcess( raw_markdown ) ) );
		let MD_script = '<script id="scroll_script">window.onscroll = function(){ window.parent.postMessage( { "messageContent":"scroll_iframe","functionName":"","arguments":window.scrollY },"*") }; window.onclick = function(e){ e.stopPropagation(); window.parent.postMessage({ "messageContent":"focus_text_preview" },"*" )};</script><style>body{margin:0;padding:0;}</style>';  							// inline scripts to permit sync scrolling and focus
			document.getElementById('text_editor_preview_pane').srcdoc = MD_script + MD_Preview; 																// set previewed text
		let source_HTML = MD_Preview.toString();
			document.getElementById('text_editor_html_pane').value = source_HTML; 																			// set raw html view
	}
	function MDlivePreview() { MDmarkdown( document.getElementById('text_editor_raw_pane').value ); MDsetChecklistClass(); }							// ===> MD LIVE PREVIEW add edited warning
	// MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
	function MDreplaceAt(str, replacement, position) { str = str.substring(0, position) + replacement + str.substring(position + replacement.length); return str; }
	function MDreplaceNthSubStr(str,substr,replacement,index) {
		let count = 0, found = substr.exec(str);
		while ( found !== null ) { if ( count === index ) { return MDreplaceAt(str, replacement, found.index ); } else { count++; found = substr.exec(str); } }
	}
	function MDliveCheckBoxes(checkbox,$source_el,$preview_el) {																			// ===> MD LIVE CHECKBOXES
		removeClass('.checklist','clicked');
		checkbox.closest('p,li,dt,dd').classList.add('clicked');
		const this_index = $preview_el.querySelector('.checklist').index( $preview_el.querySelector('.checklist .clicked') );
		const src_text = $source_el.value;
		const substr = new RegExp(/\[\s*.\s*\]/g);
		const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
		$source_el.value = MDreplaceNthSubStr(src_text, substr, replacement, this_index);
	}
	function MDsetChecklistClass() {																										// ===> MD SET CHECKBOX LIST CLASS
		document.querySelectorAll('#content_text input[type="checkbox"]').forEach( el => el.closest('ul').style.cssText = 'list-style:none;padding:0;' );	// Prevent checkbox lists from having list bullets
	}
	function MDresizeSplit() {																												// ===> MD RESIZE SPLIT VIEW
		let page_width = window.innerWidth;
		let editor_width = document.getElementById('content_text').offsetWidth;
		let editor_offsetLeft = ( document.body.id === 'top' ? document.getElementById('content_pane').offsetLeft : 0);
		document.onmousemove = function(e) {
			e.stopPropagation(); e.preventDefault();
			let pageX = e.pageX;
			if ( pageX > editor_offsetLeft + 150 && pageX < page_width - 150 ) {																	// min split pane widths
				document.getElementById('text_editing_handle').style.left = pageX - editor_offsetLeft - 4 + 'px';
				document.getElementById('text_editor_raw_pane').style.width = pageX - editor_offsetLeft + 'px';
				document.getElementById('text_editor_preview_pane').style.width = editor_width + editor_offsetLeft - pageX + 'px';
				document.getElementById('text_editor_html_pane').style.width = editor_width + editor_offsetLeft - pageX + 'px';
			}
		};
	}
	function MDsyncScroll(e,id,iframe_scrollTop,iframe_scrollHeight) {																		// ===> MD SYNC SCROLL
		if ( !document.querySelector('input[name="text_editor_sync_scroll"').checked || !hasClass('body','text_editor_split_view') ) { return; } 							// ignore if no split or no sync scroll
		// let editor_height = document.getElementById('text_container').offsetHeight;
		let scrolled = e.currentTarget, scrolled_scrollTop = iframe_scrollTop || scrolled.scrollTop,
			scrolled_height = iframe_scrollHeight || scrolled.scrollHeight,
			scrolled_percentage = (scrolled_scrollTop/scrolled_height).toFixed(4);
		// the element to be sync scrolled: remove the target element id and the hidden editor pane from the array of editor pane ids
		let synced_id = ['text_editor_raw_pane','text_editor_html_pane'].filter(el => el !== scrolled.id).filter(el => document.getElementById(el).offsetHeight > 0).toString();
		let synced = document.getElementById(synced_id) || document.getElementById('text_editor_preview_pane').contentWindow.document.documentElement; 			// the element to be sync scrolled
		synced.scrollTo(0, (scrolled_percentage * synced.scrollHeight).toFixed(0), {behavior:'smooth'});
		// if ( (scrolled.scrollTop + editor_height) === scrolled_height) { synced.scrollTo(0,synced.scrollHeight, {behavior:'smooth'}); }; // force synced element to bottom
	}
	function MDtocClick(id) { 																												// ===> MD TOC CLICK anchors
		let thisId = document.getElementById(id).href; if ( thisId !== null ) { document.getElementById('text_editor_preview_pane').scrollTop = document.getElementById(id).offset().top - 48; }
	}
	function MDheaderClick() {																												// ===> MD HEADER CLICK
		switch(true) {
			case document.getElementById('text_editor_preview_pane').getElementsByClassName('.table-of-contents') !== null:
						document.getElementById('text_editor_preview_pane').getElementsByClassName('table-of-contents')[0].scrollIntoView({behavior:"smooth",block:"nearest"}); break;
			default:	document.getElementById('text_editor_preview_pane').scroll(0,0);
		}
	}
	//***********************//
	// MESSAGES
	function sendMessage(target,message,funcName,args) {																					// ===> SEND MESSAGE to iframe or parent
		let messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
		switch(target) {
			case 'iframe': 			document.getElementById('content_iframe').contentWindow.postMessage( messageObj, '*' ); break;
			case 'top':				window.parent.postMessage( messageObj, '*'); break;
		}
	}
	function receiveMessage(e) {																											// ===> RECEIVE MESSAGE from iframe or parent, do appropriate action
		if ( e.origin === 'null' || e.origin === $origin ) {
			let message = e.data.messageContent, args = e.data.arguments;
			switch( message ) {
				case 'toggleUIPref':		toggleUIPref(args);	break;
				case 'arrow_key_navigation': removeClass('body','iframe_edited'); focusSidebar(); arrowKeyNavigation(args); 				break;	// class_name, key
				case 'hide_sidebar': 		document.body.classList.toggle('hide_sidebar');								break;
				case 'toggle_menu':		 	showMenus('menu_container');																	break;
				case 'close_menu': 		case 'top_closed_menu': 	removeClass('body','has_menu is_blurred');								break;
				case 'top_has_menu':		addClass('#iframe_body','has_menu is_blurred');													break;	// tell iframe top has menu
				case 'menu_navigation': 	arrowKeyNavigation(args);																		break;	// menu navigation from iframe
				case 'menu_selection':	case 'clickMenu': 			e.preventDefault(); e.stopPropagation(); clickMenu();					break;	// show menu
				case 'toggle_invisibles':	document.querySelector('#show_invisibles_container input').click();	focusContent();				break;
				case 'focus_sidebar':		focusSidebar(); 																						// no break
				case 'escape': 			case 'tab': 																								// close menus and refocus content or focus sidebar
					switch(true) {
						case hasClass('#top','focus_content') && hasClass('#top','has_menu'):					focusContent();				break;
						default: focusSidebar(); scrollThis('#directory_list','.selected');												break;
						}
				case 'shift_focus_iframe':
					switch(true) {
						case document.querySelector('#directory_wrapper.iframe') !== null: 														// if iframe dir_list visible...
							removeClass('body','is_blurred');
							switch(true) {
								case 		document.querySelectorAll('.directory_item.selected, .directory_item.is_blurred') !== undefined:	addClass('.directory_item.is_blurred','selected');
											removeClass('.directory_item.is_blurred','is_blurred');											break;
								default:	getVisibleElsBySelector('.directory_item')[getVisibleElsBySelector('.directory_item').length - 1].classList.add('selected'); // select last row when tabbing into directory
							}
							break;
						default: getFocusableEls('#iframe_body').last().focus();															break;
					}
					break;
				case 'focus_iframe': 																												// after tabbing into iframe
					removeClass('body','has_menu is_blurred');
					switch(true) {
						case document.querySelector('#directory_wrapper.iframe') !== null: 															// if iframe dir_list visible...
							switch(true) {
								case 		document.querySelector('.directory_item.selected, .directory_item.is_blurred') !== null:
												addClass('.directory_item.is_blurred','selected'); removeClass('.directory_item.is_blurred','is_blurred');	break;
								default:	getVisibleElsBySelector('.directory_item')[0].classList.add('selected'); 								// select first row when tabbing into directory
							}
							break;
						case hasClass('body','has_text_editor_UI'): 																				// if text editor visible...
							switch(true) { case document.activeElement.id === 'iframe_body': focusTextEditorPanes(); break; }
							switch(true) {
								case hasClass('body','has_text_editor_split_view'):
								case hasClass('body','text_editor_raw'): {
									let selection = window.getSelection();
									if ( selection.anchorOffset > 0 ) { 																			// restore cursor position or text selection ( but not for text files )
										// document.getElementById('text_editor_raw').setSelectionRange(x,y);											// not implemented yet
										// document.getElementById('text_editor_raw').scrollTop = [position of x];
										// return;
									} else {
										document.getElementById('text_editor_raw_pane').setSelectionRange(0,0);												// set cursor to beginning and scroll to top
										document.getElementById('text_editor_raw_pane').scrollTop = 0;
									}
									break;
									}
								}
							break;
						case document.activeElement.id === 'iframe_body': 																			// focus form elements and textareas in iframe files
							break;
					}
					break;
				case 'text_editor_theme_default':
					addClass('#iframe_body',message); removeClass('#iframe_body','text_editor_theme_light text_editor_theme_dark');
					break;
				case 'text_editor_theme_light': case 'text_editor_theme_dark':
					addClass('#iframe_body',message); removeClass('#iframe_body','text_editor_theme_light text_editor_theme_dark');
					break;
				case 'theme_light': case 'theme_dark': 																								// toggle iframe UI theme and iframe Text Editor theme
					document.getElementById('iframe_body').classList.remove('theme_dark','theme_light','text_editor_theme_light','text_editor_theme_dark');
					document.getElementById('iframe_body').classList.add(message,'text_editor_'+ message);										break;	// change iframe dir theme
				case 'blur_iframe':					addClass('body','is_blurred');															break;
				case 'iframe_click':				focusContent();																			break;	// close menus and fade sidebar
				case 'show_iframe_parent':			showContent('content_iframe_parent',args);												break;	// args[0] === item link, args[1] === item kind
				case 'show_iframe_dir':				showContent('content_iframe_dir',args);													break;	// args[0] === item link, args[1] === item kind
				case 'show_iframe_file':			showContent('content_iframe_file',args);												break;	// args[0] === item link, args[1] === item kind
				case 'show_content': 																												// mainly for opening webloc and url files from
					if ( args[1] === 'dir' ) { showContent('content_iframe_dir',args); } else { showContent('content_iframe_file',args); }
					break;
				case 'open_iframe_dir_in_sidebar':	window.location = args;																	break;	// tell top to open iframe directory in sidebar; args === iframe directory url
				case 'open_iframe_parent_dir': 		iframeClickLink(e,getAttribute('#iframe_parent_link','href'),'parent'); 				break;	// document.getElementById('#parent').find('a').click();
				case 'iframe_arrow_navigation':
					switch(args) {
						case 'ArrowUp': 			showThis(getVisibleElsBySelector('.directory_item')[getVisibleElsBySelector('.directory_item').length - 1].id); 	break;
						case 'ArrowDown':			showThis(getVisibleElsBySelector('.directory_item')[0].id); 	break;
					}
					break;
				case 'close':						closeContent();																			break;	// escape content_iframe and close content
				case 'closeContent':				closeContent(); removeClass('body','iframe_edited'); focusSidebar();					break;	// close edited_iframe text after clicking "Save/Don't Save" buttons
				case 'reload':						showWarning('reloadContent');															break;	// reload content
				case 'reloadContent': 				showThis(document.querySelector('.directory_item.content_loaded').id); removeClass('body','iframe_edited'); break; // reload iframe content after "Save/Don't Save" buttons
				case 'showThis':					removeClass('body','iframe_edited'); focusSidebar(); showThis(args);					break;	// top: show clicked/navigated sidebar item after "Save/Don't Save" buttons
				case 'show_numbers': case 'show_invisibles': case 'alternate_background': case 'hide_ignored_items': case 'ignore_ignored_items':
													document.getElementById('iframe_body').classList.toggle(message);						break;	// toggle iframe dir_list UI prefs from main menu:
				case 'show_image_thumbnails':
					console.log('message show_image_thumbnails');
							//if ( hasClass('body','show_image_thumbnails') ) { console.log("REMOVE"); document.body.classList.remove('show_image_thumbnails'); }  else { console.log("ADD");
							 showImageThumbnails(); //document.body.classList.add('show_image_thumbnails'); }												break;	// toggle image thumbnails in iframe
				// AUDIO MESSAGES
				case 'iframe_play_pause_media':		playPauseMedia();																		break;	// tell top to play/pause audio from iframe click
				case 'mediaSkip':					mediaSkip(undefined,args);																break;	// tell top to mediaskip from focused iframe
				case 'play_prev_next_iframe_audio': playPrevNextMediaTrack(args); 															break;	// play next iframe track
				case 'close_iframe_audio':			removeClass('.audio_loaded','audio_loaded');											break;
				case 'set_media_duration':			setMediaDuration(args[0],args[1],args[2],true);											break;	// set media durations for subdirectories [id, item_sort_kind, duration]
				// TEXT EDITING MESSAGES
				case 'iframe_edited':				if ( !hasClass('#top','iframe_edited') ) { addClass('#top','iframe_edited'); }			break;	// let top know iframe text has been edited
				case 'scroll_iframe':				MDsyncScroll(e,'text_editor_preview_pane',args,document.getElementById('text_editor_preview_pane').contentWindow.document.documentElement.scrollHeight); break;
				case 'text_editor_toolbar_button':	if ( !isTopWindow() ) { document.body.classList.toggle(args); } else { toggleSearchParam(args); }	break;
				case 'clear':						addClass('body#top','iframe_edited');													break;	// add edited class after clearing text from edited iframe file
				case 'save_text':					saveFile(args[0],'text/plain',args[1]);													break;
				case 'iframe_text_saved':			removeClass('body','iframe_edited');													break;
				case 'toggle_text_editor':			showTextEditor();																		break;
				case 'focus_text_preview':			document.getElementById('text_editor_preview_pane').focus();								break;
				case 'unloading':					showWarning('closeContent',args);														break;	// show unsaved changes warning in iframe
				case 'reloading':					showWarning('reloadContent');															break;
				//case 'save_text_selection':		document.querySelector('.text.selected,.code.selected,.markdown.selected').dataset.selection_start = args[0];
													// document.querySelector('.text.selected,.code.selected,.markdown.selected').dataset.selection_end = args[1];	break; // from iframe
				//case 'get_text_selection': 		document.getElementById('content_text').data.selection_start = args[0];
													// document.getElementById('content_text').data.selection_end = args[1];				break;
				// OTHERS
				case 'link_file_link':
					if ( document.querySelector('.directory_item.link.selected') !== null ) {
						document.querySelector('.directory_item.link.selected').dataset.link_file_link = args;
						showIframeContent(args);
					}																														break;	// add link file text to dir_list.selected
				case 'iframe_loaded':																												// iframe loaded
					switch(true) {
						case args[0].endsWith('.cuetxt'): processCueSheet(args[1]);	break;															// cue sheet; cue file name must end with ".cuetxt"
						default: showIframeContent(args); 																							// if message received by top, iframe loaded successfully
					}																														break;
				case 'dir_list_subdir_loaded': 																										// subdirectory loaded, add the subdirectory to the dir_list, update stats
					if ( document.querySelector('.directory_item.dir_list_subdir_loading') === null ) { return; }
					document.querySelector('.directory_item.dir_list_subdir_loading').insertAdjacentHTML('afterend',args[0]);							// insert subdir items
					document.querySelector('.directory_item.dir_list_subdir_loading').classList.add('has_subdirectory');								// add "has_subdirectory" class
					document.querySelector('.directory_item.dir_list_subdir_loading').classList.remove('dir_list_subdir_loading');					// remove "loading" class
					if ( getSearchParam('show_image_thumbnails') === 'true' ) { showImageThumbnails(); }
					updateStats();																													// update stats
					['has_fonts','has_images','has_media'].forEach( function(subdir_class) { if ( args[1].split(' ').includes(subdir_class) ) { addClass('body',subdir_class) } }); // add new body classes
					initDirListEventListeners();																									// init dir_list event listeners
					initIframeEventListeners();																								break;
				case 'iframe_playlist':																												// iframe_playlist
					document.querySelectorAll('.directory_item.text').forEach( el => el.removeAttribute('data-playlist'));
					removeClass('.directory_item.text','playlist');
					if ( document.querySelector('.directory_item.text.selected') !== null ) { document.querySelector('.directory_item.text.selected').dataset.playlist = args; }
					addClass('.directory_item.text.selected','playlist');																	break;
				case 'local_link': 			showWarning('warning_local_file'); 											break;	// local link warning
				case 'setIframePlayerStatus': // for iframe audio playback
					if ( args === 'play' ) { removeClass('body','is_paused'); addClass('body','is_playing'); } else { removeClass('body','is_playing'); addClass('body','is_paused'); } break;
			}
		}
	}
	// END MESSAGES
	//============================//
	// WARNINGS
	function doFunction(funcName,args) {																									// ===> DO FUNCTION
		var funcDictionary = { 'arrowKeyNavigation':arrowKeyNavigation, 'showThis':showThis, 'doubleClickThis':doubleClickThis, 'null':null, 'clickMenu':clickMenu, 'clickThis':clickThis, 'clearText':clearText,
			'closeButton':closeButton, 'closeContent':closeContent, 'closeFontFile':closeFontFile, 'closePlaylist':closePlaylist, 'closeGlyph':closeGlyph, 'mediaSkip':mediaSkip,
			'openSidebarInContentPane':openSidebarInContentPane, 'reloadContent':reloadContent, 'setLocation':setLocation, 'showDirectorySource':showDirectorySource, 'toggleUIPref':toggleUIPref, 'openInTextEditor':openInTextEditor, 'makePlaylist':makePlaylist,'changeLocation':changeLocation };																		// list of functions to remember and execute after warning button click
		return funcName === 'null' ? null : funcDictionary[funcName](args); 																		// return the function and call it with args
	}
	function openWarning(id,buttonid,funcName,args)	{																						// ===> OPEN WARNING
		addClass('body','has_warning'); removeAttribute('#warnings_container',['class']); addClass('#warnings_container',id); focusButton(buttonid);
		if ( funcName !== undefined && args !== undefined ) { 																						// store funcName and args to complete after clicking warning button
			document.getElementById('warnings_container').dataset.funcname = funcName; document.getElementById('warnings_container').dataset.args = args;
		}
	}
	function showWarning(funcName,args) {																									// ===> SHOW WARNING
		let el = document.getElementById(args);
		switch(true) {
//			case args.length === 2 && args[1] === 'false': doFunction(funcName,args);												break;	// ignore toggle theme toggleSearchParam
//			case ( /theme|alternate_background|show_image_thumbnails|show_numbers|hide_ignored_items|ignore_ignored_items|autoload_media|autoload_index_files|play_all_media|enable_text_editing/.test( args.toString() ) ): 				doFunction(funcName,args);												break;	// ignore toggle theme
			case ( /warning_make_playlist/.test(funcName) ):		openWarning('warning_make_playlist','warning_btn_ok','makePlaylist',args);	break;	// make playlist/filelist
			case ( /warning_no_playlist/.test(funcName) ):			openWarning('warning_no_playlist','warning_btn_ok');						break;	// warning if no playlist made
			case ( /closePlaylist/.test(funcName) ):																									// close playlist
				switch(true) {
					case args !== undefined && args[0] === true:	openWarning('warning_close_playlist','warning_btn_cancel'); 				break;	// close playlist button: close playlist and audio
					case hasClass('#content_pane','has_audio'): 	closeMedia('audio');														break;	// close_button & cmd-W: close audio first
					default: 										openWarning('warning_close_playlist','warning_btn_cancel');							// then close playlist
				}
				break;
			case ( /showThis|closeContent|reloadContent|closeFontFile|changeLocation|setLocation/.test(funcName) && !hasClass('body','has_playlist') && !hasClass('body','has_filelist') ): // warnings for showing content
				switch(true) {																														// upon receipt of message, show iframe warning message, based on the funcName
					case !isTopWindow():
						switch(true) {
							case args.length === 2: 				openWarning('unloading','warning_btn_save',args[0],args[1]); 				break;	// show iframe edited warning after clicking sidebar item
							default: 								openWarning('unloading','warning_btn_save',funcName,args);
						}
						break;	// open iframe unloading warning for closeContent or reloadContent
					case ( /has_font_file|has_glyph/.test( getContentPaneData() ) ):
																	openWarning('warning_close_font','warning_btn_cancel',funcName,[args]);	 	break; // warning for open font file
					default: 										doFunction(funcName,[args]);												break;	// default: perform the requested function
				}
				break;
			case ( !/showThis/.test(funcName) ): 																									// warnings for other functions
				switch(true) {
					case ( funcName === 'toggleUIPref' && /sort_by_*/.test(args[0]) ):	if ( !isTopWindow() ) { doFunction(funcName,args); }	break; // warn if sorting—since sorting reloads content—but not iframe dirlists
					case getContentPaneData() === 'has_font_file' && funcName !== 'arrowKeyNavigation' && el !== null && !el.classList.contains('ignore_warning'):
																	openWarning('warning_close_font','warning_btn_cancel','closeFontFile');		break;	// warn with open font file and close button.
					case hasClass('body','iframe_edited'):			sendMessage('iframe','unloading',funcName,args);							break;	// send unloading message to iframe for closeContent or reloadContent
					case hasClass('body','edited') && funcName !== 'toggleUIPref':	case funcName === 'clearText':
						if ( isTopWindow() ) {						removeClass('#content_pane','has_hidden_text_editor');
																	document.getElementById('content_pane').dataset.content = 'has_text_editor';
						}
																	openWarning('clear','warning_btn_save');									break;
					default: 										doFunction(funcName,args);													break;
				}
		}
	}
	function closeWarning() { removeClass('body','has_warning'); removeClass('#warnings_container, #warning_buttons button'); }					// ===> CLOSE WARNINGS
	function warningButtons(id) {																												// ===> WARNING BUTTONs: what to do after warning button click
		let funcName = document.getElementById('warnings_container').dataset.funcname || '', args = document.getElementById('warnings_container').dataset.args || '';
		switch(id) {
			case 'warning_btn_save': case 'warning_btn_dont_save':			 																			// Save/Don't Save Buttons
				switch(true) {																															// After clicking Save/Don't Save Button...
					case !isTopWindow():
					sendMessage('top',document.getElementById('warnings_container').dataset.funcname,document.getElementById('warnings_container').dataset.funcname,[document.getElementById('warnings_container').dataset.args]);
																																				break;	// send funcName to top (e.g., showThis)
					default:
						document.body.classList.remove('iframe_edited');																// remove iframe_edited class
						doFunction(funcName,args);																									 	// do the function, if any, after clicking don't save button
						focusSidebar();																													// focus sidebar
				}
				delete document.getElementById('warnings_container').dataset.funcname; delete document.getElementById('warnings_container').dataset.args; 	// remove warnings_container data
				if (id === 'warning_btn_save') { document.getElementById('save_text_link').click(); }													// if id = save button, click save text link
				// openInTextEditor();	// why this?
				closeWarning();
				break;
			case 'warning_btn_cancel':												closeWarning();
				switch(true) {
					case isTopWindow():
						if ( hasClass('body','focus_content') ) { focusContent(); }	else { focusSidebar(); }
						break;	// Cancel Button
					case !isTopWindow():
						if ( document.getElementById('warnings_container').dataset.args === 'warning_btn_save' && document.getElementById('warnings_container').dataset.funcname === 'closeContent' ) {
							sendMessage('top','closeContent');
						}
						break;
				}
				break;
			case 'warning_btn_clear':												closeWarning();	clearText();								break; 	// Clear text editor
			case 'warning_btn_ok':																														// OK Button
				switch(true) {
					case hasClass('#warnings_container','warning_close_font'):		closeWarning();	closeFontFile();							break;	// close font
					case hasClass('#warnings_container','warning_close_playlist'):	closeWarning();	closePlaylist();							break;	// close playlist
					case hasClass('#warnings_container','warning_make_playlist'):	closeWarning();	makePlaylist();								break;	// make playlist; open no playlist warning if needed
					case hasClass('#warnings_container','warning_local_bookmark'):																		// no break; local bookmark
					case hasClass('#warnings_container','warning_local_file'):																			// no break; local file
					case hasClass('#warnings_container','warning_local_playlist'):																		// no break; local playlist
					case hasClass('#warnings_container','warning_no_playlist'):		closeWarning();												break;	// no playlist
				}
				doFunction(funcName,args);																									 	// do the function, if any, after clicking OK button
				break;
		}
	}
	// END WARNINGS
	//============================//
	// FINIS! † DEO GRATIAS † //
})();